Skip to content
Modules in Depth: Dynamic Imports
step 1/7

Reading — step 1 of 7

Learn

~3 min readCollections and Property Descriptors

You met import/export in JavaScript Fundamentals. Now we'll go deeper: dynamic imports, top-level await, side-effect imports, and the gotchas of mixing ESM with CommonJS.

Static vs dynamic imports

Static — at the top of the file, hoisted, dependencies known at parse time:

javascript

Dynamic — anywhere in code, runs at the call site, returns a Promise:

javascript

Why dynamic?

  • Code splitting — bundlers split the imported module into a separate chunk
  • Conditional loadingif (user.isAdmin) await import('./adminPanel.js')
  • Lazy/deferred loading — improve startup time by deferring rare-use code
  • Plugin systems — load modules whose paths come from config

Top-level await

In ESM (browsers, Node 14+), you can use await at the top of a module:

javascript

Importing modules wait for it:

javascript

Used for: loading WASM, fetching configuration, opening DB connections during module init. Use sparingly — slows down the entire dependency graph.

Side-effect imports

Import for side effects only, no bindings:

javascript

Useful for global setup, polyfills, registering plugins.

ESM vs CommonJS — the divide

CommonJS (Node's original module system):

javascript

ESM (modern):

javascript

Key differences:

  • ESM is async; CJS is sync
  • ESM imports are HOISTED; CJS require can be conditional
  • ESM imports are LIVE BINDINGS (read-only views of the source); CJS exports are snapshots
  • Node treats .js files as CJS by default — set "type": "module" in package.json to switch to ESM
  • Use .cjs to force CommonJS, .mjs to force ESM regardless of package.json

Interoperability in Node:

  • ESM can import CJS modules: import pkg from 'cjs-only-pkg' (default-imports module.exports)
  • CJS can require() ESM only via await import(...) (since CJS can't await at top level, it's awkward)

import.meta

A special object available in modules with metadata:

javascript

Replaces CommonJS's __filename and __dirname (which don't exist in ESM).

Tree shaking and bundlers

With static imports, bundlers (Rollup, esbuild, Webpack) can do tree shaking — removing unused exports from the final bundle:

javascript

Dynamic imports prevent tree shaking (the bundler doesn't know what you'll grab) but enable code splitting.

Common mistakes

  • Forgetting the file extension in Node ESMimport './util' fails; need import './util.js'.
  • Missing "type": "module" in package.json — Node treats .js as CommonJS by default.
  • Using __filename / __dirname in ESM — undefined; use import.meta.url.
  • Top-level await blocking module loading — slows down anything depending on the module.
  • Side effects in the top-level of a module — runs once on first import, but order across siblings can surprise. Prefer explicit init functions.
  • Mixing ESM and CommonJS in the same file — not possible. Pick one per file.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…