TypeScript Step-by-Step Guide: How to Convert a JavaScript Project the Right Way

Quick TL;DR

If you want safer code and better tooling, migrate incrementally:

  1. Install TypeScript, add tsconfig.json.
  2. Use allowJs + checkJs to gradually type-check JS.
  3. Rename one file at a time to .ts/.tsx.
  4. Use a migration tool (like ts-migrate) to jumpstart conversion.
  5. Add .d.ts where libraries lack types (DefinitelyTyped).
  6. Tighten compiler options and replace any with real types over time. typescriptlang.org+1

Why migrate? The benefits (short)

  • Type safety: catches more bugs at compile time.
  • Great editor tooling: better auto-complete, refactors and navigation.
  • Maintainability: explicit APIs and easier onboarding.
    These improvements are especially valuable for mid- to large-sized codebases and teams. (General guidance from TypeScript docs and common industry practice.) typescriptlang.org

Preparation: what to check before you start

  • Confirm Node / build tool compatibility (Babel, Webpack, Vite, etc.).
  • Run tests and ensure good unit test coverage — it helps catch runtime regressions during migration.
  • Pick a branch/time window — big migrations are easier in small increments.

Step 1 — Install TypeScript & basic setup

Install TypeScript locally:

 npm install --save-dev typescript
npm install --save-dev @types/node    # optional for Node projects
  1. Create a basic tsconfig.json:
 {
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": false,
    "esModuleInterop": true,
    "allowJs": true,
    "checkJs": false,
    "outDir": "dist",
    "declaration": true
  },
  "include": ["src/**/*"]
}

2. allowJs lets TypeScript accept .js inputs; checkJs controls whether JS files are type-checked. Start with checkJs:false and raise it later. For a full list of compiler options consult the tsconfig reference. typescriptlang.org+1

Step 2 — Incremental migration strategy (safe & proven)

There are two widely used incremental approaches:

A. Allow-JS first (less friction):

  • Keep .js files. Set “allowJs”: true and leave checkJs off initially.
  • Add // @ts-check or enable checkJs only in folders you want to evaluate.
  • Rename a few stable files to .ts/.tsx and fix the immediate compiler errors.

B. Rename-first (bolder):

  • Pick a small module or feature and rename its .js → .ts.
  • Fix all type errors and add explicit types. Repeat module by module.

Both approaches are used in real projects; Airbnb and others use tools to accelerate larger changes. typescriptlang.org+1

Step 3 — Use tooling to speed things up

  • ts-migrate (Airbnb) can bulk-rename and scaffold types to make the repo compile quickly. It will often add any or // @ts-expect-error markers that you should remove over time. Use it for large codebases with follow up cleanup. GitHub+1
  • Editor support: VS Code + TypeScript gives fast feedback and automated code actions (inlay hints, quick fixes). Use editor refactors for safe renames and signature updates.

Step 4 — Handling third-party libraries (types)

  • Search for @types/ packages on npm (DefinitelyTyped). If a package lacks types, either:
    • Install @types/<lib> if available, or
    • Add a local /types folder with a module.d.ts stub (ambient declarations) until you write proper types.
  • You can learn best practices for writing declaration files from the DefinitelyTyped guides. GitHub+1

Example minimal module shim:

// types/my-legacy-lib/index.d.ts
declare module "my-legacy-lib" {
  export function doThing(opts: any): any;
}

Step 5 — Using JSDoc for a gentler ramp

If you want types without renaming files yet, JSDoc annotations can teach TypeScript about your JS code. TypeScript understands many JSDoc tags and you can enable checkJs to inspect those files. This is great for progressively improving types while keeping .js filenames. typescriptlang.org+1

Step 6 — Tighten compiler flags and remove any

Start with “strict”: false if needed. As you gain confidence, flip on these flags one at a time:

  • strict (umbrella) → true
  • noImplicitAny
  • strictNullChecks
  • noImplicitReturns
  • noUnusedLocals / noUnusedParameters

Fix or annotate code flagged by the compiler. Prefer specific types over any. This incremental shift yields the real long-term value of TypeScript: fewer runtime surprises.

Step 7 — Common gotchas & how to fix them

  • Default exports vs named: Use esModuleInterop and allowSyntheticDefaultImports to smooth interop with CommonJS. typescriptlang.org
  • DOM vs Node types: Be explicit — don’t include both global type sets unless you need them. Use types or typeRoots in tsconfig to scope global types. typescriptlang.org
  • Large codebase noise: Tools will sometimes create many any or @ts-expect-error markers. Turn them into tickets and track incremental fixes. Consider linting rules and CI checks to prevent regressions.

Example: Convert a small module (practical)

Original JS

// src/calc.js
function sum(a, b) {
  return a + b;
}
module.exports = { sum };

Step 1: rename to src/calc.ts

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

Fix usage

import { sum } from './calc';
const total: number = sum(2, 3);

This small example shows the simplest type insertion; real-world modules typically need interface/type definitions for objects and functions.

CI & tests: enforce quality during migration

  • Add a TypeScript build step to CI (e.g., tsc –noEmit or npm run build) to avoid shipping untyped code.
  • Use ESLint with TypeScript plugin to enforce typing rules progressively.
  • Run unit tests after each migration chunk to catch runtime regressions.

Long-term maintenance & best practices

  • Keep library type definitions under version control if you modify them locally, and contribute to DefinitelyTyped when appropriate. GitHub
  • Use smaller, focused PRs that migrate a feature or folder — easier to review.
  • Pair migration with adding tests where coverage is missing.

Recommended learning & references (reliable sources)

  • TypeScript Handbook — Migrating from JavaScript (official guidance). typescriptlang.org
  • TypeScript Compiler Options (tsconfig reference). typescriptlang.org
  • checkJs / allowJs documentation (how to gradually adopt). typescriptlang.org
  • Airbnb ts-migrate (tool + engineering writeup) — helpful for large codebases. GitHub+1
  • DefinitelyTyped — how to write and publish declaration files. GitHub

Final checklist (copy-paste for your repo)

  • npm i -D typescript @types/node
  • tsconfig.json with “allowJs”: true initially
  • Add ts-migrate for large codebases (optional)
  • Rename and convert 1 module/folder; run tsc –noEmit
  • Install @types/* for libraries or add local .d.ts stubs
  • Turn on strict flags incrementally
  • Add TypeScript step to CI and update tests

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top