Lab 05: Performance Optimization

Time: 40 minutes | Level: Advanced | Docker: docker run -it --rm node:20-alpine sh

Optimize TypeScript compilation speed and reliability using project references, incremental builds, compiler flags, and type-level performance patterns.


Step 1: Environment Setup

docker run -it --rm node:20-alpine sh
npm install -g typescript
mkdir lab05 && cd lab05

💡 We'll measure compilation times with tsc --diagnostics. Even small projects benefit from understanding these metrics at scale.


Step 2: Understanding tsc --diagnostics

Create a sample project and measure baseline performance:

mkdir src
cat > src/index.ts << 'EOF'
export function greet(name: string): string {
  return `Hello, ${name}!`;
}

export function add(a: number, b: number): number {
  return a + b;
}

export interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

export class UserService {
  private users: Map<number, User> = new Map();

  create(name: string, email: string): User {
    const id = this.users.size + 1;
    const user: User = { id, name, email, createdAt: new Date() };
    this.users.set(id, user);
    return user;
  }

  findById(id: number): User | undefined {
    return this.users.get(id);
  }

  getAll(): User[] {
    return Array.from(this.users.values());
  }
}
EOF

cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src"]
}
EOF

tsc --diagnostics

📸 Verified Output:

💡 Key metrics: Parse time = reading .ts files. Check time = type checking. For large projects, check time dominates. tsc --extendedDiagnostics gives even more detail.


Step 3: skipLibCheck and isolatedModules

Two flags with major performance impact:

When to use each:

Flag
Speedup
Trade-off

skipLibCheck: true

10-40%

Misses errors in .d.ts files

isolatedModules: true

Enables parallel transpilation

Can't use const enum, namespace merging

💡 isolatedModules is required for tools like babel, esbuild, and swc that transpile TypeScript without type checking.


Step 4: Incremental Builds

Enable caching with incremental: true:

💡 .tsbuildinfo stores a JSON snapshot of the last compilation. Add it to .gitignore but keep it in CI for faster builds.


Step 5: Project References (composite: true)

Split large projects into sub-projects for parallel, incremental compilation:

packages/shared/src/types.ts:

packages/shared/tsconfig.json:

💡 composite: true requires declaration: true. It signals this project can be referenced by others and enables tsc --build mode.

packages/core/src/services.ts:

packages/core/tsconfig.json:

Root tsconfig.json (build orchestrator):

Build all with dependency ordering:


Step 6: Avoiding Deep Recursive Type Performance Traps

Some type patterns can exponentially slow compilation:

💡 Rule of thumb: If a conditional type recurses more than 10 levels deep, TypeScript will throw "Type instantiation is excessively deep". Use mapped types or utility types instead.


Step 7: declaration Maps and Source Maps

Enable better debugging and IDE support:

💡 declarationMap: true lets consumers of your library "Go to definition" and land in your original .ts source rather than the generated .d.ts file.


Step 8: Capstone — Optimized tsconfig Template

Production-ready tsconfig with all optimizations:

📸 Verified tsc --diagnostics Output:


Summary

Optimization
Config
Impact

skipLibCheck: true

tsconfig

10-40% faster

isolatedModules: true

tsconfig

Enables parallel transpilation

incremental: true

tsconfig

10-15x faster on unchanged files

Project references

composite: true + references

Parallel sub-project builds

tsc --build

CLI flag

Dependency-ordered builds

Avoid deep recursion

Type patterns

Prevent O(n) type instantiation

declarationMap: true

tsconfig

Better "Go to definition"

Last updated