Photo by Ylanite Koppens from Pexels

Improvements to Flow in 2019

Andrew Pardoe
Flow
Published in
4 min readFeb 19, 2020

--

The retrospective post we shared last year included a roadmap that spans multiple years. It included three focus areas:

  1. Performance: Make Flow scale as O(size of edits) rather than O(size of codebase)
  2. Reliability: Make Flow type analysis trustworthy with respect to run-time semantics, for product safety and developer efficiency.
  3. Language tooling: Make Flow the basis for unified JavaScript tooling to enable language evolution for performance and reliability.

Working from this roadmap, we identified these goals for 2019:

  1. Reduce recheck time and peak memory usage
  2. Improve IDE reliability and responsiveness
  3. Make enhancements to the type system

“Types-first” for recheck performance

Flow is built for huge codebases: we do types at scale. But Flow wasn’t holding up to Facebook’s enormous scale. Editing code at Facebook grew increasingly unresponsive as more teams moved their code to React, React Native, and Relay. Our JavaScript codebase is tens of millions of lines and growing!

We made changes to optimize performance and memory usage that improved our recheck times by 70%. These changes are built on a rearchitecture called “types-first”. Types-first allows us to tame large dependency cycles, decrease memory usage, and increase IDE responsiveness.

Before types-first, Flow needed to check files in dependency order. That is, if file B depends on file A, then we could not check file B until we have finished checking file A. Types-first avoids this dependency order by first extracting a signature from all files before checking. You can think of these signatures as header files or interface files from other languages, except they are automatically extracted from your code. With these signatures in place, we can check A and B in parallel, since B depends only on the signature of A, not A itself.

Types-first requires more annotations in your codebase, which means it’s not trivial to upgrade your code. But developers at Facebook have found that it’s worth it, and have been very happy with early results of this work as we’ve rolled it out across our codebases. We’ll post more about types-first, along with advice on migrating your code, later this year.

Editing and browsing improvements

Flow provides rich editing features in IDEs through the LSP API. Some of the editing commands supported by the Flow server were slow to respond, or even failed unexpectedly. We set up logging and dashboards to collect and view metrics about IDE command usage inside Facebook. From this data, we identified the top five IDE commands that needed to be more responsive and reliable:

  • typeCoverage
  • documentHighlight
  • hover
  • completion
  • definition

We made all of these commands far more robust and responsive.

We also made Flow act and feel more responsive when you edit code:

  • Responding to IDE requests during rechecks makes Flow more responsive. Previously, Flow responded to requests sequentially, so it couldn’t respond to even a simple IDE request while rechecking the codebase. Now, if an IDE request comes while Flow is rechecking, Flow will respond to that request with the most recent information available. (Note that this type information may be out-of-date until the recheck completes.)
  • Building upon that work, Flow now shows live errors while you edit your code. Showing type errors before you save your file makes Flow feel more responsive. If your IDE uses the Flow language server via flow lsp, Flow will respond to onChange notifications to build a copy of your modified file. Flow typechecks that modified file and sends errors back to the IDE while you edit your code.

We’ll continue to invest throughout 2020 in improving the reliability and responsiveness of editing and browsing features.

Type system improvements

Our goal is that the Flow type system is simple and expressive, while helping developers to avoid common costly mistakes. Our desire to better support JavaScript’s popular spread syntax motivated us to switch Flow to use exact objects by default and to change our object model for spreads. Flow also implemented support for JavaScript’s optional chaining operator.

  • We’ve heard from developers that they almost always want to use exact object types. Exact objects are much safer, more precise, and clearer. We’ve introduced an exact-by-default type syntax. You can read more and find instructions for taking advantage of this syntax in your code in this post from January.
    Inexact object types interact poorly with spread syntax. For example, given a : { p: string, ... }, the type of the expression { q: 0, ...a } is not { q: number, p: string, ...} because a might include some q of an unknown type via width subtyping.
  • Our object model for spreads was imprecise and unsound, leading us to infer mixed and optional properties in cases where we couldn't infer a precise type. Now, Flow will emit an early error letting you know that a precise type cannot be inferred. You can read more about our new object model for spreads in this post. Work done on top of this object model allowed us to make fixes to object spreads. This post helps you identify and fix common errors in spreads.

Looking forward

The performance, reliability, responsiveness, and type system improvements we delivered in 2019 made great progress towards our goal of providing a delightful developer experience and strong safety guarantees. We hope you’ve enjoyed the improvements we made to Flow in 2019. We’ll build upon these gains in 2020 with an even better editing and browsing experience, continued type system improvements, and better performance and reliability. Keep an eye on this blog for an upcoming roadmap for our planned improvements in 2020.

--

--