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.
8 minute lesson
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