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.
I am learning next js. I want to call a function getItem of https://www.npmjs.com/package/encrypt-storage
Using below code, but I am getting TypeError: EncryptStorage.getItem is not a function
import dynamic from 'next/dynamic';
const EncryptStorage = dynamic(() => import('encrypt-storage').then((mod) => mod.EncryptStorage(process.env.NEXT_PUBLIC_SKK)), { ssr: false });
console.log(EncryptStorage.getItem('aa'));
please help me to sort it out.
tl;dr: You need to use await import(...) instead of dynamic(() => import(...)) as the latter is only for components.
The longer version:
This was confusing to me as well as the docs don't outright state that you can't import modules with dynamic(...), only that it should be used to import components:
React components can also be imported using dynamic imports, but in this case we use it in conjunction with next/dynamic to make sure it works just like any other React Component.
And indeed, looking at this comment from a maintainer you can't use dynamic(...) to import modules, only components.
Given this, here's a possible solution:
Also, note that .getItem(...) is a method that needs to be called on an instance of EncryptStorage.
// Needs to be ran in an `async` context or environment that supports top-level `await`s
const EncryptStorage = (await import("encrypt-storage")).default;
const encryptStorage = EncryptStorage(process.env.NEXT_PUBLIC_SKK);
console.log(encryptStorage.getItem("aa"));
And, here's a sandbox with a full working example.
I am trying to import a module that could be associated with multiple files depending on conditionals. I originally had that being imported using a require dynamically through a "const" as so:
const question_pack_requires = {};
require.context('../question-packs/section-2', true, /^(.*\.(js$))[^.]*$/im).keys()
.forEach(async (key) => {
const name = path.normalize(key).replace('.js', '');
// THIS IS THE PROMISE-BASED (ASYNC / AWAIT) WEBPACK DYNAMIC IMPORT SYNTAX
const questionPack = await import(`../question-packs/section-2/${name}`);
question_pack_requires[name] = questionPack;
});
export default question_pack_requires;
However, this has broken when upgrading to Webpack 4 and now is throwing a Type Error. So was planning on just importing each of these manually and assigning a key to each one, however, I am unsure the syntax on how to do that.
Basically I need to convert this "require" to an "import" or import these files manually one by one. s there anyone that can help guide me in the best way to do that?
I want to use the validator for an express project. How do I import just two subsets of the packages directly?
Like:
import {isEmail, isEmpty} from 'validator';
or importing each on a separate line.
I just want to know if there is another option apart from import validator from 'validator'; as stated on the https://www.npmjs.com/package/validator
const isEmailValidator = require('validator').isEmail;
const isEmptyValidator = require('validator').isEmpty;
isEmailValidator('bla#bla.com');
Like this you mean? What you wrote should also be valid:
import {isEmail, isEmpty} from 'validator';
isEmail('bla#bla.com');
Edit for clarification: As you can see here https://github.com/chriso/validator.js/blob/master/src/index.js the library is exporting an object with each function. You can import everything import validator from 'validator' or you can use destructuring to get only a few properties.
const {isEmail, isEmpty} = require('validator');
This will not actually stop node from importing all of validator though. This just has node load the validator object that is returned from that modules export and then destructures isEmail and isEmpty out of the exported Object.
Maybe whenever ES6 modules become full supported you can use the regular import syntax. See node.js documentation: ECMAScript Modules.
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