Skip to main content

Module Resolution Overview

Module resolution is the process of determining which file an import statement refers to. TypeScript’s module resolution logic is implemented in src/compiler/moduleNameResolver.ts (181KB).
// TypeScript needs to find the file for this import
import { helper } from "./utils";
Module resolution is separate from type checking and happens during the program creation phase.

Resolution Strategies

TypeScript supports multiple module resolution strategies:

ModuleResolutionKind

// From src/compiler/moduleNameResolver.ts
enum ModuleResolutionKind {
    Classic = 1,
    NodeJs = 2,
    Node10 = 2,      // Alias for NodeJs
    Node16 = 3,
    NodeNext = 99,
    Bundler = 100,
}

Classic

Legacy resolution strategy. Rarely used in modern projects.

Node10 (NodeJs)

Mimics Node.js CommonJS resolution. Most common for older projects.

Node16

Supports both ESM and CommonJS with package.json “exports”.

NodeNext

Latest Node.js resolution. Tracks Node.js updates.

Bundler

Optimized for bundlers like webpack, esbuild. Relaxed rules.

Node Resolution Algorithm

Node10 Resolution (Classic Node.js)

For relative imports like import { x } from "./module":
1

Try Extensions

Look for the file with TypeScript extensions:
./module.ts
./module.tsx
./module.d.ts
2

Try Directory Index

If not found, check for directory with index file:
./module/index.ts
./module/index.tsx
./module/index.d.ts
3

Try package.json

Check for package.json with “types” or “typings” field:
// ./module/package.json
{
  "types": "./dist/index.d.ts"
}
For non-relative imports like import { x } from "library":
1

Check node_modules

Look in the current directory’s node_modules:
./node_modules/library/...
2

Walk Up Directory Tree

If not found, check parent directories:
../node_modules/library/...
../../node_modules/library/...
3

Resolve Package

Once found, apply relative resolution rules to the package.

Node16/NodeNext Resolution

These modes support package.json exports field:
// node_modules/library/package.json
{
  "name": "library",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js"
    },
    "./utils": {
      "types": "./dist/utils.d.ts",
      "import": "./dist/esm/utils.js"
    }
  }
}
Node16/NodeNext enforce that ESM imports must include file extensions:
// Required in Node16/NodeNext with "type": "module"
import { x } from "./module.js"; // ✓
import { x } from "./module";     // ✗ Error

Resolution Features

TypeScript extends Node’s resolution with additional features:

Path Mapping

Configure custom module paths in tsconfig.json:
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@utils/*": ["utils/*"],
      "@components/*": ["components/*"],
      "@app/*": ["app/*"]
    }
  }
}
// Instead of relative imports:
import { Button } from "../../../components/Button";

// Use mapped paths:
import { Button } from "@components/Button";

Base URL

Set a base directory for non-relative imports:
{
  "compilerOptions": {
    "baseUrl": "./src"
  }
}
// With baseUrl set to "./src"
import { config } from "config"; // Resolves to src/config

Root Dirs

Treat multiple directories as a single virtual directory:
{
  "compilerOptions": {
    "rootDirs": ["src", "generated"]
  }
}
project/
  src/
    app.ts
  generated/
    models.ts
// In src/app.ts - treated as same root
import { Model } from "./models"; // Finds generated/models.ts

Module Resolution State

The resolver maintains state during resolution:
// From src/compiler/moduleNameResolver.ts
interface ModuleResolutionState {
    compilerOptions: CompilerOptions;
    host: ModuleResolutionHost;
    traceEnabled: boolean;
    failedLookupLocations: string[];
    affectingLocations: string[];
    resultFromCache?: ResolvedModuleWithFailedLookupLocations;
}

Resolution Tracing

Enable detailed resolution logging:
{
  "compilerOptions": {
    "traceResolution": true
  }
}
Output:
======== Resolving module './utils' from '/project/src/app.ts'. ========
Module resolution kind is not specified, using 'Node10'.
Loading module as file / folder, candidate module location '/project/src/utils', target file types: TypeScript, Declaration.
File '/project/src/utils.ts' exists - use it as a name resolution result.
======== Module name './utils' was successfully resolved to '/project/src/utils.ts'. ========

Resolution Caching

Module resolution results are cached for performance:
// From src/compiler/program.ts
const moduleResolutionCache = createModuleResolutionCache(
    currentDirectory,
    getCanonicalFileName,
    options
);
The cache is invalidated when:
  • Compiler options change
  • Files are added/removed
  • package.json files change

Resolution Result

Successful resolution returns:
interface ResolvedModuleWithFailedLookupLocations {
    resolvedModule?: ResolvedModule;
    failedLookupLocations: string[];
    affectingLocations: string[];
    resolutionDiagnostics?: Diagnostic[];
}

interface ResolvedModule {
    resolvedFileName: string;
    originalPath?: string;
    extension: Extension;
    isExternalLibraryImport?: boolean;
    packageId?: PackageId;
}

Extension Resolution

The resolver tries different file extensions:
// From src/compiler/moduleNameResolver.ts
enum Extensions {
    TypeScript  = 1 << 0, // .ts, .tsx, .mts, .cts
    JavaScript  = 1 << 1, // .js, .jsx, .mjs, .cjs
    Declaration = 1 << 2, // .d.ts, .d.mts, .d.cts
    Json        = 1 << 3, // .json
    
    ImplementationFiles = TypeScript | JavaScript,
}

Extension Priority

import { x } from "./module";
Resolution order:
  1. module.ts
  2. module.tsx
  3. module.d.ts
  4. module/index.ts
  5. module/index.tsx
  6. module/index.d.ts

Package.json Support

TypeScript respects several package.json fields:

Standard Fields

{
  "name": "my-library",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "typings": "./dist/index.d.ts"
}

types

Primary field for TypeScript declarations

typings

Alternative to “types” (older packages)

main

Fallback entry point

Exports Field (Node16+)

{
  "exports": {
    ".": {
      "types": "./index.d.ts",
      "import": "./esm/index.js",
      "require": "./cjs/index.js",
      "default": "./index.js"
    },
    "./package.json": "./package.json"
  }
}

Type Versioning

{
  "typesVersions": {
    ">=4.5": {
      "*": ["ts4.5/*"]
    },
    "*": {
      "*": ["ts4.0/*"]
    }
  }
}

Resolution Modes

Import vs Require Resolution

// In Node16/NodeNext, resolution mode depends on file type

// In .mts or "type": "module"
import { x } from "library"; // Uses "import" condition

// In .cts or without "type": "module"
const { x } = require("library"); // Uses "require" condition

Triple-Slash Directives

/// <reference types="node" />
/// <reference path="./custom.d.ts" />

Troubleshooting Resolution

Problem: Cannot find module './utils' or its corresponding type declarations.Solutions:
  • Verify the file exists
  • Check file extension requirements (Node16+)
  • Enable traceResolution to see lookup paths
  • Check baseUrl and paths configuration
Problem: TypeScript resolves to an unexpected file.Solutions:
  • Clear the module resolution cache
  • Check for multiple node_modules directories
  • Verify paths mapping order
  • Review package.json “exports” field
Problem: Library found but no type declarations.Solutions:
  • Install @types/library package
  • Check package.json “types” field
  • Add ambient declarations (.d.ts file)
  • Use skipLibCheck as temporary workaround
Problem: Exports field ignored.Solutions:
  • Use Node16 or NodeNext module resolution
  • Update TypeScript to 4.7+
  • Verify exports field syntax
  • Check for “types” condition in exports

Best Practices

Use Modern Resolution

Prefer Node16 or NodeNext for new projects to support package.json exports.
{
  "compilerOptions": {
    "moduleResolution": "NodeNext"
  }
}

Avoid Deep Relative Imports

Use path mapping to simplify imports:
{
  "compilerOptions": {
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

Enable Resolution Tracing

Use traceResolution during debugging:
{
  "compilerOptions": {
    "traceResolution": true
  }
}

Compiler Overview

Learn about the full compilation pipeline

Emit

Understand JavaScript generation

Build docs developers (and LLMs) love