A normal import at the top of a file is static: the browser loads it before your code runs. Dynamic import() is a function-like expression that loads a module when you call it, and returns a promise.
const module = await import("./chart.js");
module.drawChart(data);
Why load code later?
- A heavy feature (a chart, an editor, a PDF viewer) is only needed after a click, so there is no reason to download it on page load. This is lazy loading, and it makes the first load faster.
- Load a module only if a condition is true.
- Choose a module by name at run time.
Try it
<button id="load">Load module</button>
<p id="out"></p>
<script type="module">
// In a real project: await import("./greeting.js")
const source = "data:text/javascript," + encodeURIComponent(`
export default function greet(name) { return "Hello from a lazy module, " + name; }
`);
document.querySelector("#load").addEventListener("click", async () => {
document.querySelector("#out").textContent = "Loading…";
const { default: greet } = await import(source);
document.querySelector("#out").textContent = greet("Maya");
});
</script>
The module is not loaded until the button is clicked.
What you get back
A module namespace object: named exports are properties, and the default export is .default.
const { add, subtract } = await import("./math.js");
const { default: Logger } = await import("./Logger.js");
Handle failures
The network can fail, so a dynamic import can reject.
try {
const { renderChart } = await import("./chart.js");
renderChart(data);
} catch (error) {
showMessage("Could not load the chart. Please try again.");
}
Load once, reuse
Imported modules are cached, so calling import() again for the same file is cheap. A common pattern keeps the promise:
let chartModule;
function loadChart() {
chartModule ??= import("./chart.js");
return chartModule;
}
Preloading
You can start loading early (on hover, or after the page is idle), so the module is ready by the time the user needs it:
button.addEventListener("pointerenter", () => import("./editor.js"), { once: true });
Code splitting
Bundlers use dynamic import() as a signal to split your code into separate chunks that load separately. See Code Splitting.
Differences from static import
Static import | import() | |
|---|---|---|
| Where | top level only | anywhere |
| Loads | before the code runs | when called |
| Returns | bindings | a promise |
| Path | a string literal | any expression |