Migrating and Living With TypeScript
AdvancedHow to add TypeScript to an existing codebase without a rewrite, and how to keep any from spreading once it is in.
Overview
You will more often join a partially typed codebase than start a clean one, so the migration skills matter. TypeScript is designed for incremental adoption: allowJs lets the two coexist, checkJs applies checks to plain JavaScript, and strictness can be raised directory by directory. The discipline that decides whether it works is what you do with `any` — it is contagious, spreading through every value it touches, and a codebase that reaches for it under time pressure ends up with the cost of TypeScript and none of the benefit.
Incremental Adoption
A migration order that keeps the build green throughout.
// 1. Add TypeScript with the loosest possible config
{ "compilerOptions": { "allowJs": true, "strict": false, "noEmit": true } }
// 2. Convert leaf-first: utilities and types before components.
// A typed module makes everything importing it better; a typed
// component with untyped dependencies fights inference.
// 3. Turn on strict flags one at a time, fixing as you go
"noImplicitAny": true // usually the largest single batch
"strictNullChecks": true // usually the most valuable
// 4. Check JS files without renaming them
// @ts-check at the top of a .js file, with JSDoc types:
/** @param {string} slug @returns {Promise<Problem>} */
async function getProblem(slug) { ... }
// 5. Ratchet: never let the error count go up.
// tsc --noEmit in CI, and each PR must not add errors.Containing any
any spreads. These are the tools for keeping it at the edges.
// any infects everything downstream
const data: any = await res.json()
const name = data.user.name // any
const upper = name.toUpperCase() // any — no checking anywhere below
// unknown stops the spread at the source
const data: unknown = await res.json()
// forced to narrow before any use
// Suppressions, in order of preference
// @ts-expect-error — errors if the line STOPS failing, so it self-cleans
// @ts-ignore — silent forever, including after the bug is fixed
// Find where any lives
npx type-coverage --detail // percentage of typed expressions
// and forbid new ones:
// eslint: @typescript-eslint/no-explicit-any, no-unsafe-assignment
// Third-party modules with no types
declare module 'untyped-lib' {
export function doThing(input: string): Promise<void>
}
// Better than 'any' — one small honest declaration instead of a hole.Build Setup
Which tool does what. Bundlers strip types; only tsc actually checks them.
// esbuild, swc, Vite, Next: transpile TypeScript by DELETING types.
// They are fast because they never type-check. Which means:
// a broken type does not fail your dev server or your build.
// So run the checker separately, and in CI
"scripts": {
"typecheck": "tsc --noEmit",
"build": "next build",
"ci": "npm run typecheck && npm run lint && npm test"
}
// Because types are stripped per-file, these need explicit syntax:
import type { User } from './types' // erased for certain
export type { User }
"isolatedModules": true // enforces it
// Speed, for large repos
"incremental": true // caches in .tsbuildinfo
// project references to split the graph into checked unitsKey Points to Remember
- 1Adopt incrementally: allowJs with strict off, convert leaf modules first, then enable strict flags one at a time
- 2@ts-check with JSDoc types checks a .js file without renaming it
- 3any spreads to every value it touches; unknown at the boundary stops the spread
- 4@ts-expect-error is preferable to @ts-ignore because it errors once the underlying problem is fixed
- 5Bundlers strip types without checking them — tsc --noEmit in CI is what actually enforces the types
Interview Questions
Sign in to ask AriaHow would you introduce TypeScript into a large existing JavaScript codebase?
What is the difference between @ts-ignore and @ts-expect-error?
Why can a project build successfully while containing type errors?
Ask Aria about Migrating and Living With TypeScript
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.