TypeScript Migration
> My team wants to adopt TypeScript but half the codebase is JavaScript. Where do we start?
Opus Default gave a solid, practical playbook: set up tsconfig, write all new files in TypeScript, rename the easy JavaScript files first, add types at your API boundaries, then gradually tighten the strictness settings. It’s good advice and most teams could follow it.
What Opus Default skipped was checking whether the team is actually ready to start. Opus Deepthink opened with three questions first: Is the JavaScript code modern enough to type cleanly? Does the existing code have test coverage? Has the team written TypeScript before? Those answers change everything — a codebase with no tests and an inexperienced team needs a very different approach than a well-tested codebase with a seasoned team.
Opus Deepthink also caught two things Opus Default missed entirely. The common advice to start with small utility files turns out to be backwards — those files almost never cause bugs. The real wins come from converting the files your team touches most often, which is easy to find in your git history. And migration regularly stalls not because of technical problems but because no one negotiated dedicated time for it — without that, developers get pulled between shipping features and doing conversion work, and the migration quietly dies.
“Start with utility/leaf files”
Git history analysis
Inverted: target most-frequently-modified files with test coverage, not stable leaf nodes
“Start strict: false, upgrade later”
Behavioral observation
Rejected: teams that start with strict: false never turn it on. Start strict: true for new files.
“Migration is a technical project”
Organizational analysis
Reframed: without explicit capacity negotiation, it becomes an unfunded mandate that damages morale
“We should start now”
Precondition audit
Challenged: if test coverage <50%, team under pressure, no TS experience — delay is the responsible call
Start by adding a tsconfig.json with allowJs: true and strict: false. This lets your existing JavaScript files compile without changes while new files can be written in TypeScript. Point include at your source directories and set outDir if you’re compiling.
The single highest-leverage policy: all new files are .ts. No exceptions. This stops the JS surface area from growing while you chip away at the existing code. It requires zero refactoring and gets the team writing TypeScript immediately.
Many JavaScript files will pass TypeScript checks with minimal changes. Rename .js to .ts, run the compiler, and fix what breaks. Prioritize utility/helper modules, config files, and constants. Avoid starting with your most complex files — API routes with lots of middleware, complex state management. Those come later when the team has more confidence.
Before converting internal modules, add type definitions at your system boundaries: API request/response shapes, database query results, and third-party library interfaces. This gives you the most safety per line of type code written, because boundary errors are where the worst runtime bugs hide.
Once a meaningful portion of the codebase is .ts, start enabling strict checks one at a time: noImplicitAny first, then strictNullChecks (this is where the real value is), then strictFunctionTypes, then strict: true once the team is comfortable. You can use per-file @ts-strict-ignore comments to enforce strictness only on already-converted files.
Don’t do a big-bang migration. Don’t start with strict: true — you’ll drown in errors and the team will associate TypeScript with pain rather than value. Don’t write any everywhere to make errors go away. That gives you the syntax overhead of TypeScript with none of the safety. A file with a few TODO: type this properly comments is better than a file that’s still JavaScript.
Done · 18 sec
Is the JS modern or legacy? Does it have test coverage? Does the team know TypeScript? Default skipped these entirely and went straight to a generic playbook.
Leaf modules are stable and rarely cause bugs. The high-value targets are the most-frequently-modified files with adequate test coverage — use git history, not file complexity, to prioritize.
Default recommended incremental strictness. Deepthink recognized that teams who start with strict: false never turn it on — strict: true for new code is the only viable path.
Sprint velocity drops 20–40% during active migration. If stakeholders aren’t told in advance, they question the investment. Morale damage is the most dangerous failure mode.
When the champions leave or burn out, the migration freezes permanently. Conversion must be distributed across the team through pairing.
If test coverage is below 50%, the team is under delivery pressure, and no one has authority to defend migration time, starting now will fail. Write new code in TS, invest in training, revisit in 6 months.
Before choosing a migration strategy, your team needs to answer three questions. Is your JavaScript modern? If it relies on prototype chains, arguments objects, or dynamic property injection, the code may be genuinely difficult to type without refactoring first — consider rewriting the worst modules rather than annotating them. Does the JavaScript have test coverage? Converting a JS file to TS without tests is dangerous — you will type functions based on what you think they do, not what they actually do. Never convert a file with less than 60% test coverage. How many developers have written TypeScript before? If fewer than half, the bottleneck is education, not migration.
Validate your build pipeline before converting a single file. Create one .ts file, import it from an existing JS file, and verify that your dev server, bundler, test runner, and deployment pipeline all handle it correctly. This takes 2–4 hours and prevents days of tooling frustration. If your build system predates modern bundlers (custom Gulp/Babel pipelines, Makefiles), budget days, not hours, for this step.
Set strict: true in tsconfig for all new .ts files. Do not start with strict: false intending to turn it on later. You won’t. Add a CI check that fails if any new .js files are committed. This stops the JS codebase from growing and builds team competence through daily practice on low-stakes new code rather than high-stakes conversion of existing code.
The standard advice to start with leaf nodes and utility functions maximizes effort while minimizing value. Leaf modules are stable and rarely cause bugs. Instead, use git history to find the most-frequently-modified files, then filter for those with adequate test coverage. The resulting list, filtered to files with >60% test coverage, is your conversion priority queue. These files change often (so types prevent the most bugs) and have tests (so you can verify your type annotations are correct).
Without a negotiated agreement — 20% of sprint capacity goes to migration for the next quarter — conversion becomes an unfunded mandate. Developers feel caught between pressure to convert files and pressure to ship features. Sprint velocity will drop 20–40% during active migration periods. If stakeholders aren’t told this in advance, they’ll question the investment. Morale damage is the most dangerous failure mode because it persists after the migration ends and poisons future technical initiatives.
Don’t let one person do all the conversion work. Champion burnout is a primary failure mode. When the 1–2 enthusiasts leave or burn out, the migration freezes permanently. Don’t convert files without tests — you will type them incorrectly and create false confidence. The compiler will say everything is fine. Production will disagree.
For some teams, the right answer to ‘where do we start?’ is ‘not yet.’ If test coverage is below 50%, the team is under delivery pressure this quarter, most developers have no TypeScript experience, and no one with authority is willing to defend migration time to stakeholders, starting now will fail. Write new code in TypeScript, invest in training through new features, add tests to the JS code, and revisit conversion in 6 months when the preconditions are met. That delay is not failure; it is the responsible engineering decision.
7 constraints resolved · 3m 48s
Is the JS modern or legacy? Does it have test coverage? Does the team know TypeScript? Default skipped these entirely and went straight to a generic playbook.
Leaf modules are stable and rarely cause bugs. The high-value targets are the most-frequently-modified files with adequate test coverage — use git history, not file complexity, to prioritize.
Default recommended incremental strictness. Deepthink recognized that teams who start with strict: false never turn it on — strict: true for new code is the only viable path.
Sprint velocity drops 20–40% during active migration. If stakeholders aren’t told in advance, they question the investment. Morale damage is the most dangerous failure mode.
When the champions leave or burn out, the migration freezes permanently. Conversion must be distributed across the team through pairing.
If test coverage is below 50%, the team is under delivery pressure, and no one has authority to defend migration time, starting now will fail. Write new code in TS, invest in training, revisit in 6 months.
Before choosing a migration strategy, your team needs to answer three questions. Is your JavaScript modern? If it relies on prototype chains, arguments objects, or dynamic property injection, the code may be genuinely difficult to type without refactoring first — consider rewriting the worst modules rather than annotating them. Does the JavaScript have test coverage? Converting a JS file to TS without tests is dangerous — you will type functions based on what you think they do, not what they actually do. Never convert a file with less than 60% test coverage. How many developers have written TypeScript before? If fewer than half, the bottleneck is education, not migration.
Validate your build pipeline before converting a single file. Create one .ts file, import it from an existing JS file, and verify that your dev server, bundler, test runner, and deployment pipeline all handle it correctly. This takes 2–4 hours and prevents days of tooling frustration. If your build system predates modern bundlers (custom Gulp/Babel pipelines, Makefiles), budget days, not hours, for this step.
Set strict: true in tsconfig for all new .ts files. Do not start with strict: false intending to turn it on later. You won’t. Add a CI check that fails if any new .js files are committed. This stops the JS codebase from growing and builds team competence through daily practice on low-stakes new code rather than high-stakes conversion of existing code.
The standard advice to start with leaf nodes and utility functions maximizes effort while minimizing value. Leaf modules are stable and rarely cause bugs. Instead, use git history to find the most-frequently-modified files, then filter for those with adequate test coverage. The resulting list, filtered to files with >60% test coverage, is your conversion priority queue. These files change often (so types prevent the most bugs) and have tests (so you can verify your type annotations are correct).
Without a negotiated agreement — 20% of sprint capacity goes to migration for the next quarter — conversion becomes an unfunded mandate. Developers feel caught between pressure to convert files and pressure to ship features. Sprint velocity will drop 20–40% during active migration periods. If stakeholders aren’t told this in advance, they’ll question the investment. Morale damage is the most dangerous failure mode because it persists after the migration ends and poisons future technical initiatives.
Don’t let one person do all the conversion work. Champion burnout is a primary failure mode. When the 1–2 enthusiasts leave or burn out, the migration freezes permanently. Don’t convert files without tests — you will type them incorrectly and create false confidence. The compiler will say everything is fine. Production will disagree.
For some teams, the right answer to ‘where do we start?’ is ‘not yet.’ If test coverage is below 50%, the team is under delivery pressure this quarter, most developers have no TypeScript experience, and no one with authority is willing to defend migration time to stakeholders, starting now will fail. Write new code in TypeScript, invest in training through new features, add tests to the JS code, and revisit conversion in 6 months when the preconditions are met. That delay is not failure; it is the responsible engineering decision.
7 constraints resolved · 3m 48s
Unlock your AI’s deep reasoning potential
Simple installation for Claude Code, Claude Desktop, Codex, Gemini, Anti-Gravity, Cursor, and Windsurf.
Works with any client that supports MCPs.