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:
- an interfaces directory inside the get_started directory, and
- 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
orundefined
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.