Javascript to Typescript
Migrate a JavaScript codebase to TypeScript incrementally with minimal disruption
You are an expert in migrating JavaScript projects to TypeScript for improved type safety, tooling, and maintainability. ## Key Points 1. **Preparation** — audit the codebase, identify entry points and dependency graph. 2. **Bootstrap TypeScript** — add `tsconfig.json` with permissive settings alongside existing JS tooling. 3. **Incremental Rename** — convert files leaf-first (utilities, constants, types) then work inward toward application logic. 4. **Strict Mode Ramp** — enable strict flags one at a time (`noImplicitAny`, `strictNullChecks`, etc.) after a critical mass of files have been converted. - Convert files leaf-first (no imports from unconverted files) to minimize cascading errors. - Use `// @ts-expect-error` sparingly as a temporary bridge, and track occurrences with a lint rule. - Add a CI check that runs `tsc --noEmit` so regressions are caught immediately. - Adopt `unknown` over `any` wherever possible — it forces explicit narrowing. - Write new code exclusively in TypeScript from day one of the migration. - Use `@types/*` packages from DefinitelyTyped for third-party libraries. - **Big-bang rewrites** — converting everything at once leads to thousands of errors and stalled PRs. Go incremental. - **Overly loose `any` usage** — sprinkling `any` everywhere compiles clean but defeats the purpose. Track and reduce `any` count over time. ## Quick Example ```bash npm install --save-dev typescript @types/node npx tsc --init ``` ```bash # Rename a utility file mv src/utils/format.js src/utils/format.ts ```
skilldb get migration-patterns-skills/Javascript to TypescriptFull skill: 138 linesJavaScript to TypeScript — Migration Patterns
You are an expert in migrating JavaScript projects to TypeScript for improved type safety, tooling, and maintainability.
Core Philosophy
Overview
TypeScript adoption does not require a big-bang rewrite. The recommended approach is an incremental migration: rename files one at a time from .js to .ts/.tsx, fix type errors as they surface, and progressively tighten compiler strictness. This keeps the project shippable at every step.
Migration Strategy
- Preparation — audit the codebase, identify entry points and dependency graph.
- Bootstrap TypeScript — add
tsconfig.jsonwith permissive settings alongside existing JS tooling. - Incremental Rename — convert files leaf-first (utilities, constants, types) then work inward toward application logic.
- Strict Mode Ramp — enable strict flags one at a time (
noImplicitAny,strictNullChecks, etc.) after a critical mass of files have been converted.
Step-by-Step Guide
1. Install TypeScript and initial config
npm install --save-dev typescript @types/node
npx tsc --init
2. Start with a permissive tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"allowJs": true, // key: lets .js and .ts coexist
"checkJs": false,
"outDir": "./dist",
"strict": false, // start loose, tighten later
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}
3. Rename leaf files first
# Rename a utility file
mv src/utils/format.js src/utils/format.ts
Add minimal types to fix errors:
// Before (JS)
export function formatCurrency(amount, locale) {
return new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(amount);
}
// After (TS)
export function formatCurrency(amount: number, locale: string): string {
return new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(amount);
}
4. Use declaration files for third-party JS modules without types
// src/types/legacy-lib.d.ts
declare module 'legacy-lib' {
export function doSomething(input: string): void;
}
5. Progressively enable strict flags
// Phase 1
"noImplicitAny": true,
// Phase 2
"strictNullChecks": true,
// Phase 3 — full strict
"strict": true
6. Update build tooling
For a project using ESBuild or Webpack, ensure the loader handles .ts files:
// webpack.config.js addition
module.exports = {
resolve: { extensions: ['.ts', '.tsx', '.js', '.jsx'] },
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }
]
}
};
Best Practices
- Convert files leaf-first (no imports from unconverted files) to minimize cascading errors.
- Use
// @ts-expect-errorsparingly as a temporary bridge, and track occurrences with a lint rule. - Add a CI check that runs
tsc --noEmitso regressions are caught immediately. - Adopt
unknownoveranywherever possible — it forces explicit narrowing. - Write new code exclusively in TypeScript from day one of the migration.
- Use
@types/*packages from DefinitelyTyped for third-party libraries.
Common Pitfalls
- Big-bang rewrites — converting everything at once leads to thousands of errors and stalled PRs. Go incremental.
- Overly loose
anyusage — sprinklinganyeverywhere compiles clean but defeats the purpose. Track and reduceanycount over time. - Ignoring
strictNullChecks— this single flag catches the most real bugs; do not leave it off permanently. - Forgetting test files — tests should also be converted; otherwise you lose type coverage on mocks and assertions.
- Mismatched module systems — ensure
tsconfigmodule settings align with your bundler (CommonJS vs ESM).
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add migration-patterns-skills
Related Skills
Class to Functional React
Convert React class components to functional components with hooks
Cra to Nextjs
Migrate a Create React App project to Next.js for server-side rendering and file-based routing
Jest to Vitest
Migrate a test suite from Jest to Vitest for faster execution and native ESM support
Monolith to Microservices
Decompose a monolithic application into microservices using the strangler fig pattern
REST to GRAPHQL
Migrate a REST API to GraphQL while maintaining backward compatibility
SQL to Nosql
Migrate from a relational SQL database to a NoSQL document or key-value store