NodeJS how to import conditionally? [duplicate] - javascript

Is it possible to import something into a module providing a variable name while using ES6 import?
I.e. I want to import some module at a runtime depending on values provided in a config:
import something from './utils/' + variableName;
Note that I’m using Node.js, but answers must take compatibility with ECMAScript modules into consideration.

Not with the import statement. import and export are defined in such a way that they are statically analyzable, so they cannot depend on runtime information.
You are looking for the loader API (polyfill), but I'm a bit unclear about the status of the specification:
System.import('./utils/' + variableName).then(function(m) {
console.log(m);
});

Whilst this is not actually a dynamic import (eg in my circumstance, all the files I'm importing below will be imported and bundled by webpack, not selected at runtime), a pattern I've been using which may assist in some circumstances is:
import Template1 from './Template1.js';
import Template2 from './Template2.js';
const templates = {
Template1,
Template2
};
export function getTemplate (name) {
return templates[name];
}
or alternatively:
// index.js
export { default as Template1 } from './Template1';
export { default as Template2 } from './Template2';
// OtherComponent.js
import * as templates from './index.js'
...
// handy to be able to fall back to a default!
return templates[name] || templates.Template1;
I don't think I can fall back to a default as easily with require(), which throws an error if I try to import a constructed template path that doesn't exist.
Good examples and comparisons between require and import can be found here: http://www.2ality.com/2014/09/es6-modules-final.html
Excellent documentation on re-exporting from #iainastacio:
http://exploringjs.com/es6/ch_modules.html#sec_all-exporting-styles
I'm interested to hear feedback on this approach :)

There is a new specification which is called a dynamic import for ES modules.
Basically, you just call import('./path/file.js') and you're good to go. The function returns a promise, which resolves with the module if the import was successful.
async function importModule() {
try {
const module = await import('./path/module.js');
} catch (error) {
console.error('import failed');
}
}
Use cases
Use-cases include route based component importing for React, Vue etc and the ability to lazy load modules, once they are required during runtime.
Further Information
Here's is an explanation on Google Developers.
Browser compatibility (April 2020)
According to MDN it is supported by every current major browser (except IE) and caniuse.com shows 87% support across the global market share. Again no support in IE or non-chromium Edge.

In addition to Felix's answer, I'll note explicitly that this is not currently allowed by the ECMAScript 6 grammar:
ImportDeclaration :
import ImportClause FromClause ;
import ModuleSpecifier ;
FromClause :
from ModuleSpecifier
ModuleSpecifier :
StringLiteral
A ModuleSpecifier can only be a StringLiteral, not any other kind of expression like an AdditiveExpression.

I understand the question specifically asked for ES6 import in Node.js, but the following might help others looking for a more generic solution:
let variableName = "es5.js";
const something = require(`./utils/${variableName}`);
Note if you're importing an ES6 module and need to access the default export, you will need to use one of the following:
let variableName = "es6.js";
// Assigning
const defaultMethod = require(`./utils/${variableName}`).default;
// Accessing
const something = require(`./utils/${variableName}`);
something.default();
You can also use destructuring with this approach which may add more syntax familiarity with your other imports:
// Destructuring
const { someMethod } = require(`./utils/${variableName}`);
someMethod();
Unfortunately, if you want to access default as well as destructuring, you will need to perform this in multiple steps:
// ES6 Syntax
Import defaultMethod, { someMethod } from "const-path.js";
// Destructuring + default assignment
const something = require(`./utils/${variableName}`);
const defaultMethod = something.default;
const { someMethod, someOtherMethod } = something;

you can use the non-ES6 notation to do that. this is what worked for me:
let myModule = null;
if (needsToLoadModule) {
myModule = require('my-module').default;
}

I had similar problem using Vue.js: When you use variable in import(variableName) at build time Webpack doesn't know where to looking for. So you have to restrict it to known path with propriate extension like that:
let something = import("#/" + variableName + ".js")
That answer in github for the same issue was very helpful for me.

I less like this syntax, but it work:
instead of writing
import memberName from "path" + "fileName";
// this will not work!, since "path" + "fileName" need to be string literal
use this syntax:
let memberName = require("path" + "fileName");

Dynamic import() (available in Chrome 63+) will do your job. Here's how:
let variableName = 'test.js';
let utilsPath = './utils/' + variableName;
import(utilsPath).then((module) => { module.something(); });

./utils/test.js
export default () => {
doSomething...
}
call from file
const variableName = 'test';
const package = require(`./utils/${variableName}`);
package.default();

I would do it like this
function load(filePath) {
return () => System.import(`${filePath}.js`);
// Note: Change .js to your file extension
}
let A = load('./utils/' + variableName)
// Now you can use A in your module

It depends. You can use template literals in dynamic imports to import a file based on a variable.
I used dynamic imports to add .vue files to vue router. I have excluded the Home.vue view import.
const pages = [
'About',
['About', 'Team'],
]
const nodes = [
{
name: 'Home',
path: '/',
component: Home,
}
]
for (const page of pages) {
if (typeof page === 'string') {
nodes.push({
name: page,
path: `/${page}`,
component: import(`./views/${page}.vue`),
})
} else {
nodes.push({
name: _.last(page),
path: `/${page.join('/')}`,
component: import(`./views/${_.last(page)}.vue`)
})
}
}
This worked for me. I was using yarn + vite + vue on replit.

Related

ES6 Imports inside Export default

I'm currently migrating the whole code of a NodeJS application from ES5 to ES6/7.
I'm having trouble when it comes to imports :
First, I understood that making an import directly call the file. For example :
import moduleTest from './moduleTest';
This code will go into moduleTest.js and execute it.
So, the real question is about this code :
import mongoose from 'mongoose';
import autopopulate from 'mongoose-autopopulate';
import dp from 'mongoose-deep-populate';
import { someUtils } from '../utils';
const types = mongoose.Schema.Types;
const deepPopulate = dp(mongoose);
export default () => {
// DOES SOMETHING USING types AND deepPopulate
return someThing;
};
export const anotherModule = () => {
// ALSO USE types and deepPopulate
};
Is this a good practice to have types and deepPopulate declared outside of the two exports ? Or should I declare them in each export ?
The reason of this question is that I'm having a conflict due to this practice (to simplify, let's say that dp(mongoose) will call something that is not declared yet)
You can only have one 'default' export to a module, or you can have multiple 'named' exports per module. Take a look at the following for a good description of handling exports in ES6: ECMAScript 6 Modules: The Final Syntax

ES6 module import a function value [duplicate]

Is it possible to pass options to ES6 imports?
How do you translate this:
var x = require('module')(someoptions);
to ES6?
There is no way to do this with a single import statement, it does not allow for invocations.
So you wouldn't call it directly, but you can basically do just the same what commonjs does with default exports:
// module.js
export default function(options) {
return {
// actual module
}
}
// main.js
import m from 'module';
var x = m(someoptions);
Alternatively, if you use a module loader that supports monadic promises, you might be able to do something like
System.import('module').ap(someoptions).then(function(x) {
…
});
With the new import operator it might become
const promise = import('module').then(m => m(someoptions));
or
const x = (await import('module'))(someoptions)
however you probably don't want a dynamic import but a static one.
Concept
Here's my solution using ES6
Very much inline with #Bergi's response, this is the "template" I use when creating imports that need parameters passed for class declarations. This is used on an isomorphic framework I'm writing, so will work with a transpiler in the browser and in node.js (I use Babel with Webpack):
./MyClass.js
export default (Param1, Param2) => class MyClass {
constructor(){
console.log( Param1 );
}
}
./main.js
import MyClassFactory from './MyClass.js';
let MyClass = MyClassFactory('foo', 'bar');
let myInstance = new MyClass();
The above will output foo in a console
EDIT
Real World Example
For a real world example, I'm using this to pass in a namespace for accessing other classes and instances within a framework. Because we're simply creating a function and passing the object in as an argument, we can use it with our class declaration likeso:
export default (UIFramework) => class MyView extends UIFramework.Type.View {
getModels() {
// ...
UIFramework.Models.getModelsForView( this._models );
// ...
}
}
The importation is a bit more complicated and automagical in my case given that it's an entire framework, but essentially this is what is happening:
// ...
getView( viewName ){
//...
const ViewFactory = require(viewFileLoc);
const View = ViewFactory(this);
return new View();
}
// ...
I hope this helps!
Building on #Bergi's answer to use the debug module using es6 would be the following
// original
var debug = require('debug')('http');
// ES6
import * as Debug from 'debug';
const debug = Debug('http');
// Use in your code as normal
debug('Hello World!');
I've landed on this thread looking up for somewhat similar and would like to propose a sort of solution, at least for some cases (but see Remark below).
Use case
I have a module, that is running some instantiation logic immediately upon loading. I do not like to call this init logic outside the module (which is the same as call new SomeClass(p1, p2) or new ((p1, p2) => class SomeClass { ... p1 ... p2 ... }) and alike).
I do like that this init logic will run once, kind of a singular instantiation flow, but once per some specific parametrized context.
Example
service.js has at its very basic scope:
let context = null; // meanwhile i'm just leaving this as is
console.log('initialized in context ' + (context ? context : 'root'));
Module A does:
import * as S from 'service.js'; // console has now "initialized in context root"
Module B does:
import * as S from 'service.js'; // console stays unchanged! module's script runs only once
So far so good: service is available for both modules but was initialized only once.
Problem
How to make it run as another instance and init itself once again in another context, say in Module C?
Solution?
This is what I'm thinking about: use query parameters. In the service we'd add the following:
let context = new URL(import.meta.url).searchParams.get('context');
Module C would do:
import * as S from 'service.js?context=special';
the module will be re-imported, it's basic init logic will run and we'll see in the console:
initialized in context special
Remark: I'd myself advise to NOT practice this approach much, but leave it as the last resort. Why? Module imported more than once is more of an exception than a rule, so it is somewhat unexpected behavior and as such may confuse a consumers or even break it's own 'singleton' paradigms, if any.
I believe you can use es6 module loaders.
http://babeljs.io/docs/learn-es6/
System.import("lib/math").then(function(m) {
m(youroptionshere);
});
You just need to add these 2 lines.
import xModule from 'module';
const x = xModule('someOptions');
Here's my take on this question using the debug module as an example;
On this module's npm page, you have this:
var debug = require('debug')('http')
In the line above, a string is passed to the module that is imported, to construct. Here's how you would do same in ES6
import { debug as Debug } from 'debug'
const debug = Debug('http');
Hope this helps someone out there.
I ran into an analogous syntax issue when trying to convert some CJS (require()) code to ESM (import) - here's what worked when I needed to import Redis:
CJS
const RedisStore = require('connect-redis')(session);
ESM Equivalent
import connectRedis from 'connect-redis';
const RedisStore = connectRedis(session);
You can pass parameters in the module specifier directly:
import * as Lib from "./lib?foo=bar";
cf: https://flaming.codes/en/posts/es6-import-with-parameters

What is the difference between these two methods for importing a javascript module?

I'm new to javascript development. I'm using npm.
I wanted to generate some uuids so I found a uuid package:
https://www.npmjs.com/package/uuid
I installed it by running
npm install uuid
Now, I want to use this package in my code.
I found that there are 2 ways to do it. The npm docs suggest:
// Generate a v4 UUID (random)
const uuidV4 = require('uuid/v4');
uuidV4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1'
But I guessed I could also do (which seems to work):
import uuid from 'uuid';
console.info(uuid.v4());
My questions:
What are the differences between these?
How can I tell what a module exports so I know what I can import and under what path? For example, it seems I also could have done import v4 from 'uuid'... but I don't really understand how this works.
require is the es5 way to include a module in your project.
import is the es6 way to include a module in your project.
If you need to support older browsers and wish to use es6 features, you will need to use a transpilier such as Babel to convert your code into es5 format.
With import you can take pieces of the module. Lets use this as an example:
// test.js
export default () => { console.log("Hi, I'm a default export"); }
export NotDefault = () => { console.log("I am not the default export"); }
Now if you were to try to:
import NotDefault from "test.js";
You wouldn't actually be importing NotDefault, you would import the default export.
To import NotDefault, you would use the following format:
import { NotDefault } from "test.js";
ES5 Syntax
module.exports = function() {
// this is now the default export of this file.
// you can use this file in another by typing var MyVar = require("module_name");
}
exports.MyFunc = function() {
// this is also exported from this file
// you can use this by var MyVar = require("module_name").myFunc;
}
ES6 Syntax
default export function() {
// this is the default export of this file
// use can use me by typing import MyVar from "module_name";
}
export const MyFunc = function() {
// this is also exported and can be used by typing
// import { MyFunc } from "module_name";
}
I hope this helps!

In VSCode when exporting functions: "Individual declarations must be all exported or all local"

I recently upgraded to Visual Studio Code 0.5.0 and some new errors cropped up that weren't there before.
I have a bunch of functions that are declared locally and then exported. Since the upgrade, however, hovering over each of the local function names produces the error Individual declarations in merged declaration functionName must be all exported or all local.
This is an example local function that is exported.
var testParamsCreatorUpdater = function (lTestParams, creatorID){
lTestParams.creator = creatorID;
return lTestParams;
};
module.exports.testParamsCreatorUpdater = testParamsCreatorUpdater;
I realize I can change this to...
module.exports.testParamsCreatorUpdater = function (lTestParams, creatorID){
lTestParams.creator = creatorID;
return lTestParams;
};
And prepend module.exports. to every testParamsCreatorUpdater() call.
But why is the first snippet wrong? As I understand it, require() makes everything in the module.exports object available to whatever required it.
I had this issue in Webstorm , I Restarted it and it went away
I think at a JavaScript level it cannot differentiate between:
var testParamsCreatorUpdater = ...
and
module.exports.testParamsCreatorUpdater = ...
as the names are the same. I got the exact same error (leading me to this post) in TypeScript when I tried this:
import { AuditService } from '../services/audit.service';
import { Audit } from '../models/audit.model';
#Component({
selector: 'audit',
templateUrl: './audit.component.html',
})
export class Audit {
constructor(private auditService: AuditService) {
}
}
So TypeScript did not like that I imported a module called Audit and exported a class also called Audit.
You are exporting a variable in this file which is imported in the same file module (locally).
I think it's related to the feature of merged declaration for TypeScript ref. I have not done the detailed research for Typescript but it seems that it can include Javascript in the Typescript file.
I guess the way testParamsCreatorUpdater was declared in the Javascript was detected to be error by VSCode because it thinks the two declarations cannot be merged.
So DuckDuckGo got me here searching for the exact same error, but in 2022. I didn't find the exact reason so posting as an update and for completeness.
import { Something, SomethingElse } from './some/path';
import { ref } from 'vue';
// Many lines of code
function doTheStuff() {
// The declarations were previously just local variables ...
// const Something = ref<Something>();
// const SomethingElse = ref<SomethingElse>();
}
// ... but then I decided to export them and got the error
export const Something = ref<Something>();
export const SomethingElse = ref<SomethingElse>();
You simply can not import Something as a type and then export Something variable as a value of a kind (here a vue reference object). However, you can name a local variable the same name as the type, like I originally had. It is the import/export combination where things got broken. Solution for me was to locally rename the types:
import {
Something as SomethingType,
SomethingElse as SomethingElseType
} from './some/path';
import { ref } from 'vue';
// ...
// No naming conflict anymore
export const Something = ref<SomethingType>();
export const SomethingElse = ref<SomethingElseType>();

is there any way to obtain a reference to (and use) an es6/2015 import in the same expression? [duplicate]

Is it possible to pass options to ES6 imports?
How do you translate this:
var x = require('module')(someoptions);
to ES6?
There is no way to do this with a single import statement, it does not allow for invocations.
So you wouldn't call it directly, but you can basically do just the same what commonjs does with default exports:
// module.js
export default function(options) {
return {
// actual module
}
}
// main.js
import m from 'module';
var x = m(someoptions);
Alternatively, if you use a module loader that supports monadic promises, you might be able to do something like
System.import('module').ap(someoptions).then(function(x) {
…
});
With the new import operator it might become
const promise = import('module').then(m => m(someoptions));
or
const x = (await import('module'))(someoptions)
however you probably don't want a dynamic import but a static one.
Concept
Here's my solution using ES6
Very much inline with #Bergi's response, this is the "template" I use when creating imports that need parameters passed for class declarations. This is used on an isomorphic framework I'm writing, so will work with a transpiler in the browser and in node.js (I use Babel with Webpack):
./MyClass.js
export default (Param1, Param2) => class MyClass {
constructor(){
console.log( Param1 );
}
}
./main.js
import MyClassFactory from './MyClass.js';
let MyClass = MyClassFactory('foo', 'bar');
let myInstance = new MyClass();
The above will output foo in a console
EDIT
Real World Example
For a real world example, I'm using this to pass in a namespace for accessing other classes and instances within a framework. Because we're simply creating a function and passing the object in as an argument, we can use it with our class declaration likeso:
export default (UIFramework) => class MyView extends UIFramework.Type.View {
getModels() {
// ...
UIFramework.Models.getModelsForView( this._models );
// ...
}
}
The importation is a bit more complicated and automagical in my case given that it's an entire framework, but essentially this is what is happening:
// ...
getView( viewName ){
//...
const ViewFactory = require(viewFileLoc);
const View = ViewFactory(this);
return new View();
}
// ...
I hope this helps!
Building on #Bergi's answer to use the debug module using es6 would be the following
// original
var debug = require('debug')('http');
// ES6
import * as Debug from 'debug';
const debug = Debug('http');
// Use in your code as normal
debug('Hello World!');
I've landed on this thread looking up for somewhat similar and would like to propose a sort of solution, at least for some cases (but see Remark below).
Use case
I have a module, that is running some instantiation logic immediately upon loading. I do not like to call this init logic outside the module (which is the same as call new SomeClass(p1, p2) or new ((p1, p2) => class SomeClass { ... p1 ... p2 ... }) and alike).
I do like that this init logic will run once, kind of a singular instantiation flow, but once per some specific parametrized context.
Example
service.js has at its very basic scope:
let context = null; // meanwhile i'm just leaving this as is
console.log('initialized in context ' + (context ? context : 'root'));
Module A does:
import * as S from 'service.js'; // console has now "initialized in context root"
Module B does:
import * as S from 'service.js'; // console stays unchanged! module's script runs only once
So far so good: service is available for both modules but was initialized only once.
Problem
How to make it run as another instance and init itself once again in another context, say in Module C?
Solution?
This is what I'm thinking about: use query parameters. In the service we'd add the following:
let context = new URL(import.meta.url).searchParams.get('context');
Module C would do:
import * as S from 'service.js?context=special';
the module will be re-imported, it's basic init logic will run and we'll see in the console:
initialized in context special
Remark: I'd myself advise to NOT practice this approach much, but leave it as the last resort. Why? Module imported more than once is more of an exception than a rule, so it is somewhat unexpected behavior and as such may confuse a consumers or even break it's own 'singleton' paradigms, if any.
I believe you can use es6 module loaders.
http://babeljs.io/docs/learn-es6/
System.import("lib/math").then(function(m) {
m(youroptionshere);
});
You just need to add these 2 lines.
import xModule from 'module';
const x = xModule('someOptions');
Here's my take on this question using the debug module as an example;
On this module's npm page, you have this:
var debug = require('debug')('http')
In the line above, a string is passed to the module that is imported, to construct. Here's how you would do same in ES6
import { debug as Debug } from 'debug'
const debug = Debug('http');
Hope this helps someone out there.
I ran into an analogous syntax issue when trying to convert some CJS (require()) code to ESM (import) - here's what worked when I needed to import Redis:
CJS
const RedisStore = require('connect-redis')(session);
ESM Equivalent
import connectRedis from 'connect-redis';
const RedisStore = connectRedis(session);
You can pass parameters in the module specifier directly:
import * as Lib from "./lib?foo=bar";
cf: https://flaming.codes/en/posts/es6-import-with-parameters

Categories

Resources