108

Conditional

Is it possible to have conditional import statements like below?

if (foo === bar) {
    import Baz from './Baz';
}

I have tried the above but get the following error (from Babel) when compiling.

'import' and 'export' may only appear at the top level

Dynamic

Is it possible to have dynamic import statements like below?

for (let foo in bar) {
    if (bar.hasOwnProperty(foo)) {
        import Baz from `./${foo}`;
    }
}

The above receives the same error from Babel whilst compiling.

Is this possible to do or is there something I am missing?

Reasoning

The reason I am trying to do this is that I have a lot of imports for a number of "pages" and they follow a similar pattern. I would like to clean up my code base by importing these files with a dynamic for loop.

If this is not possible then is there a better way to handle large number of imports in ES6?

Enijar
  • 6,387
  • 9
  • 44
  • 73
  • 1
    can't inheritance be used in such case? use `super` to call specific. – Jai Mar 10 '16 at 11:11
  • I am already using inheritance, but these "pages" contain "page" specific logic in them. I do have a base "page" class that all extend but this is not enough to clean up the vast number of imports I have. – Enijar Mar 10 '16 at 11:13
  • 1
    @zerkms: They're not hoisted out of blocks - they're syntax errors. – Bergi Mar 10 '16 at 11:48
  • possible duplicate of [ES6 variable import name in node.js](https://stackoverflow.com/questions/29168433/es6-variable-import-name-in-node-js)? – Bergi Mar 10 '16 at 11:53
  • related: [Generating es6 module exports](http://stackoverflow.com/q/35875278/1048572) – Bergi Mar 10 '16 at 11:53
  • also related / possible duplicate: http://stackoverflow.com/questions/36367532/how-can-i-conditionally-import-an-es6-module – ericsoco Nov 30 '16 at 21:17

5 Answers5

73

We do have dynamic imports proposal now with ECMA. This is in stage 2. This is also available as babel-preset.

Following is way to do conditional rendering as per your case.

if (foo === bar) {
    import('./Baz')
    .then((Baz) => {
       console.log(Baz.Baz);
    });
}

This basically returns a promise. Resolution of promise is expected to have the module. The proposal also has things like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.

thecodejack
  • 12,689
  • 10
  • 44
  • 59
  • 4
    This. Dynamic imports are the way to go. They work just like a require(), except they give you a promise rather than a module. – superluminary Feb 21 '18 at 13:21
  • So this approach is finally in! See the more modern [answer](https://stackoverflow.com/a/59928473/2313004) by @LeeGoddard for more info on this – grreeenn Oct 12 '22 at 12:17
25

You can't resolve dynamically your dependencies, as imports are meant for static analysis. However, you can probably use some require here, something like:

for (let foo in bar) {
    if (bar.hasOwnProperty(foo)) {
        const Baz = require(foo).Baz;
    }
}
Jonathan Petitcolas
  • 4,254
  • 4
  • 31
  • 42
  • 10
    "as imports are meant for static analysis. " --- this statement is vague. `import`s are designed to import, not for analysis. – zerkms Mar 10 '16 at 11:14
  • 14
    @zerkms - I think what they meant is that `import` statements are meant to be _suitable_ for static analysis - because they're never conditional, tools can analyse the dependency trees easier. – Joe Clay Mar 10 '16 at 11:16
  • 6
    Hard to understand with "foo" "baz" and "bar" - how about a real life example? – TetraDev Oct 05 '16 at 16:24
  • 1
    This is no longer true. Dynamic imports are now a thing. See here: https://stackoverflow.com/a/46543949/687677 – superluminary Feb 21 '18 at 13:22
19

As this question is highly-ranked by Google, it is worth pointing out that things have changed since the older answers were posted.

MDN has this entry under 'Dynamic Imports':

The import keyword may be called as a function to dynamically import a module. When used this way, it returns a promise.

import('/modules/my-module.js')
  .then((module) => {
    // Do something with the module.
  });

This form also supports the await keyword.

let module = await import('/modules/my-module.js');

MDN also has a more detailed explanation.

A useful article on the subject can be found on Medium.

Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
  • 2022 update: MDN docs now have a separate entry for dynamic imports with more great examples (like conditional imports): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import – grreeenn Oct 12 '22 at 12:14
  • 1
    it seems to, but it doesn't ;) – grreeenn Oct 14 '22 at 16:15
10

Since 2016 a lot has passed in JavaScript world, so I believe it's time to offer most updated info on this topic. Currently Dynamic imports are a reality both on Node and on browsers (natively if you don't care about IE, or with @babel/plugin-syntax-dynamic-import if you do care).

So, consider a sample module something.js with two named exports and one default export:

export const hi = (name) => console.log(`Hi, ${name}!`)
export const bye = (name) => console.log(`Bye, ${name}!`)
export default () => console.log('Hello World!')

We can use import() syntax to easily and cleanly load it conditionally:

if (somethingIsTrue) {
  import('./something.js').then((module) => {
    // Use the module the way you want, as:
    module.hi('Erick') // Named export
    module.bye('Erick') // Named export
    module.default() // Default export
  })
}

But since the return is a Promise, the async/await syntactic sugar is also possible:

async imAsyncFunction () {
  if (somethingIsTrue) {
    const module = await import('./something.js')
    module.hi('Erick')
  }
}

Now think about the possibilities along with Object Destructuring Assignment! For example, we are able to easily put only one of those named exports in memory for posterior use:

const { bye } = await import('./something.js')
bye('Erick')

Or maybe grab one of those named exports and rename it to anything else we want:

const { hi: hello } = await import('./something.js')
hello('Erick')

Or even rename the default exported function to something that makes more sense:

const { default: helloWorld } = await import('./something.js')
helloWorld()

Just a last (but no least) note: import() may looks like a function call, but it isn't a Function. It's a special syntax that just happens to use parentheses (similar to what happens with super()). So it's not possible to assign import to a variable or use things of the Function prototype, like call/apply.

Erick Petrucelli
  • 14,386
  • 8
  • 64
  • 84
1

Require will not solve your problem as it is a synchronous call. There are several options and they all involve

  1. Asking for the module you need
  2. Waiting for a promise to return the module

In ECMA Script there is support for lazy loading modules using SystemJS. This of course isn't supported in all browsers, so in the meantime you can use JSPM or a SystemJS shim.

https://github.com/ModuleLoader/es6-module-loader

Henrik Vendelbo
  • 176
  • 2
  • 3