๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

TypeScript Module Resolution

Module resolution is the set of rules the compiler follows to find the file behind an import. The moduleResolution setting picks the rule set, and node16 follows the strict rules of modern Node.

A tsconfig with node16 resolution

The config sets both module and moduleResolution to node16 and turns on strict checks. The closing example of this lesson is compiled by this file as a small project.

{
  "compilerOptions": {
    "target": "es2020",
    "module": "node16",
    "moduleResolution": "node16",
    "strict": true,
    "outDir": "out"
  }
}

The util.ts file

The helper file exports one greet function. Every import in this lesson resolves to this file.

export function greet(userName: string): string {
  return 'Hello, ' + userName + '!';
}

An extensionless import under node mode

The default node lookup tries the path as a file and adds the extension while searching. This program uses the default mode and runs.

import { greet } from './util';

console.log(greet('Ada'));

node16 refuses a missing extension

A dynamic import is an ECMAScript import, so node16 applies the extension rule to its specifier. The specifier names no extension and the real compiler refusal follows.

async function main(): Promise<void> {
  const mod = await import('./util');
  console.log(mod.greet('Ada'));
}

void main();

Example

import { greet } from './util.js';

console.log(greet('Ada'));
console.log(greet('Grace'));

Key points

  • Classic resolution walks up the folder tree looking for a matching file name.
  • Node resolution mirrors older CommonJS lookup, so it tries the file itself, then a folder index.
  • node16 resolution follows modern Node, where ECMAScript imports need an explicit file extension.
  • The written import names the emitted file, so a TypeScript source imported under node16 is named with its .js extension.
  • Bundler resolution suits projects built by a bundler and is named here for completeness.

In this example, the project compiles under its own node16 tsconfig, the import names util.js with its extension, and Node runs the emitted files.

Note

The setting changes how imports are found, never what the code means once found.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

Under node16, how does an ECMAScript import name a sibling TypeScript file?