Glossary / Module resolution

Module resolution

Module resolution is the host or tool's process for mapping an import specifier such as ./utils.js, node:fs, or react to a concrete module. It depends on the module system, importer location, package metadata, file extensions, export maps, conditions, and runtime or bundler configuration.

Specifier categories

Relative specifiers (./x.js, ../x.js) resolve from the importing module. Absolute paths or URLs have host-specific meaning. Bare specifiers (react, @scope/pkg/subpath) identify packages or built-ins and use package resolution. Import maps can remap specifiers in browsers; Node package "imports" provides package-private # aliases; bundlers commonly support configured aliases.

Node ESM generally requires explicit file extensions for relative imports and does not perform CommonJS-style directory-index or extension searching. Package "exports" defines the public subpaths and can reject deep imports not listed. Conditional exports choose targets based on conditions such as import, require, node, or default; order and nesting matter. CommonJS require() uses different legacy resolution rules, including package entry fields and extension lookup.

Why environments disagree

TypeScript's moduleResolution, a bundler's resolver, a test runner, browser import maps, and Node's loader can each interpret the same specifier differently. A project may compile successfully but fail at runtime if emitted paths, extensions, aliases, or package conditions do not match deployment. Duplicate package copies can also resolve to separate module identities, breaking singleton assumptions or instanceof checks.

Engineering guidance

Choose the runtime contract first, then align compiler, bundler, tests, and editor resolution. Prefer explicit extensions and public package exports for portable Node ESM. Avoid relying on undeclared hoisted dependencies. When debugging, inspect the actual resolved path and conditions in the failing environment; test the built artifact, not only source through a bundler.

Interview questions

  1. What is a bare specifier? A non-relative package or built-in name such as react or node:fs.
  2. Why can TypeScript compile while Node fails? TypeScript may resolve source aliases or extensions differently from the emitted runtime loader.
  3. What does exports do? It defines package-visible entry points and can encapsulate internal paths.
  4. Why can duplicate package versions break singleton behavior? Each resolved copy can have independent module state and constructor identity.
  5. How diagnose resolution? Inspect loader/bundler resolution in the target environment and run the built output there.

References