A year later, we are in the process of completely replacing Flow with Typescript. Over the last year, Typescript has added all the features I was missing, while Flow started to show serious problems. I want to list some of the individual points that made us switch and then give some advice for projects attempting the same.
Unhelpful error messages
Although facebook has made some attempts to improve the errors in Flow, the current state of things is that errors usually show where they were detected, not were they’re caused. The most dreaded we encountered time and time again is
Incompatible instantiation for T,
T being a type variable.
Flow doesn’t tell you what the incompatible instantiation is or where it thinks this instance occurs, leaving you to guess and attempt to tighten down things by adding more explicit annotations everywhere. We never got this to work and usually resorted to suppressing the error.
Compare this to Typescript, we had the same problem appear, but Typescript tells you exactly where an incompatible Type flows in and tells you precisely which part of the type is incompatible, enumerating all possible alternatives. This is enough to quickly find the source of these problems.
There are some issues that are too tricky for Typescript to solve. To circumvent these, the way to go seems to be to cast some expression to
any and then to the type you know it has. You need to be explicit here but you can be sure that whatever problem exists, doesn’t propagate
Community and relationship to React
My original assumption was that Flow would be the ideal choice for a React project since both are developed at facebook. After a year I don’t have the feeling that the React and Flow teams talk much to one another.
I take this as a sign that Facebook isn’t as invested in Flow as it should be. Microsoft on the other hand has given Typescript a ton of attention. Progress is fast, there are open discussions with the community about new features and they even built their own editor for Typescript (more on that in the next section).
I don’t have concrete numbers, but it feels like there are way more developers out there using Typescript than there are using Flow. This results in a much better selection of exiting typings and more places where I can go to look for help and answers. To be fair, recently (February 2017), Flow seems to be gaining a little more traction, but it’s still not quite there yet.
Getting Flow to run is not easy. For the Mac, luckily there are prebuilt binaries available through homebrew but if you need to compile it yourself, you need an OCaml development stack set up. The flow binary starts a server that monitors your files and can be queried for errors or type information.
These queries can be sent using the flow binary against a running server. Integration into different editors and IDEs is done through third party plugins. To be fair, I’ve only tried Vim, one of my coworkers had success with a plugin for Atom. But developing Flow never ever felt like developing in a typed language. It is noticable that Flow is a type system that has been bolted onto an untyped language.
The flow binary provides some helpers for analyzing your code. There is the
coverage command that tells you how many of the expressions in a file Flow knows about. It comes with a
--debug flag whose output looks like this:
$ flow coverage --debug src/index.js 5:6,5:16: (true) 5:19,5:27: (true) 5:19,5:49: (true) 5:43,5:48: (true) 7:0,7:6: (false) 7:0,7:26: (false) 7:7,7:13: (false)
Very helpful. You can also tell it print your file, coloring some expressions in red. Funny enough, using
type-at-pos, I’ve seen both red and normal text whose type Flow thought was
The biggest advantage when working with Typescript in Terms of tooling though is Visual Studio Code, an editor written in Electron, like Github’s Atom. Editing Typescript with VSCode gives you all the power and convenience of a big IDE. Jumping to definitions, autocompletion, JSDoc tooltips, refactoring commands, inline rendering of type errors are things I don’t want to miss again. The increase in productivity these features provide is huge!
Even though Flow supposedly makes a distinction between
loose mode, I found that strict does not, in any way prevent untyped code from passing checks. To the contrary, it feels very much like untyped code is contagious. Everything it touches becomes untyped too. Files that were once typed, can silently become untyped and stop producing errors. And you’ll never know about it unless you use
type-at-pos on the commandline to manually sample expressions.
While all the previous points were annoying but acceptable, this lack of reliability finally caused us to abandon Flow. The entire purpose of a typechecker is to give me confidence that certain problems are absent from my codebase. Flow will happily proclaim
No errors!, while I stare at code that is obviously wrong. I am adding type annotations that make my code hard to read left and right, only to have Flow ignore them.
Tips for switching
All the work that went into typing the project with Flow did not go to waste. Semantically and syntactically, Flow and Typescript are very, very similar. Writing our code in a way that worked well with Flow meant it also worked well with Typescript. The translation consisted mostly of
- Rename the file to
- Remove the
- Make slight syntax adjustments (imports, interfaces)
- Fix the type errors that Flow never managed to discover
We are using webpack and were writing our files using ES6 features (through Babel-Loader) and ES6 modules (through Webpack2). Nothing that’s not supported by Typescript. The change in setup then was:
.tsxextensions to webpacks resolver configuration, before
- Add the
- Create a
--noImplicitAnyenabled. If you set your target for typescript to
es5, set the
["dom", "es5", "es2015", "es2016", "es2017"]to prevent complaints about missing definitions for stuff like