Organizing programs

How to dynamically import JavaScript modules

Learn how to dynamically import a JavaScript module when the path is built at runtime, using the await import() syntax instead of a static import statement.

Static import statements are fixed when the module graph loads. You cannot build the path from a variable:

import test from `${folder}/test.js`

Use the import() expression when the decision happens at runtime:

const module = await import('./charts.js')
module.renderChart(data)

import() returns a promise for the module namespace. The default export is module.default. Named exports appear as properties with their export names.

A common pattern is loading a heavy feature only after the user clicks:

button.addEventListener('click', async () => {
  button.disabled = true

  try {
    const { openEditor } = await import('./editor.js')
    openEditor()
  } catch (error) {
    showMessage('The editor could not be loaded')
  } finally {
    button.disabled = false
  }
})

Bundlers can split dynamic imports into separate chunks. That shrinks the first load only if you truly defer the feature. You also add a network round trip and a failure path later in the flow.

Never pass arbitrary user input straight into import(). Map choices to known modules:

const loaders = {
  bar: () => import('./charts/bar.js'),
  line: () => import('./charts/line.js')
}

Wire a button to one loader, open the Network panel, and confirm the chunk loads on click. Turn offline mode on and check that your error message appears.

The returned module namespace is a plain object. Destructure named exports from it the same way you would from a static import.

Bundlers still need to know which files might load. That is why the loaders map beats building a path string from user input.

Static imports run during module initialization. Dynamic imports run when your code reaches the import() call, which makes them a good fit for optional UI the user might never open.

Top-level await in a module can call import() for bootstrapping code that must finish before the rest of the module exports run.

If dynamic import fails, the promise rejects with the same module load error you would see from a static import typo.

Lesson completed