microsoft/TypeScript
Module resolution
How TypeScript turns an import specifier or require call into a resolved file path. Handles five module-resolution strategies (classic, node10/node, node16, nodenext, bundler) plus the related dance of paths, baseUrl, package.json exports/imports, and conditional exports.
Source
| File | Lines | Role |
|---|---|---|
src/compiler/moduleNameResolver.ts |
3,428 | The resolution algorithms |
src/compiler/resolutionCache.ts |
2,000+ | Memoisation across Program rebuilds |
src/compiler/moduleSpecifiers.ts |
1,800+ | The inverse — generate import specifiers (used by auto-import / refactor) |
src/server/packageJsonCache.ts |
small | tsserver-side package.json caching |
src/server/moduleSpecifierCache.ts |
small | Cache for auto-import specifier generation |
Purpose
When the parser encounters import { foo } from "bar", the program needs to find bar. That means:
- Apply
--pathsand--baseUrlrewrites. - If
baris relative, look forbar.ts,bar.tsx,bar.d.ts, then maybebar/index.ts, thenbar.js,bar.jsx(whenallowJs). - If
baris bare, walk upnode_modules/, consultingpackage.json'stypes,typings,exports, andimports. - Apply conditions (
import/require/types/browser/development/etc.) per the resolution strategy. - Honour
.cts/.mts/.ts/.tsxand the inferred CommonJS-vs-ESM mode. - If everything fails, fall through to the JavaScript file (when
allowJs) or report a diagnostic.
Resolution modes
| Mode | When to use | Notes |
|---|---|---|
classic |
TypeScript 1.5–era resolution. Walks parent directories for relative imports. | Legacy. Avoid in new projects. |
node10 (alias node) |
Original Node.js CommonJS behaviour. | Default for --module commonjs. |
node16 |
Node 16+ ESM/CJS dual mode with package.json#exports. |
For Node-targeted code. |
nodenext |
Same as node16 but tracks newer Node versions. |
Recommended for new Node projects. |
bundler |
Like node10 but allows extension-less imports and assumes a bundler resolves further. |
For Webpack/Vite/esbuild projects. |
The implementation files generally have one entry function per strategy: nodeModuleNameResolver, bundlerModuleNameResolver, classicNameResolver. Each returns a ResolvedModuleWithFailedLookupLocations that tells the caller (a) the resolved file, and (b) the locations where it looked — used for --traceResolution output and for invalidating watchers when something appears at a previously-looked-up location.
Key abstractions
| Symbol | Role |
|---|---|
resolveModuleName(moduleName, containingFile, options, host, cache?, redirectedReference?) |
Public entry — picks the strategy and returns ResolvedModuleWithFailedLookupLocations |
nodeModuleNameResolver, bundlerModuleNameResolver, classicNameResolver |
Per-strategy implementations |
resolveTypeReferenceDirective |
Resolves /// <reference types=… /> and types/typeRoots |
getPackageScopeForPath, readPackageJsonInfo |
package.json lookups |
ModuleResolutionCache, TypeReferenceDirectiveResolutionCache |
Per-program caches |
createResolutionCache (in resolutionCache.ts) |
Coordinates caches across watch rebuilds and invalidates them when files appear/disappear |
How it works
graph TD
Imp["import 'pkg'"] --> Resolve["resolveModuleName"]
Resolve --> Strat{"strategy?"}
Strat -->|node16/nodenext| Node["node-style + exports/conditions"]
Strat -->|node10| Node10["legacy node resolution"]
Strat -->|classic| Classic["walk parents for files"]
Strat -->|bundler| Bundler["node-style without strict ext check"]
Node --> Cache["ModuleResolutionCache"]
Node10 --> Cache
Classic --> Cache
Bundler --> Cache
Cache --> Result["ResolvedModuleWithFailedLookupLocations"]
Result --> ProgramMR["program.ts processes imports"]
Result --> Watch["resolutionCache schedules invalidation watchers"]processImportedModules in program.ts loops over every import in every source file and calls resolveModuleName. The failedLookupLocations returned with each result drive watch-mode invalidation: if any of those paths appears later (e.g., a missing node_modules/foo is finally installed), the program rebuilds.
package.json exports
In node16/nodenext mode, exports substantially changes resolution. A package's exports field is a tree keyed on subpath patterns, where each leaf is a tree keyed on conditions (import, require, types, default, custom). The resolver walks both trees per the Node spec. The bundler mode applies a simplified version that prefers import first.
paths and baseUrl
compilerOptions.paths lets users alias module names ("@app/*": ["src/*"]). The resolver applies these aliases first, then attempts the standard resolution against each candidate. baseUrl is the implicit root for these aliases.
Inverse: generating specifiers
When the language service needs to suggest an auto-import, it must generate the specifier a user should type, not just the resolved file. moduleSpecifiers.ts is the inverse of resolution. It tries paths, then baseUrl, then node_modules package names (preserving package.json#exports shapes), then relative paths, choosing the shortest stable form.
Diagnostics and tracing
--traceResolution writes a verbose log of every resolution attempt: which files were probed, which package.json fields were consulted, which conditions matched. This is the primary debugging tool when "Cannot find module" appears unexpectedly.
Integration points
- Called from
program.tsviaprocessImportedModuleswhen building the file closure. - Called from
tsserver'seditorServices.tswhen animportchanges. - Called from the language service's auto-import feature (
src/services/codefixes/importFixes.ts, among others). - Cached results are kept in
resolutionCache.tsand shared across program rebuilds when possible.
Entry points for modification
- New strategy: add a resolver function and wire it into
resolveModuleName's strategy switch. Add an entry toModuleResolutionKindinsrc/compiler/types.ts. Update the option declaration incommandLineParser.ts. - New conditional-exports condition: extend
getConditionsand the per-strategy condition lists inmoduleNameResolver.ts. - Caching changes:
resolutionCache.tsis the only place to modify.
See features/modules for the user-facing module-system story.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.