Running a Flow to TypeScript migration

Luca Di matteo
partoo
Published in
5 min readOct 13, 2021

A little Partoo background history

Before 2017, Partoo’s entire application was built using Python template library called Mako so, there wasn’t much frontend awesomeness happening.

Thankfully things have changed, and in early 2017 we switched to a more standard React codebase. At that time we knew we wanted to stay typesafe, and after considering Flow and TypeScript, we took an informed decision to go for Flow. Oh boy.

Fast forward to January 2021, we now have a sizeable React codebase, with around 3500 files. Some of it being legacy code, with questionnable typing. But more importantly, we have faced some issues with Flow, that convinced us that the time has come to switch to TypeScript.

Why ?

Let’s briefly go over our 4 major arguments against Flow, and for TypeScript:

  • A large number of libraries are now written in TypeScript. Having to reverse engineer their types is painful.
  • We feel more confident in our capacity to handle growth with a TypeScript codebase (Enforcing stricter rules, with easier to access online resources).
  • Flow sometimes silently ignore type errors, and our migration has made it even more obvious.
  • Visual Studio Code has a suboptimal Flow integration, and a very powerful TypeScript integration.

We had at least an other dozen pinpoints, but the internet has already covered the TypeScript vs Flow fight quite well. You’ll easily find more arguments if you wish to go deeper.

How ?

Going into the migration, we had a few constraints:

  • As a technical constraint, typed TS variables are not compatible with Flow, and vice versa. So a hybrid codebase can compile, but it is impossible to maintain.
  • We cannot block our feature teams development.
  • We do this to improve our codebase, not to make it worse. So our goal is not to end up having thousands of @ts-nocheck and @ts-ignore.

Setting everything up

Just like the overwhelming majority of Flow to TS migrations, we used the flow-to-ts library by Khan Academy.

It works great, but on large codebases with sometimes old and dubious Flow typing, running it on the entire codebase is ambitious, to say the least. We tried it, and ended up with a bit more than 4000 TS errors. We could have added hundreds of @ts-nocheck, but as I said earlier our goal is to make the codebase cleaner. So, this wasn’t the right solution for us!

Instead, we took a more iterative approach. Here is what we did:

  • Update our project config to handle TS as well as JS (Adding the tsconfig, updating our webpack and eslint configs).
  • Prepare the artillery for the team members involved in the migration. We went for a flow-to-ts config applying our standards, and abstracted all that in our respective .aliasrc files:
alias js2ts="npx @khanacademy/flow-to-ts --prettier --semi true --single-quote true --tab-width 4 --trailing-comma all --bracket-spacing true --print-width 100 --write --delete-source"
  • Fire in the hole:
js2ts path/to/folder/**/*.js*

Just like that we can migrate a whole folder, and check that everything went well.

Entering the battle

Importing a JS variable in a TS file will automatically associate it to the any type. Migrating that same variable to TS, later on, can reveal a TS error that was silent on the initial file migration.
For that reason, in order to limit the number of back and forth trips on an already migrated file, we took a folder to folder approach starting with the most commonly imported folders.

Of course, the quality of the Flow typing really has an impact on the migration speed. Flow sometimes silently bails, but it will fail in TS. So we had to take some time to properly type files that went under the radar in Flow.

Handling conflicts

We had to regularly fix conflicts. It is not as trivial as it seems, since most migrated files are too different for git to treat it as a renaming.
In that case, it is still possible to access a git diff by renaming the conflicting files from .js to .ts.

But if the migration didn’t require manual intervention in the first place, rerunning our js2ts command was usually the faster option.

Deploying the new TypeScript codebase

Once everything is done, it’s time to really make sure we didn’t screw up somewhere.

In a perfect world, the compiled code after the TS migration is exactly the same as the one before. But it isn’t a realistic expectation, as some compiled variables are going to be named differently in TS anyways.
We still gave it a look throught and out of 3500 files, close to 300 had a different compiled code, amongst which 3 had actual errors introduced by the migration (PEBCAK, of course).

In addition to the existing automated tests, we also did an extensive functional test for a day, helped by other engineering teams and QAs. And live it goes!

By that point we also started to work with our feature teams in order to enforce new good practices and standards:

  • Giving them access to good TypeScript learning material on egghead
  • Doing a TypeScript Tech Dojo (1h technical presentation) to share standard cases and good practices.

Conclusion

Now our entire frontend codebase is in TypeScript ! Hurray!

It took us a little less than a month for the equivalent of a full time dev. It was a considerable time investment, but we believe it was all well worth it.

We are now more confident in our ability to handle growth within the team, and it feels so nice to finally be able to write some TypeScript at Partoo!

--

--

Luca Di matteo
partoo
Writer for

I like to build stuff, and learn in the process.