145

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.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
Vytautas Butkus
  • 5,365
  • 6
  • 30
  • 45

12 Answers12

78

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);
});
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 3
    do I need to "require" System? It's not on a global scope.. P.s. I'm using babel js – Vytautas Butkus Mar 20 '15 at 14:13
  • It's not implemented anywhere yet AFAIK. You have to use the polyfill from the link. – Felix Kling Mar 20 '15 at 14:13
  • 1
    Just checked, it kinda works (tries loading the file) but then messes up paths for other modules... Too bad it's not supported natively.. – Vytautas Butkus Mar 20 '15 at 14:26
  • 4
    Is this still an issue? I need to load ES6 modules dinamically but I haven't had success.. – calbertts Feb 06 '16 at 07:31
  • 2
    While this was the best answer in 2015, [dynamic `import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports) was standardized in ES2020 and is supported by Node.js and all modern browsers, see [this answer](https://stackoverflow.com/a/48133721/157247). – T.J. Crowder Jan 09 '22 at 11:08
41

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 :)

ptim
  • 14,902
  • 10
  • 83
  • 103
  • 1
    Upvote. I used the "or alternatively" approach. Worked like a charm for my custom localization solution. – groundh0g Dec 24 '18 at 02:29
  • 1
    [React docs: Choosing the Type at Runtime](https://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime) – ptim Jan 21 '20 at 08:57
  • 2
    I should have thought of this. Great solution, no matter how you're importing things (and even if you aren't importing anything). Have a list of items that you want to get the name of, or get by name, later? Put them in an object literal instead of an array literal, and let the object syntax take care of naming them based on their local constant/variable name. If you need them as a list again, just do `Object.values(templates)`. – Andrew Koster Jun 11 '20 at 22:40
  • Thanks, this is a great way to solve this problem simply. Very appreciated. – DrCord Jan 20 '21 at 23:16
41

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.

zcoop98
  • 2,590
  • 1
  • 18
  • 31
Nicolai Schmid
  • 1,022
  • 1
  • 14
  • 29
  • are you sure about your edit? the proposal shows an example with a variable path: https://github.com/tc39/proposal-dynamic-import#example – phil294 May 07 '18 at 14:45
  • @Blauhirn I was, but your link clearly shows that it is a possibility. Although I have no idea how webpack, for example would resolve these imports – Nicolai Schmid May 07 '18 at 15:31
  • correct me if I'm wrong, but webpack does not process these, does it? I thought it was the point of dynamic imports that they run "as is" in the browser. – phil294 May 07 '18 at 16:19
  • Yes, you can run them in the browser as is. But webpack automatically uses the imports to split your app into multiple bundles for different parts of your app, for example for routes. I use them all the time and they are really helpful. And as far as "processing" goes; webpack will pass the imports the babel, which will polyfill the feature for older browsers. – Nicolai Schmid May 07 '18 at 16:22
  • To be clear: dynamic import() *will* work with variables and is not required to be statically analyzable (that's the whole point of 'dynamic', surely?). See my answer. – Velojet Jan 25 '19 at 02:42
  • Yes, it will work and in many cases, it is the right way to go. Although you lose the benefit of webpack being able to split your bundles automatically. – Nicolai Schmid Jan 27 '19 at 19:27
  • @velojet I think the purpose of dymanic import is twofold. For one it is about import dynamic (generated) modules during runtime. The other is for lazy loading. Regular imports will be imported on boot, a dynamic import can import a module once it is required. – Nicolai Schmid Jan 27 '19 at 19:29
  • Unfortunately it is async-contagious, which completely disqualifies it as a drop-in solution. You WILL have to RefactorBasicallyEverything™ if you want to use it. – Szczepan Hołyszewski Dec 18 '22 at 16:22
27

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.

Community
  • 1
  • 1
apsillers
  • 112,806
  • 17
  • 235
  • 239
  • 2
    It's a pity this wasn't extended to include `const` `string literal`s. They're statically analysable, aren't they? It would make reusing the location of a dependency a possibility. (e.g. import a template and have both the template and the location of the template available). – nicodemus13 Oct 08 '17 at 13:47
10

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;
MCTaylor17
  • 411
  • 5
  • 16
5

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.

stanimirsp
  • 2,548
  • 2
  • 26
  • 36
3

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;
}
mlevanon
  • 39
  • 2
2

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");
Gil Epshtain
  • 8,670
  • 7
  • 63
  • 89
  • 1
    @UlysseBN Different in a bad way? Or a way that doesn't really matter? – Sam Jul 29 '19 at 09:19
  • @Jacob they really are completely different, so yeah it could matter depending on what you are doing. The first syntax is statically evaluated, whereas the second one is dynamically evaluated. So for instance, if you are using webpack, it will not be a to perform tree-shaking correctly with the second one. There are many other differences, I'd suggest you read the doc and see which one is more appropriate for you! – Ulysse BN Jul 29 '19 at 09:26
  • @Jacob - No really matter (in most cases). `require()` is a Node.JS method for loading files, which is the early version. `import` statement is the newer version, which is now part of the official language syntax. However in many cases the browser will use the previous one (behind the science). The require statement will also cash your files, so if a file is loaded 2nd time it will be loaded from memory (better performance). The import way has it's own benefits - if you are using WebPack. then webpack can remove dead references (these scripts will not be downloaded to the client). – Gil Epshtain Jul 29 '19 at 10:54
2

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(); });
Velojet
  • 878
  • 11
  • 18
1

./utils/test.js

export default () => {
  doSomething...
}

call from file

const variableName = 'test';
const package = require(`./utils/${variableName}`);
package.default();
0

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
april
  • 1
0

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.

Int Fract
  • 1
  • 2