← thecodex.expert · The Codex Family of Knowledge
TypeScript

The TypeScript Compiler

The compiler’s type-checking pass and its code-emitting pass are genuinely separate steps — which is why type errors don’t always stop JavaScript from being generated.

TypeScript 6.0 Parse → check → emit Last verified:
Where this bites you

TypeScript 6.0 changed how tsc decides the folder layout inside outDir, and a tsconfig that sets outDir without rootDir now fails with TS5011. The error isn't the worst part: because of the emit-on-error default described below, tsc still writes the JavaScript, just one folder deeper than before, at dist/src/index.js. A start script that runs node dist/index.js then can't find the file, and if CI doesn't fail on tsc's exit code, the first sign is a server that won't boot.

TypeScripttsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "outDir": "dist"
  },
  "include": ["src"]
}
Shellterminal
$ tsc
tsconfig.json(6,5): error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.
  Visit https://aka.ms/ts6 for migration information.
$ find dist -type f
dist/src/index.js
$ node dist/index.js
Error: Cannot find module '.../dist/index.js'
  code: 'MODULE_NOT_FOUND',
Canonical Definition

tsc runs through several distinct stages: parsing source into an abstract syntax tree, binding to build a symbol table, type-checking the AST against that table, and finally emitting plain JavaScript with all type syntax completely erased. These stages are genuinely separable — type-checking and emitting are different passes, which is why tsc can be configured to still emit JavaScript even when type errors are present. Project references let a large codebase be split into multiple tsconfig.json projects, each independently and incrementally compiled, which meaningfully speeds up builds in a monorepo.

Type errors don't necessarily stop JavaScript output

By default, tsc still emits JavaScript even when type errors exist — checking and emitting are separate passes. noEmitOnError changes this, refusing to emit any output at all if type-checking failed.

TypeScripttsconfig.json
{
  "compilerOptions": {
    "noEmitOnError": true   // refuse to emit .js files if there are type errors
  }
}

Two things from this page combine there: the separate check and emit passes (so a config error still produced output), and the output layout, which mirrors your source folders relative to rootDir. Setting rootDir explicitly pins that layout. As a side benefit, a file that later appears outside src becomes a hard error (TS6059, "not under 'rootDir'") instead of quietly reshaping dist again:

TypeScripttsconfig.json (fixed)
{
  "compilerOptions": {
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}
Shellterminal
$ tsc
$ find dist -type f
dist/index.js
$ node dist/index.js
server started

Incremental compilation

incremental caches information about the previous compilation in a .tsbuildinfo file, so a subsequent build only re-checks what actually changed — a meaningful speedup on large codebases, especially combined with --watch mode during development.

TypeScripttsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.tsbuildinfo"
  }
}

Project references for large codebases

Splitting a large monorepo into multiple smaller projects, each with its own tsconfig.json and a references array pointing at its dependencies, lets tsc rebuild only the projects that actually changed rather than the entire codebase every time — a significant win for build time as a repository grows.

TypeScripttsconfig.json (app)
{
  "compilerOptions": { "composite": true },
  "references": [
    { "path": "../shared-utils" }
  ]
}

2026 brought this whole pipeline its most significant change ever: TypeScript 7.0, which ports the entire compiler and language service to Go, went to Release Candidate in June 2026 and reached a full release on August 3, 2026. Microsoft reports typical speedups of roughly 8–12x on full builds — VS Code's own build time dropped from 125.7s to 10.6s in their benchmarks. The Go port was methodically translated from the existing TypeScript codebase rather than rewritten from scratch, with the stated goal of structurally identical checking behavior to TypeScript 6.0; the project tracks the handful of known, intentional behavioral differences in its own CHANGES.md rather than claiming zero differences at all.

Sources

1
Microsoft. TypeScript Handbook, "Compiler Options" and "Project References", typescriptlang.org/docs/handbook.