Migrating JavaScript to TypeScript
Migrating to TypeScript is an incremental process — you enable allowJs , add JSDoc and // @ts-check , rename files .js → .ts one at a time, replace any with real types, and turn on strictness gradually rather than all at once.
Learn Migrating JavaScript to TypeScript in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick…
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
Migrating to TypeScript is like renovating a house while still living in it. You don't demolish everything and move out — you do one room at a time, keeping the place habitable throughout. allowJs lets the old and new rooms share a roof; // @ts-check is putting up safety tape in a room before you fully redo it; flipping strict flags one by one is tightening the building code gradually instead of failing every inspection at once.
1. Start Without Renaming: // @ts-check + JSDoc
You can get type-checking on a plain .js file before renaming anything. Add // @ts-check at the top (or set allowJs + checkJs in tsconfig ), then describe types with JSDoc comments. The editor checks them; the code still runs as ordinary JavaScript because JSDoc is just comments.
2. Rename to .ts & Replace any
Next, rename files .js → .ts from the leaves of your dependency tree upward, so each converted file only imports already-typed code. As you go, replace the implicit any on parameters and data with real interface s and types — and prefer unknown over any for genuinely uncertain values.
3. Turn On Strictness Gradually
Resist setting "strict": true on day one — it can surface thousands of errors at once. Enable flags one at a time : noImplicitAny , then strictNullChecks (usually the biggest payoff), then the rest, finishing with full strict . strictNullChecks in particular forces you to add the null guards that prevent real runtime crashes.
🎯 Your Turn
Add the null guard that strictNullChecks would demand, so firstName(null) returns "anonymous" instead of crashing. Fill in the two blanks marked ___ , then run it.
Common Errors (and the fix)
- ❌ "Parameter 'x' implicitly has an 'any' type" — noImplicitAny is on and a param is untyped. ✅ Annotate it, or temporarily set noImplicitAny: false while migrating.
- ❌ "Object is possibly 'null'" — strictNullChecks flagged unguarded access. ✅ Add a guard ( if (!x) return ... ) or use optional chaining.
- ❌ "Could not find a declaration file for module..." after renaming. ✅ npm i -D @types/... , or add a declare module stub.
- ❌ Flipping "strict": true floods you with errors — too much at once. ✅ Enable one strict sub-flag per PR instead.
Pro Tips
- 💡 Convert from the leaves inward — files with the fewest dependencies first, so each .ts imports already-typed code.
- 💡 Use // @ts-expect-error (not // @ts-ignore ) for known issues — it warns you when the error is finally fixed.
- 💡 Lean on JSDoc to add types without a build step; it's a genuine on-ramp, not a toy.
- 💡 Track progress by counting remaining any s or un-migrated files — visible momentum keeps a migration alive.
📚 Keep Learning
- 📖 TypeScript Handbook — Migrating from JavaScript
- 📖 Handbook — JSDoc Supported Types
- 📖 tsconfig — strict and its sub-flags
Frequently Asked Questions
Mini-Challenge: A Strict-Safe Config Parser
No blanks this time — just a brief and a starting outline. Migrate the parser to handle missing input safely, run it, and check your output against the example in the comments.
🎉 Lesson Complete
- ✅ Migration is incremental — .js and .ts coexist via allowJs
- ✅ // @ts-check + JSDoc type a plain .js file with no rename or build step
- ✅ Rename .js → .ts from the leaves inward , replacing implicit any
- ✅ Prefer unknown over any ; flip noImplicitAny on to find the rest
- ✅ Enable strict flags one at a time ; strictNullChecks forces real null guards
- ✅ Next lesson: Checkpoint — Applied TypeScript
Practice quiz
What is the recommended way to migrate JavaScript to TypeScript?
- Rewrite the entire codebase at once
- Delete all .js files first
- Migrate incrementally, file by file
- Avoid types entirely
Answer: Migrate incrementally, file by file. Migration is incremental: .js and .ts coexist and you convert files one at a time.
What does the allowJs compiler option do?
- Lets .js files be part of the TypeScript program
- Deletes JavaScript files
- Disables type checking
- Converts .js to .ts automatically
Answer: Lets .js files be part of the TypeScript program. allowJs lets JavaScript files join the program so they can import and be imported by .ts files.
How can you type a plain .js file without renaming it?
- You cannot type a .js file
- Use a .ts extension secretly
- Add a runtime type library
- Add // @ts-check and JSDoc comments
Answer: Add // @ts-check and JSDoc comments. // @ts-check plus JSDoc gives editor type-checking on a plain .js file, with no rename.
At runtime, what is JSDoc?
- Compiled type assertions
- Just comments, so behaviour is unchanged
- A separate runtime library
- Replaced by real types
Answer: Just comments, so behaviour is unchanged. JSDoc is comments; the code runs identically before and after adding it.
Which is a safer placeholder than any during migration?
- unknown
- object
- void
- never
Answer: unknown. unknown forces you to narrow before use, keeping safety; any throws all checking away.
What is the suggested order for enabling strict flags?
- Set strict: true on day one
- Only ever enable strictNullChecks
- One flag at a time, e.g. noImplicitAny then strictNullChecks
- Enable them randomly
Answer: One flag at a time, e.g. noImplicitAny then strictNullChecks. Flip flags one at a time so each pull request stays small and reviewable.
What does strictNullChecks force you to do?
- Remove all null values
- Handle null and undefined explicitly
- Use any everywhere
- Disable optional properties
Answer: Handle null and undefined explicitly. strictNullChecks makes you add guards for null/undefined, preventing real runtime crashes.
In what order should you rename files from .js to .ts?
- Top-level entry points first
- Alphabetically
- Largest files first
- From the leaves of the dependency tree inward
Answer: From the leaves of the dependency tree inward. Convert leaf files first so each new .ts only imports already-typed code.
Which comment is preferred over // @ts-ignore for a known issue?
- // @ts-skip
- // @ts-expect-error
- // @ts-off
- // @no-check
Answer: // @ts-expect-error. // @ts-expect-error warns you when the error is finally fixed, unlike // @ts-ignore.
What does noImplicitAny do once enabled?
- Bans all type annotations
- Converts any to unknown
- Flags parameters and values that silently became any
- Disables JSDoc
Answer: Flags parameters and values that silently became any. noImplicitAny surfaces every spot still missing a type by banning silent any.
Continue this course
- Previous: Decorators
- Next: Checkpoint: Applied TypeScript