Part 8 · 2 chapters · ~20 min
Tooling and the Ecosystem
The toolchain around the checker (per-file transpilers in the loop, the language service in the editor, tsc as the CI gate, typed lint rules, type-only imports, one config every tool reads), and the strictness set: what each flag catches, the order to turn them on, and the baseline, codemod, dashboard and date that migrate a large codebase to it. Then the course in one page.
17
The toolchain around the checker
who does what, and how they agree
- Transpile versus check: esbuild, swc and oxc strip types per file without resolving any (milliseconds; no checking) and require isolatedModules-safe code (part 5). Vite, Vitest, Next and most runners use one for the loop; tsx and ts-node --swc for scripts. The Architecture course part 1's two pipelines.
- The language service is the checker in the editor, incremental per keystroke, serving hovers, completions, diagnostics and refactors; a slow type is editor lag before it is CI time; one tsserver per project, loading referenced projects on demand.
- tsc in CI (
--noEmitor--build) is the only whole-program check: incremental with a cached buildinfo, type tests riding along (part 4),--extendedDiagnosticson main weekly for the instantiation trend. - Typed lint rules (typescript-eslint with projectService) express what types alone cannot: no-floating-promises, no-unsafe-* (any leaking), strict-boolean-expressions, switch-exhaustiveness-check (part 6 as a lint), consistent-type-imports. They need the checker, so they run on save and in CI, not per keystroke.
- Type-only imports (
import type,import { type T }) are the contract between the checker and the transpiler;verbatimModuleSyntaxmakes them mandatory so a transpiler never guesses whether an import is a value. - Agreement is a config discipline: one base tsconfig every tool reads;
moduleResolution: bundlerfor bundled apps andnode16/nodenextfor Node packages; paths aliases declared once and mirrored in the bundler; the same target everywhere. "Works in the editor, fails in Vite" or "passes tsc, crashes at runtime" is always two tools disagreeing.
THE TOOLCHAIN AROUND THE CHECKER
who transpiles, who checks, who lints with types, and how the pieces agree
swipe the figure sideways, or tap expand for full screen
1/6
transpile vs check
Transpile versus check: esbuild, swc and oxc strip types and transform syntax per file without resolving a single type; they are fast because they do no checking; they require isolatedModules-safe code (part 5: no const enum across files, type-only re-exports marked). Vite, Vitest, Next and most runners use one of them for the loop; tsx and ts-node --swc run scripts the same way.
18
The strictness set, and migrating to it
every flag is a class of runtime error made visible
- The strict bundle: noImplicitAny (the hole in the lattice, part 0), strictNullChecks (null and undefined not in every type: the largest source of runtime errors), strictFunctionTypes (part 2), strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables (part 0), strictBindCallApply, alwaysStrict. A new project turns it all on.
- Three outside strict: noUncheckedIndexedAccess (an index may be undefined: true and noisy; the FSD course M6's sparse lists), exactOptionalPropertyTypes (absent is not explicitly undefined; matters for spreads and defaults), noPropertyAccessFromIndexSignature (index signatures accessed as lookups, so typos are visible).
- The order for an existing codebase: fewest errors first: noImplicitAny (mechanical), the small flags, then strictNullChecks (thousands of errors and the real bugs), then noUncheckedIndexedAccess (per package when ready), then exactOptionalPropertyTypes (breaks spreads; last).
- The baseline: record every current error by file and message so CI fails only on new ones; the baseline shrinks as owners fix files; a zero-error file locks strict and cannot regress; a dashboard by package and a date after which the flag is on without a baseline. The Big-company FE course part 2's migration machinery, for a compiler flag.
- The codemod for the mechanical third: implicit any to explicit any (honest and greppable) or unknown; nullable reads to optional chaining where the fallback is undefined; catch variables to unknown with a narrowing helper. The rest is hand work by owners, which is where the production crashes are found.
- The measure: errors remaining by flag and package, files locked, the date. A million lines takes a quarter with the machinery and never finishes without it; the last flag on is the day the type system is the one this course described.
the course, in one page
| part | the idea | the one thing to do |
|---|---|---|
| 0 the lattice | Assignability is an ordering; any is a hole | Replace any with unknown and narrow |
| 1 narrowing | Control-flow analysis over your branches | Replace a cast with a const, a predicate or satisfies |
| 2 generics | Variance from use; a generic earns its parameters by inferring them | Remove the parameter no argument infers |
| 3 type-level | Conditionals, mapped and template types compute; distribution and depth are the traps | Hover the result; simplify until legible |
| 4 declarations | Merging and ambient declarations describe without checking; the .d.ts is a contract | Read your generated .d.ts as a stranger |
| 5 the compiler | The checker is structural and lazy; the trace names the type | Run extendedDiagnostics and fix the top type |
| 6 library vs app | A surface versus a domain; unions with exhaustive checks | Add assertNever to one switch |
| 7 domain | Illegal states unrepresentable; parse at the boundary | Turn one bag of optionals into a union and parse it once |
| 8 tooling | Transpile in the loop, check in CI, lint with types, one config; strict with a baseline | Turn on the next flag with a baseline and a date |
THE STRICTNESS SET, AND MIGRATING A CODEBASE TO IT
what each flag catches, the order to turn them on, and the machinery that gets a million lines there
swipe the figure sideways, or tap expand for full screen
1/6
the strict bundle
What each flag catches: noImplicitAny (a parameter or variable the checker could not type: the hole in the lattice, part 0); strictNullChecks (null and undefined are not members of every type: the single largest source of runtime errors, made visible); strictFunctionTypes (parameters checked contravariantly, part 2); strictPropertyInitialization (a class field not assigned in the constructor); useUnknownInCatchVariables (catch (e) is unknown, not any: part 0).