Dynamic imports

Export and import statements that we covered in previous chapters are called “static”.

That’s because they are indeed static. The syntax is very strict.

First, we can’t dynamically generate any parameters of import.

The module path must be a primitive string, can’t be a function call. This won’t work:

import ... from getModuleName(); // Error, only from "string" is allowed

Second, we can’t import conditionally or at run-time:

if(...) {
  import ...; // Error, not allowed!
}

{
  import ...; // Error, we can't put import in any block
}

That’s because import/export aim to provide a backbone for the code structure. That’s a good thing, as code structure can be analyzed, modules can be gathered and bundled together, unused exports can be removed (“tree-shaken”). That’s possible only because the structure of imports/exports is simple and fixed.

But how can we import a module dynamically, on-demand?

The import() function

The import(module) function can be called from anywhere. It returns a promise that resolves into a module object.

The usage pattern looks like this:

let modulePath = prompt("Module path?");

import(modulePath)
  .then(obj => <module object>)
  .catch(err => <loading error, no such module?>)

Or, we could use let module = await import(modulePath) if inside an async function.

For instance, if we have the following say.js:

// 📁 say.js
export function hi() {
  alert(`Hello`);
}

export function bye() {
  alert(`Bye`);
}

…Then dynamic import can be like this:

let {hi, bye} = await import('./say.js');

hi();
bye();

Or, if say.js has the default export:

// 📁 say.js
export default function() {
  alert("Module loaded (export default)!");
}

…Then, in order to access it, we can use default property of the module object, as explained in the previous chapter.

So, the dynamic import will be like this:

let {default: say} = await import('./say.js'); // save .default property in say variable

say();

Here’s the full example:

Result
say.js
index.html
export function hi() {
  alert(`Hello`);
}

export function bye() {
  alert(`Bye`);
}

export default function() {
  alert("Module loaded (export default)!");
}
<!doctype html>
<script>
  async function load() {
    let say = await import('./say.js');
    say.hi(); // Hello!
    say.bye(); // Bye!
    say.default(); // Module loaded (export default)!
  }
</script>
<button onclick="load()">Click me</button>
Please note:

Dynamic imports work in regular scripts, they don’t require script type="module".

Please note:

Although import() looks like a function call, it’s a special syntax that just happens to use parentheses (similar to super()).

So we can’t copy import to a variable or use .call/apply with it.

Tutorial map

Comments

read this before commenting…
  • If you have suggestions what to improve - please submit a GitHub issue or a pull request instead of commenting.
  • If you can't understand something in the article – please elaborate.
  • To insert a few words of code, use the <code> tag, for several lines – use <pre>, for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)