An application often ships code that most visitors will never run: the rich text editor, the PDF generator, the admin view. All of it weighs on the first paint, for nothing.
The import() form lets you push that loading back to the exact moment the code becomes necessary.
Definition
A dynamic import is a function call, import(path), that loads a module during execution and returns a Promise. That promise fulfills with an object holding every export of the module, the default export included under the default key.
// cart.js
export const VAT = 0.2;
export default function totalWithTax(net) {
return net * (1 + VAT);
}
// app.js
const cart = await import('./cart.js');
console.log(Object.keys(cart)); // [ 'VAT', 'default' ]
console.log(cart.default(100)); // 120Destructuring makes the call easier to read: const { default: totalWithTax, VAT } = await import('./cart.js').
What lazy loading changes
A Bundler treats every import() as a boundary: the module and its dependencies go into a separate file, downloaded only when that line runs. This is code splitting, and it is often the most profitable performance win in a project.
button.addEventListener('click', async () => {
const { openEditor } = await import('./editor.js');
openEditor();
});The same mechanism loads a translation file based on the chosen language, or a charting library only on the page that draws charts.
Unlike a regular import, import() can be written anywhere: inside a condition, inside a function, inside an event handler. That is precisely what the static form forbids.
Three traps worth knowing
- A fully variable path breaks analysis. Tools need to guess its shape: prefer
import(`./locales/${code}.js`), whose fixed part stays readable, over a path built end to end. - The promise can be rejected. A missing file or a dropped connection causes a rejection, to be handled with try and catch or the interface simply freezes.
- The module stays cached. Ten calls on the same path download the file once, so there is no point memoizing the result yourself.
Frequently asked questions
Does import() always need await in front of it?
No, that is merely the most readable form. import('./x.js').then(...) does exactly the same job, and suits code that does not sit inside an async function. Inside an ES module, await is also accepted at the top level of the file.
Can it be used from a CommonJS file?
Yes, and it is in fact the official bridge between the two worlds: import() works inside a CommonJS file and can load a ES module, where require() fails. The trade-off is that the result arrives asynchronously.
When does splitting become counterproductive?
When the chunks get too small or too numerous: every separate file costs a request, and splitting a two kilobyte module earns nothing. Keep the technique for blocks that are genuinely heavy or genuinely rare, and measure before generalizing.