At FindHotel, we recently completed the migration of our front-end code from Facebook’s Flow to Microsoft’s TypeScript. We thought now would be a good time to detail how we did so without disrupting service to our users, and with minimal code freeze on the engineering side. Since this was the second migration of a transpiled language to another, we will give a brief overview of the evolution of the front-end technologies over time, and our motivation for doing so.
In short, we’ve gone through two major shifts, first from CoffeeScript → Flow, and a few months ago from Flow → TypeScript, while React with Redux has remained our main front-end paradigm since 2015.
2015: Adoption of CoffeeScript
When I joined FindHotel in the summer of 2015, the FindHotel website was to be built from scratch as up until then FindHotel relied on a partnership with HotelsCombined that provided our website as a white-label solution.
I started as the first front-end engineer, the other engineers were all back-end engineers writing Ruby. Since the syntax and paradigms between CoffeeScript and Ruby were quite similar, it was easier for all of us to make adjustments to the different parts of the codebase.
No technology lock-in
2017: CoffeeScript → Flow
Since Babel by now was enjoying much wider adoption, tools and IDE support were much better than CoffeeScript’s. ES2015 and later iterations had by now adopted much of what made CoffeeScript great, and tools such as linters and formatters allowed for a much better developer experience and increased robustness.
Around the end of 2017 our front-end team grew to 5 full-time employees, and most new joiners would likely have production experience with ES2015, but not with CoffeeScript. On-boarding developers to CoffeeScript was never much of a problem, but there was still a slight learning curve. Additionally, not many candidates were particularly excited about CoffeeScript, while the shiny ES2015 was something most developers were eager to work with, making it easier for us to hire new talent.
Static Typing, Flow vs TypeScript
Facebook backed: since we used React extensively, we figured since Facebook uses Flow internally for the React codebase, we assumed as long as Facebook supports React, Flow would be supported and maintained too.
In contrast, Facebook has a great track record with evolving React gradually without many breaking changes (luckily things got better with Angular's approach, too).
The transition strategy
We set up a multi-stage conversion plan where we created a conversion toolkit branch that was developed separately from the main master branch, in which we:
- used decaffeinate to mostly automate the conversion process
Once all open pull requests were merged, we started gradually adding Flow types to the codebase.
2020: Flow → TypeScript
While we were happy with the static typing features that Flow gave us, fast-forward a few years, and we ended up with another major transition - this time to TypeScript. Over time, TypeScript become many orders of magnitude more popular than Flow. At this point Flow is still maintained and being evolved by Facebook, but at a much slower rate and with a less predictable roadmap that Microsoft is following with TypeScript.
To sum up, our main reasons for moving away from Flow → TypeScript were:
Facebook maintains and evolves Flow mostly based on what is important for them internally, without ways for the wider community to influence the roadmap. TypeScript follows a predictable release schedule and accepts 3rd party pull requests.
Better tooling / IDE support and performance
Over time, with massive investment from Microsoft in the VSCode IDE, the tooling around the TypeScript ecosystem is much vaster than Flow’s.
We encountered many instances where the Flow server would crash and have to be restarted to enable type support in our IDEs. Recently Facebook has dropped support for Atom, while IDE support for TypeScript keeps improving steadily.
When I attended the Advanced React Conference in the winter of 2019 with two of my colleagues, there was a request for show of hands between engineers who were using Flow vs TypeScript in production. I believe we were about the only three people raising our hands for using Flow :| While Flow was still mostly working for us, its future looks quite bleak compared to TypeScript’s. An interesting pointer of TypeScript’s success over Flow’s for us was when Facebook’s testing framework Jest switched from Flow to TypeScript to facilitate external contributions from the community.
Here are the NPM downloads for Flow vs TypeScript over the last 5 years (https://www.npmtrends.com):
3rd party library typing
Due to TypeScript’s popularity, 3rd party libraries and packages generally use TypeScript, thus consuming them in a type-safe way is much easier with TypeScript as there are more types available than for Flow. This reduces the number of type errors in our codebase when consuming external libraries.
The transition strategy
After our successful conversion from CoffeeScript → Flow, we opted for a similar strategy to convert from Flow → TypeScript, where we prepared the automated conversion in a toolkit branch that can be applied on open branches. The transition was easier this time around as the two typed languages are so similar, though there were still a few files that needed manual fixes before the conversion would work automatically.
To recap, we:
- used flow-to-ts to convert Flow to TypeScript automatically
- wrote custom Makefile commands to clean up unsupported patterns - no need for JSCodeShift this time around - and a script to make git understand files were changed and their extension renamed, should be considered “git moved”.
- applied XO linter fixes to force a consistent coding style across the codebase
- plan for manual clean up after the conversion of a few patterns that Flow supports but TypeScript doesn’t, such as opaque types
Bumps in the road
We did run into a few snags, mainly with Github since we were changing our codebase of nearly 4000 files at once.
- Github doesn’t like Pull Requests with 3000+ files, so manual review had to be done offline - our test suite helped us with the confidence in shipping the converted files
- Github doesn’t detect git moves between .js(x) → .ts(x) properly, meaning we resorted to comparing local diffs for open pull requests. This was not a problem with git itself, rather with Github: https://github.com/isaacs/github/issues/900
In the end, these didn’t end up being a massive pain, and we were able to move pretty smoothly to TypeScript.
A few leftovers remain that we’re addressing with a campground-rule strategy over time:
- FlowTodos: converting to TypeScript removed quite a lot of gaps in the types of external packages, but some “pragmatic” (lazy) pieces of code might still be improperly typed; in the meantime we created a global TypeScript alias for us to know whether we really intend for something to be unknown or whether it’s a hack that needs fixing
- Re-introduce opaque types: we didn’t have many opaque types, and this is not something we’ve solved for yet, though there are some solutions available we’re considering
- Interfaces vs Types: The flow-to-ts tool we used converts everything to type instead of interfaces, as this is what Flow uses. Since types and interfaces can be mixed we intend to write interfaces for new code
It wasn’t trivial, but overall we’re very happy with our move from Flow to TypeScript; type coverage increased, and there are no more performance issues in our IDEs. We are as always grateful for the open-source community in providing with the tools to automate (most of) the conversion with a good degree of confidence, and are excited to be part of the TypeScript community!
PS: I apologise for including a graph in this post, I am sure you're tired of seeing them after the past year. That said, we're lucky to be doing well even in these troubling times, and our internal performance graphs are looking pretty good:
We are hiring for many roles, so if you’re looking to join an innovative travel company that can overcome tough challenges, have a look at our career page and do reach out! https://careers.findhotel.net/