Open-Source Wikis

/

TypeScript

/

Features

/

Modules

microsoft/TypeScript

Modules

Imports, exports, dynamic import(), top-level await, declaration vs. side-effect imports, package-export resolution, ESM/CJS interop. The compiler's module story is one of the most-edited areas of the codebase — every Node release that touches modules ripples through here.

Where modules touch the codebase

Subsystem File Role
Parser src/compiler/parser.ts import/export syntax, import() expressions, import attributes
Binder src/compiler/binder.ts externalModuleIndicator, CommonJS detection, module.exports patterns
Module resolution src/compiler/moduleNameResolver.ts + resolutionCache.ts import "x" → file path
Checker src/compiler/checker.ts Module symbols, export *, type-only imports, dual-package hazards
Transformers src/compiler/transformers/module/ CommonJS, SystemJS, ECMAScript module lowering
Module specifier generation src/compiler/moduleSpecifiers.ts Inverse of resolution; used by auto-import
Emitter src/compiler/emitter.ts Print resolved import shapes

--module modes

Mode Output shape
none Top-level statements only; imports are errors
commonjs require/module.exports
amd define([...], function (...) {})
umd UMD wrapper supporting both
system SystemJS' System.register API
es2015 / es2020 / es2022 / esnext Native ECMAScript modules with version-appropriate features
node16 / node18 / node20 / nodenext Per-file CJS or ESM based on the file's impliedNodeFormat
preserve Output JSX/import as-is for downstream tooling

impliedNodeFormat is computed per file from .cts / .mts extensions and the nearest package.json#type. Two files in the same project can be CJS and ESM respectively under node16/nodenext.

Resolution

See systems/module-resolution. The high-level summary:

  1. Apply paths aliases.
  2. For relative imports, walk extensions. For bare names, walk node_modules/.
  3. Consult package.json exports/imports/types/typings for the right entry.
  4. Apply conditional-export conditions (import/require/types/default/etc.) based on the resolution mode and the importing file's format.

Type-only imports / exports

import type { X } from "./mod" and export type { Y } are stripped by the transformer — they have no runtime effect. The parser tracks isTypeOnly flags on import/export specifiers; the emitter elides them. --isolatedModules and --verbatimModuleSyntax are the strictness knobs.

CommonJS interop

require() calls in .ts source are not the same thing as import statements. When --esModuleInterop is on, the emitter:

  • Inserts __importStar/__importDefault helpers around CJS imports so default-imports see the module's default export (per spec) rather than the whole CJS namespace.
  • Emits an Object.defineProperty(exports, "__esModule", { value: true }) marker.

For node16/nodenext, additional checks live in the checker:

  • A .mts (ESM) file importing a .cjs (CJS) module gets a default import that's the CJS namespace.
  • A .cjs file importing a .mts module is an error unless the import is a dynamic import().
  • Re-exports across formats are validated to avoid dual-package hazards.

import attributes / import assertions

import x from "./y" with { type: "json" } is an ES2025 feature TypeScript supports. Parsed into the attributes field on ImportDeclaration. Currently has limited semantic effect inside TypeScript itself — type-aware JSON imports come from resolveJsonModule.

Module transformers

Each output mode has its own transformer in src/compiler/transformers/module/. They share a base that handles imports, exports, hoisting, and the __exportStar helper. The most subtle is transformSystemModule (used for SystemJS): it rewrites the entire module body into a System.register call with explicit setters and execute phases so cyclic imports work.

transformImpliedNodeFormatDependentModule is the dispatcher for node16/nodenext — it inspects each source file's impliedNodeFormat and delegates to either the CommonJS or ECMAScript module transformer per file.

Auto-import

The language service auto-imports symbols when a completion is selected. The path it inserts comes from src/compiler/moduleSpecifiers.ts, which inverts the resolution rules:

  • Honours paths so users see their alias rather than a deep relative path.
  • Prefers node_modules package names when the file is published.
  • Respects package.json#exports so the suggestion is a publicly-importable entry, not an internal module.
  • Falls back to relative paths.

Tests

Module tests are spread over many tests/cases/conformance/ subdirectories:

  • externalModules/, internalModules/, moduleResolution/, dynamicImport/, importDefer/, importAssertion/, importAttributes/, node/ (Node-style).
  • tests/cases/conformance/salsa/ covers JS-with-types module patterns.

Entry points for modification

See systems/module-resolution and systems/transformers for the underpinnings.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Modules – TypeScript wiki | Factory