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 imports are resolved when the module graph is built. Their path cannot be assembled at runtime:

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

Use the import() expression when loading depends on a runtime decision:

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

import() returns a promise for the module namespace object. A default export is available as module.default; named exports are properties with their exported names.

A practical use is loading an expensive feature only after the user asks for it:

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 a dynamic import into a separate chunk. This reduces initial JavaScript only if the feature is not immediately needed; it also introduces a network request and a failure path later in the interaction.

Avoid arbitrary user-controlled paths. Bundlers must know which files may exist, and unrestricted server-side imports can load code you never intended to expose. Map choices to known import functions:

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

Try it: dynamically load one module on a button click, inspect the Network panel, then simulate offline mode and verify the error state.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →