---
# === IDENTITY ===
id: software/migrations/javascript-to-typescript/2026
canonical_question: "How do I migrate a JavaScript project to TypeScript?"
aliases:
  - "JavaScript to TypeScript migration guide"
  - "convert JavaScript to TypeScript"
  - "add TypeScript to existing JS project"
  - "JS to TS migration step by step"
  - "incremental TypeScript adoption"
  - "migrate Node.js project to TypeScript"
  - "TypeScript migration best practices"
  - "tsconfig allowJs migration"
entity_type: software_reference
domain: software > migrations > javascript_to_typescript
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "TypeScript 6.0 (Mar 23, 2026) — strict-by-default, esnext module defaults, last JS-based release"
  next_review: 2026-11-13
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "TypeScript 6.0 (Mar 2026, last JS-based release) enables strict mode by default — new migrations should target full strict compliance from the start"
  - "Node.js 22.18+ supports native TypeScript via type stripping — no tsc build step needed for runtime, but tsc is still required for type checking"
  - "Never enable strict: true on a large legacy codebase in one step — enable individual strict flags incrementally to avoid hundreds of errors"
  - "Do not mix module systems — use 'module: nodenext' (TS 6.0 default) or 'Node16', matching the package.json 'type' field; 'moduleResolution: node' is deprecated in 6.0"
  - "Airbnb's ts-migrate is unmaintained since 2023 — prefer AI-assisted migration tools (Cline/Gemini, Grit) or manual incremental approach for new projects"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User is migrating from Flow to TypeScript (not plain JavaScript)"
    use_instead: "Stripe's flow-to-typescript-codemod (github.com/stripe-archive/flow-to-typescript-codemod)"
  - condition: "User wants to migrate CommonJS to ESM modules (not adding types)"
    use_instead: "Node.js ESM migration guide (this is a module-system change, not a type-system migration)"
  - condition: "User is upgrading between TypeScript major versions (e.g., 5.x to 7.0)"
    use_instead: "TypeScript official release notes and migration guide"

# === AGENT HINTS ===
inputs_needed:
  - key: codebase_size
    question: "How large is the JavaScript codebase?"
    type: choice
    options: ["Small (<5K LOC)", "Medium (5K-50K LOC)", "Large (>50K LOC)"]
  - key: runtime_environment
    question: "What is the target runtime?"
    type: choice
    options: ["Node.js", "Browser (bundled)", "Both (isomorphic)", "Deno/Bun"]
  - key: typescript_version
    question: "Which TypeScript version will you target?"
    type: choice
    options: ["TypeScript 5.x (current stable)", "TypeScript 6.x (latest JS-based)", "TypeScript 7.0 (Go-based, strict-by-default)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/javascript-to-typescript/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/migrations/jquery-to-react/2026"
      label: "jQuery to React Migration"
    - id: "software/migrations/webpack-to-vite/2026"
      label: "Webpack to Vite Migration"
    - id: "software/migrations/redux-to-zustand/2026"
      label: "Redux to Zustand Migration"
  solves:
    - id: "software/migrations/react-classes-to-hooks/2026"
      label: "React Class Components to Hooks"
  alternative_to:
    - id: "software/migrations/jquery-to-vanilla-js/2026"
      label: "jQuery to Vanilla JS (typed via JSDoc + checkJs)"
  often_confused_with:
    - id: "software/migrations/momentjs-to-datefns/2026"
      label: "Moment.js to date-fns (library replacement, not type system migration)"

# === SOURCES (11 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Migrating from JavaScript"
    author: TypeScript
    url: https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "A simple guide for migrating from JavaScript to TypeScript"
    author: LogRocket
    url: https://blog.logrocket.com/a-simple-guide-for-migrating-from-javascript-to-typescript/
    type: technical_blog
    published: 2024-06-15
    reliability: high
  - id: src3
    title: "Incremental Migration from JavaScript to TypeScript in Our Largest Service"
    author: Mixmax
    url: https://www.mixmax.com/engineering/incremental-migration-from-javascript-to-typescript-in-our-largest-service
    type: technical_blog
    published: 2022-12-01
    reliability: high
  - id: src4
    title: "Progressively Adopting TypeScript in an Application"
    author: SitePen
    url: https://www.sitepen.com/blog/progressively-adopting-typescript-in-an-application
    type: technical_blog
    published: 2023-05-10
    reliability: high
  - id: src5
    title: "ts-migrate: A tool to help migrate JavaScript code to TypeScript"
    author: Airbnb
    url: https://github.com/airbnb/ts-migrate
    type: community_resource
    published: 2023-01-01
    reliability: moderate_high
  - id: src6
    title: "Understanding TypeScript's Strict Compiler Option"
    author: Better Stack
    url: https://betterstack.com/community/guides/scaling-nodejs/typescript-strict-option/
    type: technical_blog
    published: 2024-03-15
    reliability: high
  - id: src7
    title: "TSConfig Reference"
    author: TypeScript
    url: https://www.typescriptlang.org/tsconfig/
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src8
    title: "Migrating millions of lines of code to TypeScript"
    author: Stripe
    url: https://stripe.com/blog/migrating-to-typescript
    type: technical_blog
    published: 2025-03-06
    reliability: high
  - id: src9
    title: "Progress on TypeScript 7 — December 2025"
    author: TypeScript Team (Microsoft)
    url: https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/
    type: official_docs
    published: 2025-12-15
    reliability: authoritative
  - id: src10
    title: "Announcing TypeScript 6.0"
    author: TypeScript Team (Microsoft)
    url: https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/
    type: official_docs
    published: 2026-03-23
    reliability: authoritative
  - id: src11
    title: "TypeScript 6.0 Migration Guide (Before the Go Compiler Drops)"
    author: Anurag Nandi
    url: https://www.anuragnandi.com/blog/typescript-6-migration-guide-2026
    type: technical_blog
    published: 2026-04-04
    reliability: high
---

# How to Migrate a JavaScript Project to TypeScript

## How do I migrate a JavaScript project to TypeScript?

## TL;DR

- **Bottom line**: Migrate incrementally — add `tsconfig.json` with `allowJs: true`, rename `.js` files to `.ts` one at a time starting with leaf modules, add type annotations, then progressively enable strict mode flags. With TypeScript 6.0 (Mar 23, 2026) strict is now the default, so new migrations should target full strict compliance to be forward-compatible with the upcoming 7.0 Go-based compiler.
- **Key tool/command**: `npx tsc --init` to generate `tsconfig.json`, then set `"allowJs": true, "strict": false` to begin. On Node.js 22.18+, you can run `.ts` files natively without a build step.
- **Watch out for**: Enabling `strict: true` on day one — it generates hundreds of errors on any non-trivial codebase and stalls the migration. Also avoid the unmaintained `ts-migrate` tool; prefer AI-assisted migration or manual incremental approach.
- **Works with**: TypeScript 5.x and 6.0 (current stable, Mar 2026); 7.0 preview available via `@typescript/native-preview`. Node.js 18+ (native TS support in 22.18+), any bundler (Webpack, Vite, esbuild, Rollup), React/Vue/Angular/Express.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- TypeScript 6.0 (released Mar 23, 2026 — the last JavaScript-based compiler) enables `strict: true` by default. Projects migrating now should aim for full strict compliance to be forward-compatible with the upcoming Go-based 7.0. [src9, src10]
- Node.js 22.18+ supports native TypeScript execution via type stripping — `tsc` is still required for type checking, but not for running code. Do not confuse runtime support with compile-time safety.
- Never enable `strict: true` on a large legacy codebase in a single step — enable individual strict flags incrementally (`noImplicitAny` first, then `strictNullChecks`, etc.) to keep error counts manageable. [src6]
- Do not mix module systems — use `"module": "nodenext"` (TS 6.0 default) or `"Node16"`, matching the `"type"` field in `package.json`. `moduleResolution: "node"` is deprecated in 6.0 — use `"nodenext"` or `"bundler"` instead. [src7, src11]
- Airbnb's `ts-migrate` is unmaintained (last commit 2023) and generates excessive `@ts-expect-error` comments — for large codebases (>50K LOC), prefer AI-assisted migration tools or Stripe's codemod approach. [src5, src8]

## Quick Reference

| JavaScript Pattern | TypeScript Equivalent | Example |
|---|---|---|
| `function add(a, b)` | Typed parameters | `function add(a: number, b: number): number` |
| `const obj = {}` | Interface / type alias | `const obj: User = { name: 'Alice', age: 30 }` |
| `module.exports = fn` | ESM export | `export default fn` or `export { fn }` |
| `require('pkg')` | ESM import | `import pkg from 'pkg'` |
| `/** @param {string} name */` | JSDoc type annotation (bridge) | Works in `.js` files with `checkJs: true` [src4] |
| `// @ts-ignore` | Suppress single line | Use `// @ts-expect-error` instead (errors if no issue exists) [src1] |
| `// @ts-nocheck` | Suppress entire file | Place at top of file to skip type checking temporarily |
| `.js` extension | `.ts` extension | Rename file, fix type errors, commit |
| `.jsx` extension | `.tsx` extension | Rename, add prop types via `interface Props {}` |
| `any` (implicit) | Explicit types or `unknown` | `unknown` forces type narrowing; `any` disables checking |
| `Object.keys(obj).forEach(...)` | Type-safe iteration | `(Object.keys(obj) as Array<keyof typeof obj>).forEach(...)` |
| Dynamic property access `obj[key]` | Index signature | `Record<string, unknown>` or `{ [key: string]: Value }` |
| `callback(err, result)` | Typed callbacks / generics | `callback: (err: Error \| null, result: T) => void` |
| `Promise.all([...])` | Typed tuple inference | `const [a, b] = await Promise.all([fetchA(), fetchB()])` auto-infers |
| Third-party library without types | Install `@types/` or declare module | `npm i -D @types/lodash` or create `types/lodash.d.ts` [src2] |

## Decision Tree

```
START
├── Is this a greenfield project or existing JS codebase?
│   ├── GREENFIELD → Start with TypeScript from scratch: `npm create vite@latest -- --template ts`
│   └── EXISTING JS CODEBASE ↓
├── How large is the codebase?
│   ├── < 5,000 LOC → Manual migration: rename files, add types, enable strict
│   ├── 5,000-50,000 LOC → Incremental migration with allowJs: true (this guide)
│   └── > 50,000 LOC → Use AI-assisted tools (Cline + Gemini/Claude) or codemod scripts for bulk conversion [src8]
├── Does the project use a bundler (Webpack, Vite, esbuild)?
│   ├── YES → Add ts-loader or @babel/preset-typescript to existing config
│   └── NO (plain Node.js) → Use `tsc` directly, or `tsx` for dev, or Node.js 22.18+ native TS
├── Does the project have JSDoc annotations?
│   ├── YES → Enable checkJs: true first — get type checking without renaming files [src4]
│   └── NO ↓
├── Which TypeScript version?
│   ├── TS 6.0 (current) → strict is default; plan for full compliance from start [src10]
│   ├── TS 7.0 (preview) → Go-based compiler; install via @typescript/native-preview to validate [src9]
│   └── TS 5.x → Start with strict: false, tighten incrementally
└── DEFAULT → Set up tsconfig with allowJs, rename leaf modules first, work inward
```

## Step-by-Step Guide

### 1. Install TypeScript and generate tsconfig.json

Install TypeScript as a dev dependency and create the configuration file. This does not change any runtime behavior. [src1]

```bash
npm install --save-dev typescript
npx tsc --init
```

Edit `tsconfig.json` for incremental migration:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "lib": ["ES2022"],
    "allowJs": true,
    "checkJs": false,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

**Note**: TypeScript 5.9+ generates a more minimal `tsc --init` output with modern defaults (`"module": "nodenext"`, `"target": "esnext"`). Adjust as needed for your Node.js version.

**Verify**: `npx tsc --noEmit` exits with code 0 (no errors on existing JS files).

### 2. Install type definitions for dependencies

Most popular npm packages have community-maintained type definitions in the `@types` namespace. Install them as dev dependencies. [src2]

```bash
# Install types for common packages
npm install --save-dev @types/node @types/express @types/jest

# Check if a package ships its own types (look for "types" in package.json)
npm info express | grep types

# Search for available @types packages
npx typesync  # auto-installs missing @types
```

**Verify**: `npx tsc --noEmit` — no "Could not find a declaration file for module" errors for typed packages.

### 3. Rename leaf modules first (`.js` to `.ts`)

Start with files that have no local imports — utility functions, constants, config files. These have the smallest blast radius. [src3]

```bash
# Find leaf modules (files that don't import other local files)
# On Unix/macOS:
grep -rL "from '\./\|require('\./\|from \"\./\|require(\"\./" src/ --include="*.js"

# Rename a file
mv src/utils/format.js src/utils/format.ts

# Fix type errors in the renamed file
npx tsc --noEmit
```

**Verify**: `npx tsc --noEmit` compiles. All tests pass: `npm test`.

### 4. Add type annotations to converted files

Add types to function parameters, return values, and variables where TypeScript cannot infer them. Start with function signatures — they provide the most value. [src1, src2]

```typescript
// BEFORE: JavaScript (src/utils/format.js)
function formatPrice(amount, currency) {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: currency,
  }).format(amount);
}

// AFTER: TypeScript (src/utils/format.ts)
function formatPrice(amount: number, currency: string): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency,
  }).format(amount);
}
```

**Verify**: `npx tsc --noEmit` — zero errors. Hover over function calls in your editor to confirm types are inferred.

### 5. Create declaration files for untyped dependencies

For third-party packages without `@types`, create a `.d.ts` declaration file. This unblocks migration without modifying the package. [src3, src4]

```typescript
// types/untyped-package.d.ts
declare module 'untyped-package' {
  export function doSomething(input: string): Promise<Result>;
  export interface Result {
    success: boolean;
    data: unknown;
  }
}
```

Add the types directory to `tsconfig.json`:

```json
{
  "compilerOptions": {
    "typeRoots": ["./types", "./node_modules/@types"]
  }
}
```

**Verify**: `npx tsc --noEmit` — no "Could not find declaration" errors for the package.

### 6. Enable strict mode flags incrementally

Do not flip `strict: true` all at once. Enable individual flags one by one, fix errors for each flag, then move to the next. [src6]

```json
{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": false
  }
}
```

Recommended order (from easiest to hardest):
1. `noImplicitAny` — forces explicit types on parameters
2. `strictNullChecks` — distinguishes `null`/`undefined` from other types
3. `strictFunctionTypes` — enforces correct function parameter variance
4. `strictBindCallApply` — checks `.bind()`, `.call()`, `.apply()` arguments
5. `noImplicitThis` — requires explicit `this` typing
6. `strictPropertyInitialization` — enforces class property initialization
7. `alwaysStrict` — emits `"use strict"` in output
8. Replace all individual flags with `"strict": true`

**Note**: TypeScript 6.0 (Mar 2026) enables strict by default. You may instead start with `"strict": true` and selectively disable individual flags while fixing errors: `"strictNullChecks": false` etc. The same applies to TS 7.0 (Go-based, preview).

**Verify**: After each flag, `npx tsc --noEmit` passes with zero errors.

### 7. Update build tooling and CI

Integrate TypeScript into your existing build pipeline and add type checking to CI. [src3]

```json
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "tsc",
    "dev": "tsx watch src/index.ts",
    "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
    "ci": "npm run typecheck && npm run lint && npm test"
  }
}
```

For Webpack, add `ts-loader`:

```javascript
// webpack.config.js
module.exports = {
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
};
```

For Vite (no additional config needed — Vite supports TypeScript out of the box):

```typescript
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  // TypeScript works automatically with Vite
});
```

For Node.js 22.18+ (native TypeScript, no build step for runtime):

```bash
# Run TypeScript directly (type annotations stripped at runtime)
node src/index.ts

# Still type-check separately
npx tsc --noEmit
```

**Verify**: `npm run build` succeeds. CI pipeline passes with type checking enabled.

## Code Examples

### Node.js/Express: Converting a route handler from JS to TS

> Full script: [node-js-express-converting-a-route-handler-from-js.ts](scripts/node-js-express-converting-a-route-handler-from-js.ts) (44 lines)

```typescript
// Input:  A JavaScript Express route with untyped request/response
// Output: Fully typed TypeScript Express route with error handling
import { Router, Request, Response, NextFunction } from 'express';
// Define the shape of your data
interface User {
# ... (see full script)
```

### React: Converting a component from JSX to TSX

> Full script: [react-converting-a-component-from-jsx-to-tsx.ts](scripts/react-converting-a-component-from-jsx-to-tsx.ts) (41 lines)

```typescript
// Input:  A React component in .jsx with prop-types
// Output: Same component in .tsx with TypeScript interfaces
import { useState, useEffect } from 'react';
// BEFORE (JavaScript with PropTypes):
// import PropTypes from 'prop-types';
# ... (see full script)
```

### Configuration: tsconfig.json progression from permissive to strict

> Full script: [configuration-tsconfig-json-progression-from-permi.txt](scripts/configuration-tsconfig-json-progression-from-permi.txt) (37 lines)

```jsonc
// Input:  A project starting migration (permissive)
// Output: Three tsconfig stages showing progressive strictness
// === STAGE 1: Start migration (allowJs, no strict) ===
{
  "compilerOptions": {
# ... (see full script)
```

## Anti-Patterns

### Wrong: Enabling strict: true on day one

```typescript
// ❌ BAD — Generates hundreds of errors, stalls migration
// tsconfig.json
{
  "compilerOptions": {
    "strict": true,        // 500+ errors on a 10k LOC codebase
    "allowJs": true
  }
}
// Result: team gives up after fighting 200 type errors in the first hour
```

### Correct: Start permissive, tighten incrementally

```typescript
// ✅ GOOD — Zero errors on day one, add strictness over weeks
// tsconfig.json
{
  "compilerOptions": {
    "strict": false,       // start permissive
    "allowJs": true,       // mix JS and TS
    "noImplicitAny": false // enable later
  }
}
// Week 2: enable noImplicitAny
// Week 4: enable strictNullChecks
// Week 8: enable strict: true
```

### Wrong: Using `any` everywhere to silence errors

```typescript
// ❌ BAD — Defeats the purpose of TypeScript
function processData(data: any): any {
  return data.map((item: any) => ({
    name: item.name as any,
    value: item.count as any,
  }));
}
```

### Correct: Use `unknown` and type narrowing

```typescript
// ✅ GOOD — Forces you to validate types at runtime
function processData(data: unknown): ProcessedItem[] {
  if (!Array.isArray(data)) {
    throw new TypeError('Expected an array');
  }
  return data.map((item: unknown) => {
    if (typeof item !== 'object' || item === null) {
      throw new TypeError('Expected an object');
    }
    const obj = item as Record<string, unknown>;
    return {
      name: String(obj.name ?? ''),
      value: Number(obj.count ?? 0),
    };
  });
}

interface ProcessedItem {
  name: string;
  value: number;
}
```

### Wrong: Renaming all files at once

```bash
# ❌ BAD — Renames everything, creates hundreds of errors simultaneously
find src -name "*.js" -exec sh -c 'mv "$1" "${1%.js}.ts"' _ {} \;
npx tsc --noEmit  # 847 errors
```

### Correct: Rename one file at a time, fix, commit

```bash
# ✅ GOOD — One file per commit, zero errors at every step
mv src/utils/validators.js src/utils/validators.ts
npx tsc --noEmit   # fix the 3-5 errors in this file
git add -A && git commit -m "chore: convert validators.js to TypeScript"
# repeat for next file
```

### Wrong: Creating a separate TypeScript directory

```
# ❌ BAD — Duplicates code, configs diverge, imports break
project/
├── src/           # old JavaScript
├── src-ts/        # new TypeScript copies
└── tsconfig.json
```

### Correct: Convert in-place with allowJs

```
# ✅ GOOD — Single source tree, JS and TS coexist
project/
├── src/
│   ├── index.ts        # already converted
│   ├── api.js          # not yet converted
│   ├── utils/
│   │   ├── format.ts   # converted
│   │   └── parse.js    # not yet converted
│   └── types/
│       └── index.d.ts  # shared type definitions
└── tsconfig.json       # allowJs: true
```

### Wrong: Ignoring third-party type definitions

```typescript
// ❌ BAD — Importing untyped module gives implicit any
import csv from 'csv-parser';  // no @types/csv-parser exists
// csv is `any`, no autocomplete, no type checking
const results = csv();  // no error even if wrong
```

### Correct: Create a declaration file for untyped packages

```typescript
// ✅ GOOD — types/csv-parser.d.ts
declare module 'csv-parser' {
  import { Transform } from 'stream';

  interface CsvParserOptions {
    separator?: string;
    headers?: string[] | boolean;
    skipLines?: number;
  }

  function csvParser(options?: CsvParserOptions): Transform;
  export default csvParser;
}
```

### Wrong: Using ts-migrate on a new project in 2026

```bash
# ❌ BAD — ts-migrate is unmaintained, generates excessive @ts-expect-error
npx ts-migrate full src/
# Result: compiles, but every file has @ts-expect-error and any types
# No one cleans them up, project has "TypeScript" but zero type safety
```

### Correct: Use AI-assisted migration or manual incremental approach

```bash
# ✅ GOOD — AI tools (Cline, Cursor, Copilot) convert with real types
# Or for large codebases, write custom codemods like Stripe did [src8]
# 1. Convert one module at a time with AI assistance
# 2. Review generated types for correctness
# 3. Run tsc --noEmit to verify
# 4. Commit and move to next module
```

## Common Pitfalls

- **Implicit `any` in event handlers**: TypeScript cannot infer the type of `event` in inline callbacks. Fix: explicitly type the event — `(e: React.ChangeEvent<HTMLInputElement>) => ...` or `(e: MouseEvent) => ...`. [src2]
- **Module resolution mismatch**: Using `"module": "commonjs"` in tsconfig but `"type": "module"` in package.json causes conflicting module systems. Fix: use `"module": "Node16"` and `"moduleResolution": "Node16"` which respects package.json `type` field. [src7]
- **Missing `@types` for test frameworks**: Jest, Mocha, and Chai need separate type packages. Fix: `npm i -D @types/jest` or `@types/mocha @types/chai`. Without them, `describe`, `it`, `expect` are unresolved. [src2]
- **`Object.keys()` returns `string[]`, not keyof T**: TypeScript deliberately widens the return type because objects can have extra properties at runtime. Fix: cast explicitly `(Object.keys(obj) as Array<keyof typeof obj>)` or use a type-safe helper. [src6]
- **Enum values not assignable to string**: TypeScript enums create nominal types, not plain strings. Fix: use `as const` objects instead — `const Status = { Active: 'active', Inactive: 'inactive' } as const`. [src1]
- **Default exports vs named exports confusion**: `module.exports = fn` maps to `export default fn`, but `exports.fn = fn` maps to `export { fn }`. Mixing them up causes "has no default export" errors. Fix: use `esModuleInterop: true` and be consistent. [src7]
- **`tsconfig.json` not including all source files**: Files outside the `include` glob are silently ignored by the compiler. Fix: verify with `npx tsc --listFiles | grep yourfile` to confirm files are included. [src4]
- **`skipLibCheck: true` hiding real errors**: This skips type checking of `.d.ts` files, including your own declaration files. It speeds up compilation but can mask type bugs. Fix: periodically run with `skipLibCheck: false` to catch issues. [src7]
- **Node.js native TS gives false confidence**: Running `.ts` files with `node --experimental-strip-types` (or Node 22.18+) strips types at runtime but does not type-check. You still need `tsc --noEmit` in CI to catch type errors.
- **TypeScript 6.0 strict default surprise**: Projects that relied on `strict: false` as default will see new errors when upgrading to TS 6.0 (Mar 2026) or 7.0. Fix: explicitly set `"strict": false` in tsconfig if you are not ready for full strict mode. [src10]

## Diagnostic Commands

```bash
# Check for type errors without emitting files
npx tsc --noEmit

# List all files included in the TypeScript compilation
npx tsc --listFiles

# Show the effective tsconfig.json (with all defaults)
npx tsc --showConfig

# Count remaining .js files (migration progress)
find src -name "*.js" | wc -l
find src -name "*.ts" -o -name "*.tsx" | wc -l

# Find files with @ts-ignore or @ts-expect-error (tech debt)
grep -rn "@ts-ignore\|@ts-expect-error" src/ --include="*.ts" --include="*.tsx"

# Check TypeScript version
npx tsc --version

# Dry-run strict mode to count errors before enabling
npx tsc --noEmit --strict 2>&1 | grep "error TS" | wc -l

# Run type checking in watch mode during development
npx tsc --noEmit --watch

# Bulk-rename JS to TS (use only after verifying with a single file)
# for f in src/**/*.js; do mv "$f" "${f%.js}.ts"; done

# Test native Node.js TypeScript execution (Node 22.18+)
node --version  # must be >= 22.18.0
node src/index.ts  # runs with type stripping, no build step

# Compare TS 5.x vs 7.0 compilation speed
time npx tsc --noEmit  # TS 5.x/6.x
time npx tsgo --noEmit  # TS 7.0 (Go-based, ~10x faster)
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| TypeScript 7.0 (Project Corsa, preview) | Preview | Go-based compiler (`tsgo`), ~10x faster builds, parallel project processing | Install via `npm i -D @typescript/native-preview`; validate compatibility before stable release [src9] |
| TypeScript 6.0 (Mar 23, 2026) | Current / final JS-based | `strict: true` default; `module`/`target` default to `esnext`/ES2025; `moduleResolution: node` deprecated; `esModuleInterop` can no longer be disabled; auto-include of `@types/*` removed | Bridge to 7.0; set `target`, `module`, `moduleResolution` explicitly; declare needed `@types`; run `tsc --noEmit` to surface errors [src10, src11] |
| TypeScript 5.9 (Aug 2025) | Supported | `import defer` syntax (stage-3), `node20` module resolution, minimalistic `tsc --init` defaults | Safe upgrade from 5.8; improved default config |
| TypeScript 5.8 (Mar 2025) | Supported | `--erasableSyntaxOnly` flag; `require()` of ESM in `nodenext` | Safe to migrate to; no breaking changes from 5.7 |
| TypeScript 5.7 (Nov 2024) | Supported | Better uninitialized variable detection | May surface new errors in existing code |
| TypeScript 5.5 (Jun 2024) | Supported | Inferred type predicates | Improves type narrowing automatically |
| TypeScript 5.0 (Mar 2023) | Maintenance | `--moduleResolution bundler`; decorators stage 3 | Use `Node16` resolution for Node.js projects |
| TypeScript 4.x (2020-2022) | EOL | `--strict` expanded with new flags per release | Upgrade to 5.x; minimal breaking changes |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Codebase is >1,000 LOC and growing | One-off script or prototype <100 LOC | Plain JavaScript |
| Team has >2 developers working on same codebase | Solo project with no collaborators and no plans to grow | JavaScript with JSDoc annotations |
| Project has complex data structures or APIs | Simple static site with minimal JS | Vanilla JavaScript |
| You want editor autocomplete and refactoring tools | You need zero build step (e.g., Cloudflare Workers with JS) | JSDoc + checkJs in tsconfig |
| Catching bugs before runtime is a priority | Performance-critical hot path where tsc overhead matters | JavaScript + runtime validation (Zod, io-ts) |
| Onboarding new developers faster with self-documenting types | Team is unfamiliar with TypeScript and no time to learn | JavaScript with ESLint strict rules |
| Node.js 22.18+ project — native TS support means zero build overhead | Deno project — Deno has native TS, no migration needed | Deno's built-in TypeScript |

## Important Caveats

- TypeScript 6.0 (released Mar 23, 2026 — the last JavaScript-based compiler) enables `strict: true` by default. TypeScript 7.0 (Project Corsa, Go-based) is in preview as of May 2026 via `npm i -D @typescript/native-preview` — it delivers ~10x faster builds but has not shipped a stable release yet. Projects migrating now should target full strict compliance to avoid breakage on the 7.0 upgrade. [src9, src10]
- `allowJs: true` adds compilation overhead — TypeScript must parse and check both JS and TS files. On very large codebases (>100k LOC), consider using project references to split compilation.
- The `tsconfig.json` settings `"module": "Node16"` and `"moduleResolution": "Node16"` are the correct settings for modern Node.js. Do not use the older `"commonjs"` + `"node"` combination for new migrations. TypeScript 5.9+ defaults to `"nodenext"` which is also acceptable. [src7]
- `skipLibCheck: true` is recommended during migration for speed, but should be disabled periodically to catch declaration file bugs.
- Airbnb's `ts-migrate` tool is no longer maintained (last commit 2023). It generates compilable TypeScript with many `@ts-expect-error` comments and `any` types — it is a starting point, not a finished migration. Consider AI-assisted migration tools for better type quality. [src5]
- TypeScript does not perform runtime type checking. If you need validation of external data (API responses, user input), pair TypeScript with a runtime validation library like Zod or io-ts.
- Node.js native TypeScript support (22.18+) strips types at runtime but does not type-check. Always run `tsc --noEmit` in CI to catch type errors — do not rely solely on Node.js execution for correctness.
- Stripe successfully migrated 3.7 million lines from Flow to TypeScript in a single PR using custom codemods. For very large codebases, consider a similar all-at-once approach with automated tooling rather than incremental file-by-file migration. [src8]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [jQuery to React Migration](/software/migrations/jquery-to-react/2026)
- [Webpack to Vite Migration](/software/migrations/webpack-to-vite/2026)
- [Redux to Zustand Migration](/software/migrations/redux-to-zustand/2026)
- [React Class Components to Hooks](/software/migrations/react-classes-to-hooks/2026)
- [jQuery to Vanilla JS](/software/migrations/jquery-to-vanilla-js/2026)
