Skip to content
intermediate Phase 13 · TypeScript Build & Tooling

Deep tsconfig.json

Master all TypeScript compiler options and project references.

1h
0 problems
Topic Progress 0%

Strictness and Checking Options

Strictness and Checking Options

"strict": true is the floor; these flags catch the defects that still slip past it:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "verbatimModuleSyntax": true
  }
}

noUncheckedIndexedAccess types arr[0] as T | undefined, forcing bounds checks. exactOptionalPropertyTypes stops { a: undefined } from satisfying { a?: string }. verbatimModuleSyntax preserves import type elision exactly as written, which keeps side-effect-only imports from vanishing under isolatedModules transpilers.

Module Resolution and Paths

Module Resolution and Paths

module and moduleResolution must agree with the runtime:

{ "module": "nodenext", "moduleResolution": "nodenext" }
{ "module": "esnext",  "moduleResolution": "bundler" }

Under nodenext, relative imports need explicit .js extensions and package exports are honored, matching Node ESM. bundler allows extensionless imports because a bundler resolves them. paths maps aliases for the type checker:

{ "paths": { "@app/*": ["./src/*"] } }

Paths are type-level only — Jest, Vite, and Node need matching alias config or types compile while runtime resolution fails.

Project References and Build Flags

Project References and Build Flags

Project references split a repo into independently built pieces:

{ "compilerOptions": { "composite": true, "declaration": true, "outDir": "dist" },
  "references": [{ "path": "../core" }] }

composite requires declaration and a fixed rootDir; consumers then import via .d.ts outputs instead of re-typechecking sources. tsc -b builds the dependency graph in order and skips up-to-date projects (tracked in .tsbuildinfo), which is what makes monorepo typechecking incremental.

Pitfall: references govern TypeScript only — bundlers and test runners still need their own resolution, and a stale outDir after refactoring rootDir produces confusing "file not found" errors.

Quiz

1. What does noUncheckedIndexedAccess change?

Question 1 options

2. Why can paths aliases compile in tsc but fail at runtime?

Question 2 options

3. What does composite: true enable?

Question 3 options

Flashcards

Question

noUncheckedIndexedAccess

Answer

Makes arr[i] T | undefined so missing indexes are handled explicitly.

Question

moduleResolution: nodenext

Answer

Node ESM rules: explicit .js extensions on relative imports, honors package exports.

Question

moduleResolution: bundler

Answer

Extensionless imports allowed because a bundler resolves them at build time.

Question

Composite + references

Answer

declaration-required projects built incrementally with tsc -b, consuming .d.ts outputs.

Question

paths

Answer

Type-checker alias mapping only — mirror it in Jest/Vite/Node or runtime resolution breaks.

Revision Notes

Key Takeaways

  • 1. strict plus indexed-access flags catch the defects null checks alone miss
  • 2. Choose moduleResolution from the actual runtime, not habit
  • 3. Project references make typechecking incremental but do not configure bundlers

Interview Tips

  • • State which tool resolves each specifier: tsc, bundler, test runner, or Node
  • • Explain why two configs (nodenext vs bundler) can both be right for different packages

Cheat Sheet

strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes; nodenext needs .js extensions, bundler does not; paths is type-only; composite + tsc -b = incremental.

Continue Learning