| |

TypeScript 101 ๐Ÿ”ท Performance โ€” Type Checking at Scale

A TypeScript project that compiles in two seconds is a joy. A TypeScript project that takes two minutes is a productivity tax paid every time a developer saves a file, runs a test, or pushes to CI. Performance is not a feature that gets added at the end. It is a property of how the project is structured, how the compiler is configured, and which tools are used for which jobs.

The TypeScript compiler has been engineered for scale since the beginning, but its default configuration is not optimized for large codebases. The --build mode, project references, and incremental compilation are the features that make a monorepo with hundreds of packages compile in seconds rather than minutes . The TypeScript 7.0 rewrite in Go delivered 8xโ€“12x speedups on full builds by moving the compiler to native code with shared-memory multithreading . But even with the fastest compiler, the project’s structure determines whether the compiler can be incremental.

Key point: The single most impactful performance optimization for a large TypeScript project is to split it into smaller projects connected by project references. Each project compiles independently, produces its own declaration files, and records its own build state in a .tsbuildinfo file. When one project changes, only that project and its dependents are rechecked. The rest of the codebase is untouched. This is the mechanism that makes monorepos compile in seconds instead of minutes .


Why type-checking performance matters at scale

A small project has one tsconfig.json, a few dozen files, and a compile time measured in seconds. A large project has hundreds of files, dozens of @types packages, and a compile time measured in minutes. The difference is not linear. TypeScript’s type checker must resolve every type, every import, and every declaration in the entire program. As the program grows, the work grows faster than the line count.

The full-program-check problem. By default, tsc checks the entire program on every invocation. Every file is parsed, every import is resolved, every type is inferred. If the project has 5,000 files and the developer changes one, the compiler still checks all 5,000. The --incremental flag stores the state of the previous compilation in a .tsbuildinfo file and rechecks only what changed . But --incremental on a single project is limited: it can skip files that did not change, but it cannot skip the parts of the program that depend on a changed file.

The monorepo problem. In a monorepo with dozens of packages, a change to a shared package invalidates every package that depends on it. Without project references, the compiler rechecks the entire monorepo. With project references, the compiler checks the shared package, then the packages that directly depend on it, and skips the packages that do not. The dependency graph determines the order and the scope of the recheck .

The declaration-emit problem. When a project produces declaration files (.d.ts), the compiler must infer the types of every exported symbol. This inference requires the full type checker. The isolatedDeclarations flag requires explicit type annotations on exported symbols, which allows the declaration files to be generated per-file without the type checker. This is what enables parallel declaration emit in bundlers .

The editor problem. The language server (tsserver) runs the same type checker that tsc runs. A slow tsc means a slow editor. Every keystroke triggers a recheck, and the recheck must be fast enough to feel instant. TypeScript 7.0’s LSP-based server and multithreading reduced the time to open a file with an error in the VS Code codebase from 17.5 seconds to under 1.3 seconds .

The trade-off. Performance optimizations add configuration. Project references require composite: true, declaration: true, and a references array in each tsconfig.json. They require a root tsconfig.json that serves as the entry point for tsc --build. They require the dependency graph in package.json to match the references array. The configuration is more complex, but the payoff is a build that scales with the number of changed files rather than the total size of the codebase.


a. Project References and Build Mode

Project references are the primary tool for scaling TypeScript. They allow a codebase to be divided into multiple projects, each with its own tsconfig.json, its own compilation, and its own build state .

The references property in tsconfig.json lists the projects that the current project depends on:

{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "outDir": "./dist"
  },
  "references": [
    { "path": "../shared" },
    { "path": "../ui" }
  ],
  "include": ["src/**/*"]
}

The composite: true flag enables incremental compilation for the project. It requires declaration: true, because the referenced project exposes its .d.ts files to the projects that depend on it . The .tsbuildinfo file records the state of the last compilation. When tsc --build runs, it reads the .tsbuildinfo file and compares it against the current source. If nothing has changed in a project or its dependencies, the project is skipped .

The root tsconfig.json is the entry point for tsc --build:

{
  "files": [],
  "references": [
    { "path": "./packages/shared" },
    { "path": "./packages/ui" },
    { "path": "./packages/analytics" },
    { "path": "./apps/dashboard" }
  ]
}

The files: [] prevents TypeScript from compiling anything in the root directory. The references array tells tsc --build which projects exist. When tsc --build runs, it processes the projects in dependency order: shared first, then ui, then analytics (which depends on ui and shared), and finally dashboard (which depends on all of them). Projects that do not depend on each other can be built in parallel .

The performance gain is dramatic. A change to shared invalidates shared, ui, analytics, and dashboard. A change to analytics invalidates only analytics and dashboard. The rest of the monorepo is untouched. The second tsc --build after a change is faster than the first because the .tsbuildinfo files record what has already been compiled .


b. isolatedDeclarations and Parallel Emit

The isolatedDeclarations flag, introduced in TypeScript 5.5, requires that exported symbols have explicit type annotations. This requirement allows declaration files to be generated without the type checker, which in turn allows declaration emit to be parallelized .

Without isolatedDeclarations, generating a .d.ts file requires the full type checker to infer the types of exported symbols. This inference may depend on types imported from other modules, which means the declaration emit cannot happen until the entire program has been checked.

With isolatedDeclarations, the requirement is that every exported symbol has an explicit, locally-inferable type. The annotation can be a type reference, a union, an interface, or a primitive. What it cannot be is an inferred return type that depends on the types of other modules .

// Before isolatedDeclarations โ€” return type inferred
export function buildClient(config: ClientConfig) {
  return { send: (req: ApiRequest) => fetch(config.url, req) };
}

// After isolatedDeclarations โ€” explicit return type
export function buildClient(config: ClientConfig): ApiClient {
  return { send: (req: ApiRequest) => fetch(config.url, req) };
}

The isolatedDeclarations flag does not make the code compile faster by itself. It makes the declaration emit parallelizable, which allows tools like bundlers to generate .d.ts files per-file in parallel rather than waiting for the full program check . In a monorepo, this removes the cross-package type-check bottleneck from declaration emit and turns it into a per-file operation .

The flag is most valuable in monorepos where declaration files are the contract between packages. Each package’s .d.ts file can be generated independently, and downstream packages can read the .d.ts file without re-parsing the source .


c. Diagnosing Performance Issues

When a project is slow, the first step is to find out why. TypeScript provides several diagnostic flags that report what the compiler is doing and where the time is going.

The --extendedDiagnostics flag reports the time spent in each phase of the compiler: parsing, binding, checking, emitting. A high “Check time” indicates that the type checker is the bottleneck. A high “Program time” or “I/O Read time” indicates that the compiler is reading more files than it should .

The --listFilesOnly flag reports every file that the compiler is including in the program. If node_modules or test files are being included unexpectedly, the include and exclude settings in tsconfig.json are misconfigured. The --explainFiles flag explains why each file was included .

The --traceResolution flag reports the precise steps taken to resolve every import. This is used to diagnose module resolution issues that cause the compiler to look in the wrong places or fail to find a module .

The --generateTrace flag produces a performance trace that can be analyzed in Chrome’s about://tracing UI or with the @typescript/analyze-trace utility. The trace shows the call stack of the compiler and identifies the hot spots .

For editor performance, the typescript.tsserver.enableTracing setting in VS Code produces a trace from the language server. The TSS_LOG environment variable captures the tsserver protocol log, which shows every request and response .

The common causes of slow type checking are:

Misconfigured include and exclude. If node_modules is included, the compiler checks every .d.ts file in every dependency. The exclude array should include node_modules and hidden directories .

Missing skipLibCheck. The skipLibCheck option skips type checking of declaration files (.d.ts). It is safe to enable in almost every project and significantly reduces compile time .

Computationally intensive @types packages. Some @types packages contain complex type definitions that are expensive to check. Upgrading to a newer version or replacing the package can help .

No project references. A single tsconfig.json for a large codebase forces a full-program check on every invocation. Project references split the work into smaller units that can be checked incrementally .

No incremental compilation. The --incremental flag stores the state of the previous compilation in a .tsbuildinfo file. Without it, every invocation starts from scratch .


Complete Example Session

This session converts a single-project TypeScript codebase into a project-references setup and measures the performance difference.

// ============================================
// PART 1: THE SINGLE-PROJECT SETUP
// ============================================

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true,
    "exclude": ["node_modules", "**/.*/"]
  },
  "include": ["packages/**/*", "apps/**/*"]
}

// Run: tsc --noEmit --extendedDiagnostics
// Files: 4500
// Parse time: 1.2s
// Bind time: 0.8s
// Check time: 18.4s
// Total time: 21.5s

// ============================================
// PART 2: SPLIT INTO PROJECTS
// ============================================

// packages/shared/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

// packages/ui/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "skipLibCheck": true
  },
  "references": [
    { "path": "../shared" }
  ],
  "include": ["src/**/*"]
}

// packages/analytics/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "skipLibCheck": true
  },
  "references": [
    { "path": "../shared" },
    { "path": "../ui" }
  ],
  "include": ["src/**/*"]
}

// apps/dashboard/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "skipLibCheck": true
  },
  "references": [
    { "path": "../../packages/shared" },
    { "path": "../../packages/ui" },
    { "path": "../../packages/analytics" }
  ],
  "include": ["src/**/*"]
}

// ============================================
// PART 3: THE ROOT CONFIGURATION
// ============================================

// tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./packages/shared" },
    { "path": "./packages/ui" },
    { "path": "./packages/analytics" },
    { "path": "./apps/dashboard" }
  ]
}

// ============================================
// PART 4: THE FIRST BUILD
// ============================================

// npx tsc --build
// Compiles shared, then ui, then analytics, then dashboard.
// Each project writes its own .tsbuildinfo file.

// Time: 22.1s (similar to single-project because everything compiles)

// ============================================
// PART 5: THE SECOND BUILD (NO CHANGES)
// ============================================

// npx tsc --build
// Reads the .tsbuildinfo files.
// Detects that nothing has changed.
// Skips all projects.

// Time: 0.3s

// ============================================
// PART 6: CHANGE A FILE IN shared
// ============================================

// Edit packages/shared/src/utils.ts

// npx tsc --build
// Rechecks shared.
// Rechecks ui (depends on shared).
// Rechecks analytics (depends on shared and ui).
// Rechecks dashboard (depends on all).
// The change cascades through the dependency graph.

// Time: 8.2s

// ============================================
// PART 7: CHANGE A FILE IN analytics
// ============================================

// Edit packages/analytics/src/reports.ts

// npx tsc --build
// Rechecks analytics.
// Rechecks dashboard (depends on analytics).
// Skips shared and ui (they do not depend on analytics).

// Time: 3.1s

// ============================================
// PART 8: ENABLE isolatedDeclarations
// ============================================

// Add to each project's tsconfig.json:
{
  "compilerOptions": {
    "isolatedDeclarations": true
  }
}

// Now every exported symbol must have an explicit type annotation.
// The declaration emit can be parallelized.

// ============================================
// PART 9: THE EXTENDED DIAGNOSTICS
// ============================================

// tsc --noEmit --extendedDiagnostics

// Files: 4500
// Parse time: 0.4s
// Bind time: 0.2s
// Check time: 2.8s
// Total time: 3.6s

// The check time is much lower because the compiler
// only checks what has changed.

// ============================================
// PART 10: THE CI PIPELINE
// ============================================

// .github/workflows/ci.yml
- run: npx tsc --build --clean
- run: npx tsc --build

// The clean step removes the .tsbuildinfo files.
// The build step compiles everything from scratch.
// In a monorepo with many projects, this is faster
// than a single-project full check because of
// incremental compilation within each project.

The ten parts cover the single-project setup, splitting into projects, the root configuration, the first build, the second build, changing a file in shared, changing a file in analytics, enabling isolatedDeclarations, the extended diagnostics, and the CI pipeline.


Quick Reference

The Performance Features

FeaturePurposeConfiguration
Project referencesSplit into smaller projectsreferences array
Build modeCompile projects in dependency ordertsc --build
IncrementalStore state in .tsbuildinfocomposite: true
Declaration mapsCross-project go-to-definitiondeclarationMap: true
isolatedDeclarationsParallel declaration emitisolatedDeclarations: true
skipLibCheckSkip checking .d.ts filesskipLibCheck: true

The Diagnostic Flags

FlagPurpose
--extendedDiagnosticsTime spent in each compiler phase
--listFilesOnlyEvery file included in the program
--explainFilesWhy each file was included
--traceResolutionImport resolution steps
--generateTraceFull performance trace
--showConfigResolved compiler configuration

The Common Causes of Slow Type Checking

CauseFix
node_modules includedexclude: ["node_modules"]
skipLibCheck missingskipLibCheck: true
No project referencesSplit into projects
No incrementalcomposite: true
Expensive @types packageUpgrade or replace
Full-program checkUse tsc --build

The tsconfig for Project References

SettingValuePurpose
compositetrueEnable incremental compilation
declarationtrueEmit .d.ts files
declarationMaptrueEnable cross-project navigation
rootDir./srcPredictable declaration paths
outDir./distOutput directory
references[{ "path": "../shared" }]Dependency graph

Best Practices

โœ… Do This:

// Split the codebase into projects
{ "references": [{ "path": "../shared" }] }                  // โœ…
// Enable composite and declaration for each project
{ "compilerOptions": { "composite": true, "declaration": true } } // โœ…
// Enable skipLibCheck
{ "compilerOptions": { "skipLibCheck": true } }              // โœ…
# Use tsc --build instead of tsc
npx tsc --build                                               // โœ…
// Use isolatedDeclarations for parallel emit
{ "compilerOptions": { "isolatedDeclarations": true } }      // โœ…

โŒ Don’t Do This:

// Don't include node_modules
{ "include": ["**/*"] }  // without exclude                    // โŒ
// Don't skip declaration in composite projects
{ "composite": true }  // without declaration                  // โŒ
# Don't run tsc without --build in a project-references setup
npx tsc  // does not use project references                   // โŒ
// Don't mismatch references and package.json dependencies
// The references array must mirror package.json.             // โŒ

Common Pitfalls

PitfallWhy It HappensFix
tsc --build doesn’t buildfiles: [] missing in rootAdd files: []
Errors about missing filesinclude does not cover all filesFix include patterns
composite requires declarationDeclaration not enabledAdd declaration: true
References not usedtsc instead of tsc --buildUse --build
Stale type information.tsbuildinfo out of dateRun tsc --build --clean
Slow editorLarge project, no incrementalUse project references

Real-World Examples

1. Project References

{ "references": [{ "path": "../shared" }] }

2. Composite Project

{ "compilerOptions": { "composite": true, "declaration": true } }

3. Root Build Configuration

{ "files": [], "references": [{ "path": "./packages/shared" }] }

4. Build Mode

npx tsc --build

5. Clean Build

npx tsc --build --clean

6. Extended Diagnostics

npx tsc --noEmit --extendedDiagnostics

7. List Files

npx tsc --listFilesOnly

8. Explain Files

npx tsc --explainFiles > explanations.txt

9. Trace Resolution

npx tsc --traceResolution > resolutions.txt

10. Generate Trace

npx tsc --generateTrace tracing_output

Visual

The Single-Project vs Project-References

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SINGLE PROJECT                              โ”‚
โ”‚                                              โ”‚
โ”‚  tsconfig.json                               โ”‚
โ”‚    โ””โ”€ All files (4500)                       โ”‚
โ”‚                                              โ”‚
โ”‚  Change one file:                            โ”‚
โ”‚    โ””โ”€ Check all 4500 files                   โ”‚
โ”‚                                              โ”‚
โ”‚  Time: 21.5s                                 โ”‚
โ”‚                                              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  PROJECT REFERENCES                          โ”‚
โ”‚                                              โ”‚
โ”‚  shared (500 files)                          โ”‚
โ”‚    โ””โ”€ .tsbuildinfo                           โ”‚
โ”‚  ui (1200 files) โ† depends on shared         โ”‚
โ”‚    โ””โ”€ .tsbuildinfo                           โ”‚
โ”‚  analytics (800 files) โ† depends on shared, uiโ”‚
โ”‚    โ””โ”€ .tsbuildinfo                           โ”‚
โ”‚  dashboard (2000 files) โ† depends on all     โ”‚
โ”‚    โ””โ”€ .tsbuildinfo                           โ”‚
โ”‚                                              โ”‚
โ”‚  Change one file in shared:                  โ”‚
โ”‚    โ””โ”€ Check shared, ui, analytics, dashboard โ”‚
โ”‚    โ””โ”€ Time: 8.2s                             โ”‚
โ”‚                                              โ”‚
โ”‚  Change one file in analytics:               โ”‚
โ”‚    โ””โ”€ Check analytics, dashboard             โ”‚
โ”‚    โ””โ”€ Time: 3.1s                             โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Dependency Graph

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  DEPENDENCY GRAPH                            โ”‚
โ”‚                                              โ”‚
โ”‚  shared                                      โ”‚
โ”‚    โ”œโ”€ ui                                     โ”‚
โ”‚    โ”‚    โ””โ”€ analytics                         โ”‚
โ”‚    โ”‚         โ””โ”€ dashboard                    โ”‚
โ”‚    โ””โ”€ analytics                              โ”‚
โ”‚         โ””โ”€ dashboard                         โ”‚
โ”‚                                              โ”‚
โ”‚  Build order:                                โ”‚
โ”‚    1. shared                                 โ”‚
โ”‚    2. ui                                     โ”‚
โ”‚    3. analytics                              โ”‚
โ”‚    4. dashboard                              โ”‚
โ”‚                                              โ”‚
โ”‚  Parallel: ui and analytics could build      โ”‚
โ”‚  in parallel if they did not depend on       โ”‚
โ”‚  the same upstream.                          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The isolatedDeclarations Parallel Emit

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  isolatedDeclarations                        โ”‚
โ”‚                                              โ”‚
โ”‚  Without:                                    โ”‚
โ”‚    .d.ts emit requires full program check    โ”‚
โ”‚    โ””โ”€ Sequential, slow                       โ”‚
โ”‚                                              โ”‚
โ”‚  With:                                       โ”‚
โ”‚    .d.ts emit is per-file, parallelizable    โ”‚
โ”‚    โ””โ”€ Bundler can emit in parallel           โ”‚
โ”‚    โ””โ”€ No cross-package type-check bottleneck โ”‚
โ”‚                                              โ”‚
โ”‚  Requires:                                   โ”‚
โ”‚    โ””โ”€ Explicit return types on exports       โ”‚
โ”‚    โ””โ”€ Explicit types on exported variables   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Diagnostic Decision Tree

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  DIAGNOSING SLOW TYPE CHECKING               โ”‚
โ”‚                                              โ”‚
โ”‚  tsc --extendedDiagnostics                   โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”œโ”€ High I/O Read time                  โ”‚
โ”‚       โ”‚    โ””โ”€ Fix include/exclude            โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”œโ”€ High Program time                   โ”‚
โ”‚       โ”‚    โ””โ”€ Check listFilesOnly            โ”‚
โ”‚       โ”‚    โ””โ”€ Use explainFiles               โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ”œโ”€ High Check time                     โ”‚
โ”‚       โ”‚    โ””โ”€ Split into project references  โ”‚
โ”‚       โ”‚    โ””โ”€ Enable skipLibCheck            โ”‚
โ”‚       โ”‚    โ””โ”€ Upgrade @types packages        โ”‚
โ”‚       โ”‚                                      โ”‚
โ”‚       โ””โ”€ High Emit time                      โ”‚
โ”‚            โ””โ”€ Enable isolatedDeclarations    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Primary optimizationProject references
Build modetsc --build
Incremental state.tsbuildinfo
Required for compositedeclaration: true
Cross-project navigationdeclarationMap: true
Parallel declaration emitisolatedDeclarations: true
Skip declaration checkingskipLibCheck: true
Diagnostic flag--extendedDiagnostics
Trace flag--generateTrace
TypeScript 7.0 speedup8xโ€“12x on full builds

Key takeaways:

  • Project references split a large codebase into smaller projects. Each project has its own tsconfig.json, its own compilation, and its own .tsbuildinfo file. A change to one project invalidates only that project and its dependents. The rest of the codebase is skipped .
  • Build mode (tsc --build) processes projects in dependency order. The references array in each project’s tsconfig.json defines the graph. tsc --build reads the .tsbuildinfo files and skips projects that have not changed .
  • composite: true is required for referenced projects. It enables incremental compilation and requires declaration: true so that the project’s output can be consumed by the projects that depend on it .
  • isolatedDeclarations enables parallel declaration emit. By requiring explicit type annotations on exported symbols, it allows .d.ts files to be generated per-file without the type checker. This removes the cross-package type-check bottleneck in monorepos .
  • Diagnostic flags identify the bottleneck. --extendedDiagnostics reports the time in each compiler phase. --listFilesOnly and --explainFiles show what the compiler is including. --traceResolution shows how imports are resolved. --generateTrace produces a full performance trace .
  • The most common causes of slow type checking are misconfigured include/exclude, missing skipLibCheck, and the absence of project references. Fixing these three issues resolves most performance problems .
  • TypeScript 7.0’s native Go compiler delivered 8xโ€“12x speedups on full builds. The speedup comes from native code, shared-memory multithreading, and a more efficient traversal of the syntax graph. The --checkers and --builders flags tune the parallelism .

Remember: Type-checking performance at scale is not about making the compiler faster. It is about giving the compiler less work to do. Project references split the work into smaller units. Incremental compilation skips what has not changed. isolatedDeclarations enables parallel emit. skipLibCheck skips what does not matter. The compiler is fast. The question is whether the project is structured to let it be fast. A single tsconfig.json for a 5,000-file codebase forces a full-program check on every save. The same codebase split into ten projects with project references rechecks only what changed. The configuration is more complex. The performance is worth it.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!