You will be able to load JavaScript modules only when needed using dynamic import(), reducing initial bundle size and enabling conditional or lazy loading.
What it is
Dynamic imports let JavaScript load a module at runtime instead of at parse time. The syntax looks like a function call, but it is a special module-loading expression: import("./module.js"). It returns a promise that resolves to the module’s namespace object, which contains the module’s exports.
The mental model is: “Do not load this code now; load it only if and when this line runs.” Related terms include static import, code splitting, lazy loading, module specifier, and module namespace object.
Why it matters
- It can reduce the amount of JavaScript downloaded during initial page load.
- It lets you load optional features only when the user needs them.
- It supports conditional loading, such as loading different modules based on user role, locale, or device.
- It helps isolate rarely used code, such as admin panels, charts, editors, or analytics.
- It can improve perceived performance by deferring work until after the main interface is ready.
Syntax or steps
- Identify a module that is not needed immediately.
- Call
import("./path/to/module.js")where the module is needed. - Use
awaitinside anasyncfunction, or at the top level of a module where supported. - Read exports from the resolved module object.
- Handle loading errors with
tryandcatch.
Example
// analytics.js
export async function track(event) {
console.log("tracked", event);
}
// app.js
async function sendEvent(event) {
try {
const { track } = await import("./analytics.js");
await track(event);
} catch (error) {
console.error("Analytics failed to load", error);
}
}
sendEvent("page-view");
To run this in a browser, load app.js from a module script, for example <script type="module" src="./app.js"></script>.
export async function trackmakestrackavailable to other modules.await import("./analytics.js")loads the module only whensendEventruns.const { track } = ...extracts the named export from the module namespace object.tryandcatchhandle failures such as a missing file or network error.
Common mistakes
- Forgetting
await. Without it, you receive a promise, not the module object. - Assuming dynamic imports behave exactly like static imports. Static imports are resolved before code runs; dynamic imports happen during execution.
- Using dynamic imports for every module. If code is always needed immediately, static imports are usually clearer and easier to optimize.
- Ignoring errors. A dynamic import can fail because the file is missing, the network is unavailable, or the module throws during evaluation.
When to use it
| Need | Use | Why |
|---|---|---|
| Module is always needed at startup | Static import | Clearer, simpler, and easier for tools to analyze. |
| Module is large and rarely used | Dynamic import | Loads only when needed, reducing initial work. |
| Module depends on a runtime condition | Dynamic import | Avoids loading code that may never be used. |
Practice
Guided exercise: Create logger.js with export function log(message) { console.log(message); }. In app.js, dynamically import it and call log("hello").
Expected output: hello.
Challenge: Load either desktop.js or mobile.js based on a variable named isMobile. Hint: use await import(isMobile ? "./mobile.js" : "./desktop.js").
Quick check
Question: What does import("./module.js") return?
Answer: It returns a promise that resolves to the module namespace object containing the module’s exports.
Summary
Dynamic imports give JavaScript programs control over when modules are loaded. Use them for lazy loading, optional features, and runtime-dependent code. Keep static imports for modules that are always needed immediately.