ES modules are a way to organize cohesive chunks of code in JavaScript. Here's a simple ES module:
// An ES moduleimport { concat } from './concatModule.js';concat('a', 'b'); // => 'ab'
import { concat } from './concatModule.js'
is considered a static import.
Static importing works in most situations. But sometimes to save client's bandwidth you may choose to load the modules dynamically.
You can import ES modules dynamically if you use import
as a function — import(pathToModule)
— a feature available starting ES2020.
Let's see how ES modules' dynamic import works, and when it's useful.
1. Dynamic importing
When the import
keyword is used as a function:
const module = await import(path);
import(path)
returns a promise and starts an asynchronous task to load the module located at path
. If the module is loaded successfully, then the promise resolves to the module content, otherwise, the promise rejects.
path
can be any expression that evaluates to a string denoting a path. Valid path expressions are:
// Classic string literalsconst module1 = await import('./myModule.js');// A variableconst path = './myOtherModule.js';const module2 = await import(path);// Function callconst getPath = (version) => `./myModule/versions/${version}.js`;const moduleVersion1 = await import(getPath('v1.0'));const moduleVersion2 = await import(getPath('v2.0'));
import(path)
, returning a promise, works great with the async/await
syntax. For example, let's load a module inside of an asynchronous function:
async function loadMyModule() { const myModule = await import('./myModule.js'); // ... use myModule}loadMyModule();
Now, knowing how to load the module, let's extract components (default or named) from the imported module.
2. Importing components
2.1 Dynamic import of named
Let's consider the following module, named namedConcat.js
:
// namedConcat.jsexport const concat = (paramA, paramB) => paramA + paramB;
namedConcat
performs a named export of concat
function.
To dynamically import namedConcat.js
, and access the named export concat
, then destructure the resolved module object by the named export:
async function loadMyModule() { const { concat } = await import('./namedConcat.js'); concat('b', 'c'); // => 'bc'}loadMyModule();
2.2 Dynamic import of default
To dynamically import a default, just read the default
property from the module object.
Let's say that defaultConcat.js
exports the function as a default
export:
// defaultConcat.jsexport default (paramA, paramB) => paramA + paramB;
When importing defaultConcat.js
dynamically, and specifically accessing the default
export, just read the default
property.
But there's a nuance. default
is a keyword in JavaScript, so it cannot be used as a variable name. What you do is use destructuring with aliasing:
async function loadMyModule() { const { default: defaultFunc } = await import('./defaultConcat.js'); defaultFunc('b', 'c'); // => 'bc'}loadMyModule();
2.3 Dynamic import of mixed content
If the imported module exports default
and multiple named exports, then you can access all these components using a single destructuring:
async function loadMyModule() { const { default: defaultImport, namedExport1, namedExport2 } = await import('./mixedExportModule.js'); // ...}loadMyModule();
3. When to use dynamic import
I recommend using dynamic import when importing big modules conditionally:
- you might use the module from time to time, depending on runtime conditions
- you might want to load different versions of a big module, also depending on runtime conditions.
For example:
async function execBigModule(condition) { if (condition) { const { funcA } = await import('./bigModuleA.js'); funcA(); } else { const { funcB } = await import('./bigModuleB.js'); funcB(); }}execBigModule(true);
For small modules (like namedConcat.js
or defaultConcat.js
from the previous example), that have a few lines of code, the dynamic import doesn't worth the hassle.
4. Conclusion
To load dynamically a module call import(path)
as a function with an argument indicating the specifier (aka path) to a module.
const module = await import(path)
returns a promise that resolves to an object containing the components of the imported module.
In that object, the default
property contains the default export, and the named exports are contained in the corresponding properties:
const { default: defaultComponent, namedExport1, namedExport2} = await import(path);
The dynamic import is supported by both Node.js (version 13.2 and above) and most modern browsers.
What other interesting use cases of the dynamic import do you know? Share your idea in a comment below!