{"id":"l-at-5-4","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Migrate a JavaScript codebase to TypeScript one file at a time while it keeps shipping, and keep type-checking fast as the codebase and its types grow.","html":"<p>The migration I led took a 400,000-line JavaScript checkout and order system to strict TypeScript over about eighteen months, with no feature freeze and no long-lived branch. We shipped every day of it. The type-level techniques in this course matter, but on a real codebase the <em>process</em> decides whether a migration finishes. This lesson is that process, plus how to keep type-checking fast once you are done.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Coexist first</h2>\n<p>Start by letting TypeScript and JavaScript live together:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">tsconfig.json</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"compilerOptions\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"allowJs\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"checkJs\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"noEmit\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"strict\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"module\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"esnext\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"moduleResolution\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bundler\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"target\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"es2022\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"skipLibCheck\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"include\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"src\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">allowJs</code> brings <code dir=\"ltr\">.js</code> files into the program, so <code dir=\"ltr\">.ts</code> files can import them and vice versa. <code dir=\"ltr\">noEmit</code> leaves building to the existing bundler; <code dir=\"ltr\">tsc --noEmit</code> runs in CI only as a checker. And <code dir=\"ltr\">strict</code> is on from day one, but it only applies to <code dir=\"ltr\">.ts</code> files, so new code is held to the full standard while old code is left alone. Merge this on <code dir=\"ltr\">main</code> in a single small pull request. Nothing about the app changes.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Check JavaScript before converting it</h2>\n<p>You can get real checking in a <code dir=\"ltr\">.js</code> file without renaming it. Add <code dir=\"ltr\">// @ts-check</code> at the top, and TypeScript checks the file, reading types from JSDoc comments:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/money.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// @ts-check</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/** </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@typedef</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> {'USD' | 'EUR' | 'EGP'}</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Currency</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> */</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/** </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@typedef</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> {{ cents: number; currency: Currency }}</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Money</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> */</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/**</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> * </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@param</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> {Money}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> * </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@param</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> {Money}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> * </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">@returns</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> {Money}</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\"> */</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (a.currency </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!==</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.currency) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Currency mismatch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { cents: a.cents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.cents, currency: a.currency };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>That is the same checker, with the same rules, on a plain JavaScript file. Use it on the hottest, riskiest files first. JSDoc can even import types from <code dir=\"ltr\">.ts</code> files with <code dir=\"ltr\">@import</code> or <code dir=\"ltr\">import('./types').Order</code>, so a JavaScript file can use the domain types you have already written.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Convert from the leaves inward</h2>\n<p>When you do rename files, the order matters. Draw (or generate) the import graph and start at the <strong>leaves</strong>: modules that import nothing else from your codebase, such as money helpers, formatting, constants and the domain types. Then convert the modules that depend only on converted ones, and keep working inward toward the entry points.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-at-5-4-t1\"><defs><marker id=\"arrow-l-at-5-4-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-at-5-4-t1\">Import graph of the checkout. The checkout page imports the API client and the cart store, which both import money and format utilities. The leaves, money and format, are already TypeScript; the API client and cart store are in progress; the checkout page is still JavaScript. Conversion moves from the bottom up.</title>\n  <rect class=\"d-box\" x=\"260\" y=\"16\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"350\" y=\"44\" text-anchor=\"middle\">checkoutPage.js</text>\n  <rect class=\"d-box-warn\" x=\"110\" y=\"104\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"200\" y=\"132\" text-anchor=\"middle\">apiClient.ts</text>\n  <rect class=\"d-box-warn\" x=\"410\" y=\"104\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"500\" y=\"132\" text-anchor=\"middle\">cartStore.js</text>\n  <rect class=\"d-box-success\" x=\"110\" y=\"192\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"200\" y=\"220\" text-anchor=\"middle\">money.ts</text>\n  <rect class=\"d-box-success\" x=\"410\" y=\"192\" width=\"180\" height=\"46\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"500\" y=\"220\" text-anchor=\"middle\">format.ts</text>\n  <path class=\"d-arrow\" d=\"M310 62 L230 102\" marker-end=\"url(#arrow-l-at-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M390 62 L470 102\" marker-end=\"url(#arrow-l-at-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M200 150 L200 188\" marker-end=\"url(#arrow-l-at-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M500 150 L500 188\" marker-end=\"url(#arrow-l-at-5-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M270 150 L440 190\" marker-end=\"url(#arrow-l-at-5-4-1)\"/>\n  <text class=\"d-label-muted\" x=\"640\" y=\"132\" text-anchor=\"middle\">in progress</text>\n  <text class=\"d-label-muted\" x=\"640\" y=\"220\" text-anchor=\"middle\">done first</text>\n  <text class=\"d-label-muted\" x=\"640\" y=\"44\" text-anchor=\"middle\">last</text>\n</svg><figcaption>Convert leaves first, so each new TypeScript file imports typed code</figcaption></figure>\n<p>Converting an entry point first gives you a <code dir=\"ltr\">.ts</code> file whose imports are all untyped, which means a lot of <code dir=\"ltr\">any</code> and very little checking. Converting a leaf first gives every later file real types to build on.</p>\n<p>Alongside the leaves, prioritise the <strong>boundaries</strong>: the API client, the parsers, the domain models. A typed <code dir=\"ltr\">Order</code> and a parsed API response protect every file that touches them, converted or not. That is why sections 1, 2 and 4 of this course matter more for a migration than section 3.</p>\n<p>When the last <code dir=\"ltr\">.js</code> file is gone, remove <code dir=\"ltr\">allowJs</code>. Then ratchet up the extra flags from <a href=\"/lesson/advanced-typescript/l-at-5-1\">tsconfig Strictness That Pays Off</a>, <code dir=\"ltr\">noUncheckedIndexedAccess</code> first, one at a time, suppressing existing errors with <code dir=\"ltr\">// @ts-expect-error</code> and a ticket so new code meets the bar immediately.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Converting by renaming and adding any</span></p>\n<p>Renaming a file to <code dir=\"ltr\">.ts</code> and fixing every error with <code dir=\"ltr\">any</code> or <code dir=\"ltr\">as any</code> produces TypeScript-flavoured JavaScript: it looks finished and checks nothing. Worse, it hides the file from the “still to do” list. Track <code dir=\"ltr\">any</code> the way you track test coverage (a lint rule such as <code dir=\"ltr\">@typescript-eslint/no-explicit-any</code> set to warn, and a count in CI) and treat the number as a migration metric, not just the count of <code dir=\"ltr\">.ts</code> files.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Keeping it fast</h2>\n<p>On a large codebase, type-checking speed becomes a developer-experience problem. Measure before you guess: <code dir=\"ltr\">tsc --noEmit --extendedDiagnostics</code> prints check time, memory and the number of type instantiations. A sudden jump in instantiations almost always comes from a heavy type-level utility, like the recursive <code dir=\"ltr\">Paths</code> from <a href=\"/lesson/advanced-typescript/l-at-3-5\">Recursive Types and Knowing When to Stop</a>, applied to a big type. <code dir=\"ltr\">--generateTrace &lt;dir&gt;</code> records where the time goes, file by file.</p>\n<p>The structural fixes are well established. Turn on <code dir=\"ltr\">incremental</code> so unchanged files are not re-checked. Split a large repository into <strong>project references</strong> (<code dir=\"ltr\">composite: true</code> in each package, <code dir=\"ltr\">tsc -b</code> at the root), so each part is checked once and its declarations reused. Prefer interfaces that <code dir=\"ltr\">extend</code> each other over long chains of <code dir=\"ltr\">&amp;</code> intersections for big object types, since the compiler caches interface relationships more effectively. And annotate the return types of exported functions, which saves the checker from re-inferring them and keeps declaration output stable.</p>\n<aside class=\"callout callout-note\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">Note</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>TypeScript 7</span></p>\n<p>TypeScript 7.0, released in July 2026, is a native port of the compiler written in Go. It typically type-checks 8 to 12 times faster than 6.0 and keeps the same type system and command-line behaviour, but removes the options 6.0 deprecated and does not yet ship a programmatic API, so tools that embed the compiler may need to stay on 6.0 for a while. Fix 6.0’s deprecation warnings first; then trying 7.0 is mostly a version bump.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">Where to go from here</h2>\n<p>You have used types to design states, steered inference, written type-level code with restraint, built the patterns real codebases rely on, and set up the compiler to back them. The habit to keep is the one from the first lesson: before you write a type, name the bug it prevents. Then pick the simplest type that prevents it. The exercise below puts the migration playbook in order; the final assessment covers the whole course.</p>\n","toc":[{"id":"sec-1","text":"Coexist first","level":2},{"id":"sec-2","text":"Check JavaScript before converting it","level":2},{"id":"sec-3","text":"Convert from the leaves inward","level":2},{"id":"sec-4","text":"Keeping it fast","level":2},{"id":"sec-5","text":"Where to go from here","level":2}],"takeaways":["Migrate incrementally with <code dir=\"ltr\">allowJs</code>, so JavaScript and TypeScript files live side by side and the app ships every day.","<code dir=\"ltr\">// @ts-check</code> plus JSDoc types gives a JavaScript file real checking before you rename it.","Convert leaf modules first and work toward the entry points, so each newly converted file imports typed code.","Type the boundaries (API responses, domain models) early; they give the most protection per hour spent.","Measure type-checking with <code dir=\"ltr\">--extendedDiagnostics</code>, use incremental builds and project references, and keep heavy type-level code out of hot paths."],"further":[{"title":"Migrating from JavaScript","url":"https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html"},{"title":"JSDoc Reference","url":"https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html"},{"title":"Project References","url":"https://www.typescriptlang.org/docs/handbook/project-references.html"},{"title":"Type Checking JavaScript Files","url":"https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html"}],"quiz":[{"q":"<p>You are starting a migration of a 1,500-file JavaScript app. Which first step keeps the app shippable?</p>","options":[{"text":"Create a long-lived <code dir=\"ltr\">typescript</code> branch and rename every file to <code dir=\"ltr\">.ts</code> there.","why":"A big-bang branch drifts from <code dir=\"ltr\">main</code> daily and merges into a wall of conflicts and errors. Teams that try it often abandon it."},{"text":"Rename every file to <code dir=\"ltr\">.ts</code> on <code dir=\"ltr\">main</code> and add <code dir=\"ltr\">// @ts-nocheck</code> to each one.","why":"This creates thousands of files that look typed but check nothing, and removing the directive later is the same big-bang problem."},{"text":"Wait until the team has time to convert everything in one sprint.","why":"That sprint never comes for a codebase this size; incremental work is the only approach that finishes."},{"text":"Add a tsconfig with <code dir=\"ltr\">allowJs</code>, run <code dir=\"ltr\">tsc --noEmit</code> in CI, and convert files one at a time on <code dir=\"ltr\">main</code>.","why":"Correct. JavaScript and TypeScript coexist, every pull request ships, and the converted share only goes up."}],"answer":3},{"q":"<p>In which order should you convert modules?</p>","options":[{"text":"Entry points first, so the most visible code is typed.","why":"Entry points import everything else; converted early, they import untyped modules and get mostly <code dir=\"ltr\">any</code>."},{"text":"Leaf modules (those that import nothing internal) first, then the modules that depend on them.","why":"Correct. Each converted file then imports code that is already typed, so its own types are real from day one."},{"text":"Alphabetically, so progress is easy to track.","why":"File names say nothing about dependencies; you would often convert a file before the code it relies on."}],"answer":1},{"q":"<p><code dir=\"ltr\">tsc --extendedDiagnostics</code> shows 9 million type instantiations, most from one file. What is the likely cause?</p>","options":[{"text":"A heavy type-level utility (deep recursion, large template-literal unions) applied to a large type.","why":"Correct. Instantiation counts explode with recursive and combinatorial types; simplify or narrow that type first."},{"text":"Too many comments in that file.","why":"Comments are skipped by the type checker; they cost nothing measurable."},{"text":"Not enough <code dir=\"ltr\">any</code> annotations.","why":"Adding <code dir=\"ltr\">any</code> hides the problem by removing checking, which is not a fix."}],"answer":0},{"q":"<p>How does <code dir=\"ltr\">// @ts-check</code> at the top of a <code dir=\"ltr\">.js</code> file help a migration?</p>","options":[{"text":"It converts the file to TypeScript automatically.","why":"The file stays JavaScript; nothing is renamed or rewritten."},{"text":"It makes <code dir=\"ltr\">tsc</code> emit a <code dir=\"ltr\">.d.ts</code> file for that module.","why":"Declaration output depends on <code dir=\"ltr\">declaration</code> settings, not on <code dir=\"ltr\">@ts-check</code>."},{"text":"It type-checks that JavaScript file, using JSDoc comments for types, so you can find bugs before renaming it.","why":"Correct. JSDoc annotations like <code dir=\"ltr\">@param {Order} order</code> are real types to the checker, with no build changes."}],"answer":2}],"exercise":{"kind":"order","items":["add-tsconfig","check-boundaries","convert-leaves","convert-inward","drop-allowjs","ratchet-flags"],"title":"Put the migration playbook in order","prompt":"<p>Cartwheel’s order-management app is 1,500 JavaScript files and must keep shipping every day. Put these migration steps in the order the lesson recommends, from the first pull request to the last.</p>\n","hints":["The very first step changes nothing about how the app is built or run.","You can get type checking in JavaScript files before renaming any of them.","Converted files should import code that is already typed, and the extra strictness flags come once everything is TypeScript."],"explanation":"<p>First, a tsconfig with <code dir=\"ltr\">allowJs</code> and <code dir=\"ltr\">noEmit</code>, checked in CI, lets both languages coexist without touching the build. Next, <code dir=\"ltr\">// @ts-check</code> and JSDoc on the boundaries (API client, domain models) give the most protection before any renaming. Then convert leaf modules, and work inward, so each converted file imports typed code. When no <code dir=\"ltr\">.js</code> source remains, drop <code dir=\"ltr\">allowJs</code>. Finally, ratchet up extra flags such as <code dir=\"ltr\">noUncheckedIndexedAccess</code> one at a time, suppressing existing errors with <code dir=\"ltr\">@ts-expect-error</code> and a ticket so new code meets the higher bar immediately.</p>\n","checkLabels":{},"itemText":{"add-tsconfig":"Add a tsconfig with <code dir=\"ltr\">allowJs</code>, <code dir=\"ltr\">noEmit</code> and <code dir=\"ltr\">strict</code>, and run <code dir=\"ltr\">tsc --noEmit</code> in CI next to the existing build","check-boundaries":"Add <code dir=\"ltr\">// @ts-check</code> and JSDoc types to the API client and domain model files","convert-leaves":"Rename leaf modules (helpers that import nothing internal) from <code dir=\"ltr\">.js</code> to <code dir=\"ltr\">.ts</code>","convert-inward":"Convert the modules that depend on already-converted ones, working toward the entry points","drop-allowjs":"Remove <code dir=\"ltr\">allowJs</code> once no <code dir=\"ltr\">.js</code> source files remain","ratchet-flags":"Turn on <code dir=\"ltr\">noUncheckedIndexedAccess</code> and other extra flags one at a time, marking old errors with <code dir=\"ltr\">@ts-expect-error</code>"}},"words":830,"readMinutes":4}