06. Dynamic Import

By default ES modules completely static:

  • all imports should be resolved at compile time;

  • import declaration can only appear at the top level of a module;

  • module specifier in import expression if fixed and can not contain variables and expressions.

Dynamic import proposal adds to JS a dynamic import operator:

  • The parameter is a string with a module specifier.

  • Can be any expression whose result can be coerced to a string.

  • The result of the import operator is a Promise.

  • Once the module is completely loaded, the Promise is fulfilled with it.

const moduleSpecifier = './dir/someModule.js';
import(moduleSpecifier).then(someModule => {
    ...
});

Features:

  • The operator is used like a function (but actually import() is an operator).

  • If import() is used in a module, it can occur anywhere at any level, and is not hoisted.

  • import() can be used from scripts, not just from modules.

  • import() accepts arbitrary strings, not just static string literals.

  • The presence of import() in the module does not establish a dependency which must be fetched and evaluated before the containing module is evaluated.

  • import() does not establish a dependency which can be statically analysed.

  • import() follows the ES modules rules: singleton, specifiers, CORS etc.

  • The order of used import() in the code doesn’t have anything in common with the order they are resolved.

  • The dynamic import executes a script as a module, providing its own context which is different from the global.

  • import() specifier is always related to the file it's called.

Conditional loading of modules:

Use anywhere in the code:

if (isLegacyPlatform()) {
    import(···).then(···);
}

Use variables to construct module declaration:

const locale = "en";
import(`./utils_${locale}.js`).then(utils => {
  console.log("utils", utils);
  utils.default();
});

Loading modules on demand:

button.addEventListener("click", event => {
  import("./dialogBox.js")
    .then(dialogBox => {
      dialogBox.open();
    })
    .catch(error => {
      /* Error handling */
    });
});

Features:

  • you can use dynamic import for lazy or conditional loading and in user-depending action

  • dynamic import() can be used anywhere in your scripts

  • import() takes string literals and you can construct the specifier depending on your needs

Use Promises patterns to load modules:

You can dynamically load multiple modules at the same time via Promise.all():

const modules = Promise.all([
    import('./module1.js'),
    import('./module2.js'),
    import('./module3.js'),
]);


modules.then(([module1, module2, module3]) => {
    ···
});

Async functions and import()

async function main() {
  const myModule = await import("./myModule.js");
  const { export1, export2 } = await import("./myModule.js");
  const [module1, module2, module3] = await Promise.all([
    import("./module1.js"),
    import("./module2.js"),
    import("./module3.js")
  ]);
}
main();

Dynamically load module using async/await:

(async () => {
  const myModule = await import("./myModule.js");
})();

References:

Last updated