Migrating a JavaScript Project to TypeScript

Step by Step

  1. Install typescript and @types/* for your libraries; add a tsconfig.json with "allowJs": true and "strict": false to start.
  2. Make the build work with mixed .js/.ts files. Add tsc --noEmit to CI.
  3. Turn on "checkJs": true or add // @ts-check to important JS files to get errors from JSDoc and inference.
  4. Rename files to .ts one folder at a time, starting with leaf modules (utils, constants, types) that others depend on.
  5. Type the boundaries first: API responses, database models, function parameters of shared modules.
  6. Enable strict flags one by one (noImplicitAny, then strictNullChecks, ...). Use // @ts-expect-error with a TODO for hard cases, never blanket any.
  7. Finally set "allowJs": false and "strict": true.

ts-expect-error vs ts-ignore

Example

@ts-expect-error suppresses the next line's error but itself becomes an error (TS2578) once the problem is fixed, so suppressions do not rot. @ts-ignore stays silent forever.

function total(prices: number[]): number {
  return prices.reduce((a, b) => a + b, 0);
}

// @ts-expect-error legacy caller still passes strings, fix in JIRA-123
total(["10", "20"]);

// @ts-expect-error this line is actually fine, so the directive itself is reported
total([10, 20]);

Compiler output (tsc)

Line 8: error TS2578: Unused '@ts-expect-error' directive.

Migration Helpers

  • npx ts-migrate (Airbnb) renames files and adds any/@ts-expect-error automatically so the build is green on day one; then you tighten gradually.
  • Track progress with type-coverage (percentage of identifiers that are not any).
  • Large companies (Airbnb, Slack, Stripe, Bloomberg) migrated millions of lines this way, team by team.