JS: Module. Dynamic Import
(new in ECMAScript 2020)
What is Dynamic Import
Dynamic import is introduced in ECMAScript 2020. It let you import a module at run-time. You can import a module by condition inside a if-statement, and module name can also be a variable.
Import and export features of ECMAScript 2015 are static. That means, the code runs at compile time. You cannot import at run time, such as import a lib only when needed.
whats JavaScript Dynamic Import
whats JavaScript Dynamic Import
(ai answer)
JavaScript dynamic import (import()) is a function-like expression that loads an ES module asynchronously at runtime, instead of at parse/load time like a static import declaration.
It returns a Promise that resolves to a module namespace object (all of that module’s exports). import here is a keyword, not a normal function.
Syntax
import(moduleSpecifier); import(moduleSpecifier, options);
moduleSpecifiercan be a string or any expression that evaluates to a string (unlike static imports, which require a string literal).optionsis optional. The recognized key iswithfor import attributes (e.g. JSON modules).
const module = await import("./utils.js"); module.doStuff(); const data = await import("./data.json", { with: { type: "json" } });
Static vs dynamic import
Static import |
Dynamic import() |
|
|---|---|---|
| When it runs | Load time (before your module executes) | When the expression runs |
| Where it can appear | Top level of a module only | Anywhere (functions, if, event handlers) |
| Specifier | String literal only | Computed string OK |
| Return value | Bindings in scope | Promise → namespace object |
Needs type="module" |
Yes (in browsers) | No — can be used from classic scripts too |
Static imports are analyzed and linked before execution. Dynamic imports fetch, instantiate, and evaluate the module only when you call import().
Why use it
- Lazy / on-demand loading — don’t pay for a feature until the user needs it (click, route change, etc.).
- Code splitting — bundlers (webpack, Vite, Rollup) can put the imported file in a separate chunk.
- Conditional loading — load a module only if a feature flag, locale, or environment requires it.
- Dynamic paths — e.g.
import(\./i18n/${lang}.js`)`. - Non-module contexts — load ESM from a regular
<script>or other non-module environment.
Examples
Promise style
button.addEventListener("click", () => { import("./greeting.js") .then((mod) => { mod.sayHi(); }) .catch((err) => { console.error("Failed to load module", err); }); });
async/await
button.addEventListener("click", async () => { try { const { sayHi } = await import("./greeting.js"); sayHi(); } catch (err) { console.error(err); } });
Conditional
if (needsHeavyFeature) { const { run } = await import("./heavy-feature.js"); run(); }
The resolved object is a namespace: default export is module.default, named exports are properties. Bindings stay live (same as static imports). Modules are cached for the lifetime of the page/worker — a second import() of the same specifier does not re-fetch/re-evaluate.
Support
Widely available in browsers since ~2020 (Chrome 63+, Firefox 67+, Safari 11.1+) and in Node.js since 13.2. It is a baseline web feature.
Use static imports by default (simpler, better for tree-shaking and static analysis). Reach for import() when you actually need runtime or on-demand loading.
(ai answer)
JavaScript Dynamic Imports: A Complete Tutorial
Dynamic imports (also known as import() expressions) allow you to load JavaScript modules on demand at runtime, rather than statically at the top of your file.
Why Use Dynamic Imports?
- Code Splitting: Load only the code the user needs (e.g., a heavy chart library only when the user opens the analytics tab).
- Lazy Loading: Improve initial page load time.
- Conditional Loading: Import modules based on user actions, feature flags, or environment.
Static import (eager):
import HeavyLibrary from "./heavy-library.js"; // Loaded immediately
Dynamic import (lazy):
const button = document.getElementById("load-chart"); button.addEventListener("click", async () => { const { default: HeavyLibrary } = await import("./heavy-library.js"); // Now use it });
Basic Syntax
The import() function returns a Promise that resolves to the module namespace object.
import(moduleSpecifier) .then((module) => { // module.default, module.namedExport, etc. }) .catch((err) => console.error(err));
With async/await (recommended):
async function loadModule() { try { const module = await import("./my-module.js"); console.log(module); } catch (err) { console.error("Failed to load module:", err); } }
Module Specifier
The argument to import() can be:
-
A static string (most common):
await import("./utils.js"); await import("/absolute/path/module.js"); await import("https://cdn.example.com/lib.js"); -
A variable/dynamic string:
async function loadLanguage(lang) { const module = await import(`./languages/${lang}.js`); return module.translations; }
Working with Exports
Default Export
// math.js export default function add(a, b) { return a + b; }
// main.js const { default: add } = await import("./math.js"); // or const math = await import("./math.js"); const result = math.default(2, 3);
Named Exports
// utils.js export const PI = 3.14; export function square(x) { return x * x; }
const utils = await import("./utils.js"); console.log(utils.PI); console.log(utils.square(5));
You can also use Assignment pattern directly:
const { PI, square } = await import("./utils.js");
Real-World Examples
Feature Flags
async function initializeApp() { if (userHasPremium) { const { PremiumFeatures } = await import("./premium.js"); PremiumFeatures.init(); } }
3. Route-based Code Splitting
const routes = { "/dashboard": () => import("./pages/Dashboard.js"), "/settings": () => import("./pages/Settings.js"), }; async function navigate(path) { const module = await routes[path](); // render module.default or similar }
Error Handling
Always wrap dynamic imports in try/catch:
async function safeImport(path) { try { return await import(path); } catch (error) { if (error.name === "TypeError") { console.error("Module loading failed - network or syntax error"); } else { console.error("Unexpected error:", error); } // Fallback UI or retry logic } }
Advanced Topics
Importing JSON (modern browsers)
const data = await import("./config.json", { assert: { type: "json" } }); // or in newer syntax: const data = await import("./config.json", { with: { type: "json" } });
Dynamic Import in CommonJS (require)
Dynamic imports work in both ESM and CommonJS modules.
Caching
Modules loaded with import() are cached just like static imports. Subsequent calls to the same specifier return the cached module instantly.
const m1 = await import("./foo.js"); const m2 = await import("./foo.js"); // Returns cached module
To force a reload (rarely needed), use cache-busting query params:
await import(`./foo.js?v=${Date.now()}`);
Browser and Environment Support
- Modern browsers: Fully supported (Chrome 63+, Firefox 67+, Safari 11.1+).
- Node.js: Supported since v13.2.0 (with
--experimental-modulesearlier). - Bundlers: Excellent support in Vite, Webpack 4+, Parcel, Rollup, etc.
Polyfill for very old browsers (not recommended for new projects):
<script nomodule src="dynamic-import-polyfill.js"></script>
Best Practices
- Use async/await for readability.
- Keep the import call close to where the module is used.
- Avoid putting dynamic imports in the top level of modules unless necessary (can delay module evaluation).
- Preload critical chunks using
<link rel="modulepreload">for better performance. - Handle loading states (spinners, skeletons) in UI.
- Monitor bundle size — dynamic imports create new network requests.
Common Pitfalls
- Top-level await in modules can sometimes be combined with dynamic imports.
- Dynamic imports do not hoist (unlike static
import). - Relative paths behave differently depending on the environment.
- In some bundlers, dynamic variables may require special configuration (
webpackChunkNamecomments, etc.).
Comparison: Static vs Dynamic
| Feature | Static import |
Dynamic import() |
|---|---|---|
| Loading time | Build time / initial | Runtime (on demand) |
| Bundle splitting | Limited | Excellent |
| Tree shaking | Full | Full |
| Conditional loading | Not possible | Perfect |
| Top-level syntax | Yes | Function call |