Suggest an editImprove this articleRefine the answer for “Dynamic import”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A dynamic import is a call to the `import()` function, which returns a Promise and loads a module at runtime rather than at build time.** A static `import` pulls the module in immediately at startup, so all of that code ends up in the initial bundle. A dynamic `import()` fetches the file only when it is actually needed: on a user click, under a condition, behind a feature flag. The Promise resolves with the module object holding all of its exports, including `default`. Bundlers (Webpack, Vite, Rollup, esbuild) see the `import()` expression and perform code splitting, moving the module into a separate chunk that is fetched over the network. This is the basic mechanism behind lazy loading, localisation, plugins and deferring heavy libraries. ```javascript button.addEventListener('click', async () => { const { openModal } = await import('./modal.js'); openModal(); }); ``` **Key point:** `import()` returns a Promise with the module object and loads code on demand, which gives you code splitting and a faster application start.Shown above the full answer for quick recall.Answer (EN)Image**A dynamic import is a call to the `import()` function, which returns a Promise and lets you load modules at runtime rather than at build time.** Thanks to that, only what is needed immediately goes into the initial bundle, and the rest of the code is fetched on demand. ## Theory ### TL;DR - `import()` is a function-like expression that returns a **Promise** holding the module object. - A static `import` is resolved at build time and always lands in the bundle; a dynamic one runs at runtime. - The module path can be a variable or a template string, which a static `import` cannot do. - Bundlers use `import()` as the cut point for **code splitting**. - Typical scenarios: lazy loading, localisation, plugins, heavy dependencies, A/B tests. - Loading errors are caught with `try/catch` or `.catch()`, because this is an ordinary Promise. ### Quick example ```javascript // Dynamic import: the module is fetched only when this line runs const module = await import('./utils.js'); module.sayHello(); // Static import for comparison: resolved at build time, always in the bundle // import { sayHello } from './utils.js'; ``` ### Syntax and what `import()` returns `import()` works both with `.then()` and with `await`: ```javascript import(modulePath) .then(module => { // the module is loaded module.default(); }) .catch(err => console.error(err)); ``` ```javascript const module = await import('./math.js'); const { sum } = module; console.log(sum(2, 3)); ``` The Promise resolves with the **module object**, which carries every named export plus the `default` property: ```javascript // math.js export function add(a, b) { return a + b; } export const PI = 3.14; ``` ```javascript const math = await import('./math.js'); console.log(math.add(2, 3)); // 5 console.log(math.PI); // 3.14 ``` Since this is a regular Promise, errors are handled the usual way: ```javascript try { const module = await import('./missing.js'); } catch (err) { console.error('Module loading failed:', err); } ``` ### Conditional loading and dynamic paths You can pick the module depending on the situation, for example for localisation: ```javascript if (navigator.language.startsWith('fr')) { const messages = await import('./lang/fr.js'); console.log(messages.default.hello); } else { const messages = await import('./lang/en.js'); console.log(messages.default.hello); } ``` The path may be a variable or a template string, which is impossible with a static `import`: ```javascript const theme = 'dark'; const module = await import(`./themes/${theme}.js`); module.applyTheme(); ``` One caveat: the bundler (Webpack, for instance) still has to **predict the path pattern**, otherwise it will not include the needed files in the build. So part of the path must stay static, like `./themes/` above. The classic case is loading on a user action: ```javascript button.addEventListener('click', async () => { const { openModal } = await import('./modal.js'); openModal(); }); ``` The `modal.js` module is loaded **only on the first click**, not at site startup, which saves traffic and speeds up the launch. ### When to use a dynamic import | Scenario | Why it helps | | --- | --- | | **Lazy loading components** | Load a component only when the user navigates to that page | | **Translations and localisation** | Import just the language you need | | **Heavy dependencies** | Defer large libraries (`chart.js`, `moment`, `three.js`) | | **Plugins and modular architecture** | Wire in functionality on request | | **SSR and code splitting** | In Next.js, Vite and Webpack the bundles are split automatically | | **Feature flags and A/B tests** | Load different modules depending on a flag | ### How it works under the hood Bundlers (Webpack, Rollup, Vite, esbuild) do three things: 1. Analyse the `import('./path')` expressions in your code. 2. Perform **code splitting**, breaking the project into separate chunks. 3. Fetch the needed chunk over the network when `import()` is called. Roughly what the build output looks like: ```javascript main.js // the main bundle vendors~chart.js // the shared library chunk chart.js // fetched on click ``` In React and Next.js this is wrapped in ready-made helpers: ```javascript import dynamic from 'next/dynamic'; const Chart = dynamic(() => import('../components/Chart'), { ssr: false, loading: () => <p>Loading...</p>, }); export default function Page() { return ( <div> <h1>Data</h1> <Chart /> </div> ); } ``` The `<Chart>` component is fetched only in the browser and only once the user opens the page. ### Differences from `require()` | Criterion | `import()` | `require()` | | --- | --- | --- | | Type | Asynchronous (Promise) | Synchronous | | Works in | ESM (browser, Node.js) | CommonJS (Node.js) | | Dynamic path | Yes | Limited | | Supports tree-shaking | Yes | No | | Code splitting in bundlers | Yes | No | A summary of `import()` itself: | Question | Answer | | --- | --- | | What it does | Loads modules asynchronously, on demand | | What it returns | A Promise with the module object | | Where it works | ES Modules (Node.js, browser) | | What it is for | Lazy loading, code splitting, A/B tests, dynamic localisation | | Syntax | `const module = await import('./file.js')` | ### Common mistakes - **Forgetting it is a Promise.** `const m = import('./a.js'); m.foo()` will not work; you need `await` or `.then()`. - **A fully dynamic path.** The bundler cannot analyse `import(userInput)`, so the chunk never makes it into the build. Keep a static directory prefix. - **No error handling.** The network can fail, and a rejected Promise without a `catch` becomes an unhandled rejection. - **Dynamic imports everywhere.** If a module is needed at startup anyway, `import()` only adds an extra network round trip. Small utilities are better imported statically. - **Calling it in a loop without thinking about cost.** Repeating `import()` for the same path hits the module cache, but the extra `await` in a hot loop still slows the code down. - **Expecting `import()` to hand back a named export directly.** It returns the whole module object, so you need destructuring: `const { sum } = await import('./math.js')`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.