Learn shortcuts. Work faster.

Facebook Flow Tutorial

Overview

Flow is an open-source static type checker for Javascript, built by Facebook.

Flow’s goal is to reduce the number of unhelpful “Syntax error” and “undefined is not a function” messages by catching mismatched types before you run them in a browser (or anywhere else).

You run Flow through the command line, pointing it at a particular directory. Flow looks through Javascript files and sees if the different types would cause a problem. For example, if you tried to multiply a number and string together, Flow would find that.

Installing Flow

Installing Flow is pretty simple. Open up a terminal and type the following:

mkdir -p get_started
cd new_project/
echo '{"name": "get_started", "scripts": {"flow": "flow; test $? -eq 0 -o $? -eq 2"}}' > package.json
touch .flowconfig
npm install --save-dev flow-bin

That’s it! You’re now ready to use Flow.

Using Flow

To start using Flow, create this multiply.js file and put it in the getting_started directory.

// @flow

function multiply(num1, num2) {
  return num1 * num2;
}
var x = multiply(3, '0');
console.log(x);

You can see the problem here: we’re trying to multiply a number and string together.

Also note the // @flow line. That tells Flow to examine this file. Flow will ignore any file that doesn’t have this line. (/* @flow */ is also acceptable)

To run Flow, type the command

npm run-script flow

You should get something like this:

$ npm run-script flow

> get_started@ flow /your_path/get_started
> flow; test $? -eq 0 -o $? -eq 2

Launching Flow server for /your_path/get_started
Spawned flow server (child pid=3732)
Logs will go to /private/tmp/flow/zSyour_pathzSget_started.log

multiply.js:6
  6: var x = multiply(3, '0');
             ^^^^^^^^^^^^^^^^ function call
  4:   return num1 * num2;
                     ^^^^ string. This type is incompatible with
  4:   return num1 * num2;
              ^^^^^^^^^^^ number


Found 1 error

Flow was smart enough to know that num1 * num2 should result in a number, and passing in a string would cause that to fail.

Very cool, but Flow isn’t perfect. For example, it wouldn’t find any errors in this code:

// @flow

function add(num1, num2) {
  return num1 + num2;
}
var x = add(3, '0');
console.log(x);

Try it. Create the file above, call it add.js and run npm run-script flow.

$ npm run-script flow

> get_started@ flow /Users/DanBefore/Developer/flow/get_started
> flow; test $? -eq 0 -o $? -eq 2

No errors!

If you ran the actual Javascript code, you’d get 30 as a result, which is probably not what you want. However, this code doesn’t break any rules because Javascript lets you concatenate numbers and strings. Thus, Flow decides it passes muster.

How to fix this issue? Use type annotations.

// @flow

function add(num1: number, num2:number): number {
  return num1 + num2;
}
var x = add(3, '0');
console.log(x);

Type annotations aren’t part of Javascript, but don’t worry about that right now. We’ll deal with it later.

Go ahead and run Flow on the new add.js file.

$ npm run-script flow

> get_started@ flow /your_path/get_started
> flow; test $? -eq 0 -o $? -eq 2

add.js:6
  6: var x = add(3, '0');
             ^^^^^^^^^^^ function call
  6: var x = add(3, '0');
                    ^^^ string. This type is incompatible with
  3: function add(num1: number, num2:number): number {
                                     ^^^^^^ number


Found 1 error

That’s more like it. Now we’re seeing the error.

Using Type Annotations

While introducing type annotations into your Javascript code isn’t required to use Flow, it can help you catch more errors.

Primitive Types

Flow has types that match all of the Javascript primitives:

  • boolean
  • number
  • string
  • null
  • void

Here are some examples:

// @flow

("gad zooks": string);
(1 + 1: string);

(false: boolean);
(0: boolean);

(45: number);
(true: number);

function theGogglesDoNothing(): void {}
function returnBoolean(): void { return true }

// Null has the type null
// Undefined has the type void
(null: null); // yup
(null: void); // nope

(undefined: void); // yup
(undefined: null); // nope

Run Flow on this and you’ll see the errors.


> get_started@ flow /your_path/get_started
> flow; test $? -eq 0 -o $? -eq 2

primitives.js:4
  4: (1 + 1: string);
      ^^^^^ number. This type is incompatible with
  4: (1 + 1: string);
             ^^^^^^ string

primitives.js:7
  7: (0: boolean);
      ^ number. This type is incompatible with
  7: (0: boolean);
         ^^^^^^^ boolean

primitives.js:10
 10: (true: number);
      ^^^^ boolean. This type is incompatible with
 10: (true: number);
            ^^^^^^ number

primitives.js:13
 13: function returnBoolean(): void { return true }
                                             ^^^^ boolean. This type is incompatible with the expected return type of
 13: function returnBoolean(): void { return true }
                               ^^^^ undefined

primitives.js:18
 18: (null: void); // nope
      ^^^^ null. This type is incompatible with
 18: (null: void); // nope
            ^^^^ undefined

primitives.js:21
 21: (undefined: null); // nope
      ^^^^^^^^^ undefined. This type is incompatible with
 21: (undefined: null); // nope
                 ^^^^ null


Found 6 errors

Advanced Types

Flow also supports a bunch of more advanced, flexible types.

  • any
  • mixed
  • literal types
  • class types

any type

any is a supertype of all types and a subtype of all types. It can literally be any type (boolean, number, whatever), and Flow will consider that variable or function to be well-typed.

Here are some examples:

// @flow

function takesAnyArgument(x: any): void {}
takesAnyArgument(0);
takesAnyArgument("");
takesAnyArgument({ foo: "bar" });

var whoKnows: any;
(whoKnows: number);
(whoKnows: string);
(whoKnows: { foo: string });

Here the takesAnyArgument function doesn’t care what kind of argument is passed, and the any type spells this out. The same appears true for the whoKnows variable. Let’s run Flow and find out.

$ npm run-script flow

> get_started@ flow /your_path/get_started
> flow; test $? -eq 0 -o $? -eq 2

any.js:9
  9: (whoKnows: number);
      ^^^^^^^^ uninitialized variable. This type is incompatible with
  9: (whoKnows: number);
                ^^^^^^ number

any.js:10
 10: (whoKnows: string);
      ^^^^^^^^ uninitialized variable. This type is incompatible with
 10: (whoKnows: string);
                ^^^^^^ string

any.js:11
 11: (whoKnows: { foo: string });
      ^^^^^^^^ uninitialized variable. This type is incompatible with
 11: (whoKnows: { foo: string });
                ^^^^^^^^^^^^^^^ object type


Found 3 errors

Whoops. What’s with all the errors? Why is there a problem with whoKnows?

The issue is that whoKnows is uninitialized: it doesn’t have an initial value. But if it gets an initial value, then it really isn’t any anymore, is it?

Flow has a way around this.

declare var whoKnows: any;

As you know, declare isn’t part of Javascript. Here, it’s just for Flow. The declare tells Flow that whoKnows can be anything.

Go ahead and change var whoKnows: any; to declare var whoKnows: any;. Then run Flow.

$ npm run-script flow

> get_started@ flow /Users/DanBefore/Developer/flow/get_started
> flow; test $? -eq 0 -o $? -eq 2

No errors!

In general, using any is a bad idea and should be treated with caution. However, if you’re trying to type-check a large number of Javascript files, you can add in any types into the code, and then change them into something more specific one by one.

You can read more about any here.

mixed, Class, and Literal Types

Diving into the rest of these types would triple the size of this article and possibly distract you from getting ramping up with Flow.

If you want to know more, check out the surprisingly well-written docs.

Removing Type Annotations for Final Build

Since type annotations are not part of the Javascript specification, you need to strip them out before sending the file to the user. Facebook recommends using Babel (a Javascript compiler) to do this.

Installing Babel

First, install the Babel command-line interface (CLI).

$> npm install -g babel-cli

Then set up Babel in the directory with the type anotations.

$> cd /path/to/my/project
$> mkdir -p node_modules && npm install babel-plugin-transform-flow-strip-types
$> echo '{"plugins": ["transform-flow-strip-types"]}' > .babelrc

Running Babel

Run the transpiler (that’s a real word, apparently) in the background with this:

$> babel --watch=./src --out-dir=./build

This will pick up any changes to files in src/, and create their pure JavaScript version in build/.

Checking Third-party code

Most production Javascript relies heavily on third-party code. Fortunately, you can use Flow to typecheck Javascript with external dependencies without having to typecheck the library code.

Interface files

You can create something called interface files which separate your code from library code. Fortunately, this does not involve changing the library code in any way.

Let Flow Know That Interface Files Exist

First, open up the file .flowconfig. Note that it’s a hidden file. When you first open it, it will look like this:

It’s empty.

Change that to:

[libs]
interfaces/

Example

Suppose you’re using the Underscore library and have this Javascript:

//* @flow */

var animals = [
  { name: 'anteater', predator: true },
  { name: 'koala', predator: false },
  { name: 'cheetah', predator: true },
  { name: 'sloth', predator: false },
];

function findPredators() {
  return _.findWhere(animals, {predator: true});
}

Running Flow will produce an error because it has no idea what the global variable _. is.

animals.js:11
 11:   return _.findWhere(animals, {predator: true});
              ^ identifier `_`. Could not resolve name

To solve this problem, create:

  1. an interfaces directory inside the get_started directory, and
  2. an underscore.js file inside interfaces.
// interfaces/underscore.js
declare class Underscore {
  findWhere<T>(list: Array<T>, properties: {}): T;
}

declare var _: Underscore;

This may look complicated, but all it says is:

  • the global variable ._ is of the type Underscore
  • Underscore is just a class with one method: findWhere, which takes an array and an object as its arguments.

Run Flow now.

$ npm run-script flow

> get_started@ flow /Users/DanBefore/Developer/flow/get_started
> flow; test $? -eq 0 -o $? -eq 2

No errors!

You’ve told Flow all it needs to know about _. so it understands what it sees and moves on.

Flow Weak Mode

If you have tens of thousands of lines of unchecked Javascript, running Flow on them may produce so many errors that it would be ovwewhleming to try to correct them all.

But it’d be nice to correct some of the more egregious errors. To target those, Flow has something called a weak mode.

The difference between weak mode and normal mode is that:

  • in regular mode Flow infers types for all missing annotations, and produce errors whenever it detects a mismatch.
  • in weak mode Flow does much less type inference. It still infers types within functions, but otherwise treats unannotated variables as any, meaning no typechecking happens with them.

Calling Weak Mode

// @flow weak

That’s it.

Under weak mode, you’ll likely find some of these errors:

  • potentially null or undefined values
  • primitive type issues, like true + 9
  • cases where Flow just doesn’t understand your code

Once you get all those errors cleaned up, you’ll be ready to run Flow in regular mode.

For More Info

The mostly-human-readable documentation is at flowtype.org.

Comments