Strategies for migrating to TypeScript

[2020-04-17] dev, javascript, typescript
(Ad, please don’t block)

This blog post gives an overview of strategies for migrating code bases from JavaScript to TypeScript. It also mentions material for further reading.

Three strategies for migrating  

These are three strategies for migrating to TypeScript:

  • We can support a mix of JavaScript and TypeScript files for our code base. We start with only JavaScript files and then switch more and more files to TypeScript.

  • We can continue to use plain JavaScript and add type information via JSDoc comments until everything if fully typed. At that point, we switch to TypeScript.

  • For large projects, there may be too many TypeScript errors during migration. Then snapshot tests for the errors can help.

More information:

Strategy: mixed JavaScript/TypeScript code bases  

The TypeScript compiler supports a mix of JavaScript and TypeScript files if we use the compiler option --allowJs:

  • TypeScript files are compiled.
  • JavaScript files are simply copied over to the output directory (after a few simple type checks).

At first, there are only JavaScript files. Then, one by one, we switch files to TypeScript. While we do so, our code base keeps being compiled.

This is what tsconfig.json looks like:

{
  "compilerOptions": {
    ···
    "allowJs": true
  }
}

More information:

Strategy: adding type information to plain JavaScript files  

This approach works as follows:

  • We continue to use our current build infrastructure.
  • We run the TypeScript compiler, but only as a type checker (compiler option --noEmit).
  • We add type information via JSDoc comments (see example below) and type definition files.
  • Once TypeScript’s type checker doesn’t complain anymore, we use the compiler to build the code base (similar to the previous strategy). Switching from .js files to .ts files is not urgent now because the whole code base is already fully statically typed. We can even produce type files (filename extension .d.ts) now.

This is how we specify static types for plain JavaScript via JSDoc comments:

/**
 * @param {number} x - A number param.
 * @param {number} y - A number param.
 * @returns {number} This is the result
 */
function add(x, y) {
  return x + y;
}
/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
/** @typedef {(data: string, index?: number) => boolean} Predicate */

More information:

Strategy: migrating large projects by snapshot testing the TypeScript errors  

In large JavaScript projects, switching to TypeScript may produce too many errors – no matter which approach we choose. Then snapshot-testing the TypeScript errors may be an option:

  • We run the compiler on the whole code base for the first time.
  • The errors produced by the TypeScript compiler become our initial snapshot.
  • As we work on the code base, we compare new error output with the previous snapshot: Sometimes errors disappear. Then we can create a new snapshot. Sometimes new errors appear. Then we either have to fix these errors (if we can) or create a new snapshot.

More information:

Conclusion  

We have taken a quick look at various strategies for migrating to TypeScript. Two more tips:

  • Start your migration with experiments: Play with your code base and try out various approaches before committing to one of them.
  • Then lay out a clear plan for going forward. Talk to your team w.r.t. prioritization:
    • Sometimes finishing the migration quickly may take priority.
    • Sometimes the code remaining fully functional during the migration may take priority.
    • And so on...

What have your experiences been when you migrated code bases from JavaScript to TypeScript? Let us know in the comments!