skip to content

Modules and Bundling

How JavaScript source is split across files and linked back together — ES modules, CommonJS, and what the two do differently at load time. Children cover ESM semantics, dynamic import and code splitting, ESM/CJS interop, circular dependencies, and side effects with tree shaking.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

explore

questions

page 2 of 2

Why can a build tool usually not eliminate unused exports from a CommonJS module the way it can from an ES module?

level: middleimportance: should knowfreq 40%

basics

~20 s

CommonJS builds its exports at runtime: require is an ordinary function call and module.exports is a mutable object whose properties can be added conditionally and read with computed keys. The export set is not knowable before execution, so tools keep the whole module.

open as a page

A large JavaScript codebase intermittently sees an imported value arrive as undefined, or throws 'Cannot access before initialization', depending on which entry point loads first. How do you confirm an import cycle is the cause, and how do you break it?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Map the import graph and look for a loop — a tool such as madge run with --circular, or ESLint's import/no-cycle rule, lists them mechanically. Then break the loop by extracting the shared code into a third module, or by inverting the dependency, rather than by reordering imports.

open as a page

A CommonJS service takes several seconds to start serving traffic, and the time is spent in top-level code of required modules — parsing config, building lookup tables — before the server ever starts listening. Why does require() put that cost exactly there, and how would you restructure the modules?

level: seniorimportance: should knowfreq 36%

basics

~20 s

require() runs a module's body to completion synchronously before returning, so anything at a module's top level executes during the require call and blocks the single thread. Move expensive work into exported init or factory functions the application calls deliberately.

open as a page

In a Node service, `err instanceof ValidationError` returns false for errors thrown by the very library that exports `ValidationError`, even though `err.constructor.name` is "ValidationError" and only one version of the library is installed. Parts of the codebase load the library with `import`, others with `require`. What is happening, and how would you confirm it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Two builds of the same library are loaded, so two distinct ValidationError constructors exist. The error came from one copy and is being tested against the other copy's class, and instanceof compares prototype identity, not names. Confirm it by comparing the file paths each side actually resolved.

open as a page

You maintain a JavaScript package that ships both a CommonJS and an ESM build and keeps a module-level registry of plugins. What can you change so consumers can never end up with two independent registries, and what does each option cost them?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Guarantee a single implementation. Either ship one build and make the other entry a thin wrapper that re-exports it, move all stateful pieces into one internal file both builds load, or publish a single format. Each trades bundle quality or consumer convenience for guaranteed single-instance state.

open as a page

A single-page app lazy-loads a route with `await import('./routes/settings.js')`. In production, some users hit a blank screen because that load fails. How does `import()` surface the failure, and how would you handle it in the code?

level: seniorimportance: should knowfreq 45%

basics

~20 s

import() reports failure by rejecting its promise — nothing is thrown at the call site. Wrap the await in try/catch, distinguish a failed fetch from an error thrown while the module evaluates, show a real error state, and recover from a stale-deploy 404 with a full page reload rather than a bare retry.

open as a page

When designing an ES module's public surface, what are the practical tradeoffs between exposing values as named exports versus a single default export?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Named exports keep the public name under the author's control, so identifiers stay consistent, greppable and safely renameable by tooling. A default export makes a one-artifact file read cleanly but hands the naming decision to every importer, which invites drift.

open as a page

A module has `export let ready = false;` and sets `ready = true` during startup. Consumers using `import { ready }` see it become true, but after the same code is compiled to CommonJS and consumers write `const { ready } = require('./m')`, they see false forever. What explains the difference, and how would you export this value so it works under both?

level: seniorimportance: should knowfreq 38%

basics

~20 s

An ES module import is a live binding to the exporter's variable, while destructuring a CommonJS require copies the property's value at that instant. Export a function or a state object so consumers read the value at access time instead of capturing it.

open as a page

A JavaScript file that worked when loaded as a classic script now throws ReferenceError once the same code is loaded as an ES module — once on a helper function another file defines, and once on a line that assigns to a name without declaring it. What changed, and how do you fix each?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Two module rules bit at once. Module scope means the other file's helper is no longer visible as a bare name, so it must be exported and imported; implicit strict mode turns the undeclared assignment from a silent global into a ReferenceError, so declare it with const or let.

open as a page

A module counts how many times its top-level code runs and five different modules import it. How many times does the body actually run, what identifies a module instance, and how can a codebase end up with two separate instances of what looks like the same module?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Once. A module's body is evaluated exactly one time per instance, and instances are keyed by the resolved location of the module, so all five importers share one instance. Two instances appear when the same source resolves to two different locations — a duplicated package copy, a differing specifier, or a separate realm.

open as a page

In Node's native ESM, `import { parse } from 'legacy-cjs-pkg'` fails with `SyntaxError: The requested module 'legacy-cjs-pkg' does not provide an export named 'parse'`, yet `require('legacy-cjs-pkg').parse` is a function at runtime. Explain what causes that mismatch and how you would fix the import site.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Named exports from a CommonJS module are discovered by statically scanning its source before it runs, so properties attached dynamically are invisible to the linker even though they exist at runtime. Default-import the module and destructure the property afterwards.

open as a page

Two ES modules import each other, and one of them top-level awaits a value the other only produces after its own body finishes. What happens when the application loads, and how would you track it down?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Nothing happens: the graph's evaluation promise never settles, so the modules stay half-evaluated and the entry point never runs. There is no error, no stack trace and no timeout — the load simply hangs, which is why it is diagnosed by tracing where logging stops.

open as a page

What does `"sideEffects": false` in a package's package.json actually claim, and what goes wrong when a package makes that claim untruthfully?

level: seniorimportance: should knowfreq 42%

basics

~20 s

It claims that no file in the package does anything observable merely by being evaluated, so a bundler may skip importing any module whose exports go unused. It is an unverified promise; a false one silently deletes polyfills, styles and registrations from production builds.

open as a page

When deciding where to put `import()` split points in an application, what makes a call site a good boundary, and what goes wrong when a team splits too aggressively?

level: principalimportance: should knowfreq 33%

basics

~20 s

Good boundaries sit where a module is both large and genuinely optional, gated behind a user intention the app can anticipate. Over-splitting fragments code into many small units, creates sequential request waterfalls when one lazy module lazily imports another, and shifts latency onto interactions.

open as a page

When is top-level await in an ES module the right way to do async initialization, and when would you instead export an initializer function or a promise?

level: principalimportance: should knowfreq 26%

basics

~20 s

Use top-level await when a module's export is meaningless until an async step completes and every consumer needs it — it makes correct ordering structural. Prefer an exported initializer or promise when the module is widely imported, when the work can fail or needs retry control, or when consumers must load synchronously.

open as a page

In an ES module, `import * as ns from './m.js'` gives you `ns`. What kind of object is `ns` — can you add, delete or overwrite properties on it, and what is its prototype?

level: middleimportance: nice to knowfreq 31%

basics

~20 s

ns is a module namespace object: non-extensible, with one non-configurable property per export whose value is a live view of the exporter's variable. Adding, deleting or overwriting properties is rejected, and its prototype is null.

open as a page

In a library's published output you find `const registry = /*#__PURE__*/ createRegistry();`. What does that comment assert, what reads it, and what is the risk of adding one?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

It asserts that the immediately following call has no observable effects, so if its result is unused the whole call may be deleted. Minifiers and bundlers read it. Nothing verifies it, so a wrong annotation silently removes real behaviour from production builds.

open as a page

As the maintainer of a widely depended-on JavaScript library, how would you decide whether to ship an ESM build only, a CommonJS build only, or both, given the dual package hazard?

level: principalimportance: nice to knowfreq 16%

basics

~20 s

Decide by what your public API depends on. If correctness rests on shared state or class identity, ship a single implementation so two copies are impossible. If the library is stateless, dual builds are a bundle-size convenience with no correctness risk, and the choice becomes purely a compatibility question.

open as a page

Parts of a large codebase rely on module top-level side effects — plugins that self-register on import, a logger configured at import time — so correctness depends on which modules are imported and in what order. As the engineer setting the standard, how do you evaluate that pattern and what would you put in its place?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Treat evaluation-order dependence as hidden coupling: the ordering contract lives in import statements no reviewer reads as ordering. Prefer module bodies that only declare things, plus one composition root that constructs and wires everything in an order you can read, test, and change.

open as a page

showing 31–49 of 49