TypeScript migration: the easy way

Quentin Menoret
Doctolib
Published in
4 min readJul 21, 2020
Illustration by Gaëlle Malenfant

A few months back, our team decided to migrate one of our projects to TypeScript. The full rationale may be the subject of a future blog post, but suffice to say we’re working in an area with bindings to native code and proprietary (often undocumented) interfaces where the strong-typing we get from TS is already helping, catching runtime errors before they occur in time-pressured situations.

Here we’ll assume that, like us, you want to perform a full migration, without impacting the delivery of new features.

Disclaimer: This is not a guide on how to change JavaScript code to TypeScript. It’s a more of a generic guide about how to organise and manage such a migration.

Boy scout rule

It’s common to have a soft “touch it, migrate it” Boy Scout rule, but for our migration we made this rule hard. But please, not in the same PR! Quite often it makes it impossible to review.

Most of the time we prefer to move the file to TypeScript first, then add the new feature. This way you can already benefit from the type system to alter the code.

And when we say “move to TypeScript first”, that’s really all we mean. Do not try to refactor at the same time. TypeScript migration PRs should be extremely fast and simple to review. Any changes to the code can make the git diff completely unreadable.

Migrate from bottom to top

As you probably know, calling JavaScript functions from TypeScript is not exactly useful as you have no way to know if you’re providing the right arguments or types. Of course calling TypeScript from JavaScript won’t tell you much either, but you will have types on your return values. If you have to start somewhere, it’s easier to start from the utility functions than with the entry point of your program.

We found a very simple way to estimate how complicated a file will be to migrate: count the number of “imports”. If you migrate all files without any imports, then most likely a file with one import will rely either on TypeScript or an external dependency (which hopefully includes types!). It is not true 100% of the time, but we found that it’s a good place to start and it works quite well in general.

Here is a simple way to sort all your files by the number of import statements:

https://gist.github.com/qmenoret/25926e22090a0c7dacace54d2c5f0046

Don’t be too strict from day one

Of course in the long run we want to enable strict mode on our projects, but on the first day you may want to have some flexibility.

Not activating noImplicitAny, for instance, will allow you to move faster. Then you can introduce it later once you actually have defined the types you need. You would otherwise need to write all types beforehand, which is kinda discouraging.

Just don’t forget to enable strict mode once ready!

Start with what you can do

If it’s your first experience with TypeScript, you will sometimes encounter files you are not yet ready to migrate. If you have already spent hours on a file, it’s ok to give up. Try to migrate something easier first and come back to it later. Build your skills on the easy parts!

There are a few patterns that need practice. Give it time, you’ll get there.

Measure everything

When we decided to move to TypeScript we also decided to go with 100% TypeScript coverage (full migration) for consistency. It’s of course not realistic for all project but the codebase in question was only ~12k LOC in ~200 files. It’s achievable.

In order to track the migration we used the following metrics:

  • Number of files (js vs ts)
  • Number of lines (js vs ts)
  • Number of “any” usage (while migrating we had to use it in some places but we want the usage to remain low)
  • Number of “unknown” usage (same reasons)

We reviewed them every month to decide if we should increase our investment and what to focus on. Migrating files is important, but you need some quality metrics as well.

This is what our dashboard currently looks like:

TypeScript migration dashboard

Step by step, steady migration

It’s impossible to completely stop working on your product and just refactor a project. It will take months to finish, but it’s extremely easy to move one file at a time. That’s the whole point of a TypeScript migration, you don’t have to rush it as both languages can live together. Just don’t overdo it, but don’t give up. It’s only a matter of time.

You might also encounter a dependency you use that does not provide typing. Do not overcommit, type only the features you use. Typing an external dependency is a huge amount of work (and burdensome to maintain), even when it does not seem like it. If you really want to contribute you can do that later on.

Looking back 6 month after starting

We still haven’t finished, and yes we’ve been working on it for the last 6 months. We migrated around 75% of the codebase already and we keep investing steadily, piece by piece. There is no rush to get there, we can already benefit a lot from TypeScript with what we have.

Have you already migrated a codebase to TypeScript? Do you have any tips to share?

Anyway, if you like this post feel free to subscribe to our tech newsletter for more of these!

--

--