Flow
& bringing types to our JavaScript
William Beard
Engineer @ CA Agile Central
etc., etc., etc.
Some context
You're just not my type
Object is not an Immutable.Map
Immutable.Map is not an Object
Cross-app Actions
Runtime Errors
Goals
- Know when something is Immutable and when not.
- Know what a function expects & what it returns.
- Reduce runtime errors.
Types
What is flow?
A static type checker for JavaScript
- Built in JS types (number, string, boolean, etc).
- Custom JS types (immutable ships with flow type definitions & you can write your own).
- CLI & integration with Atom with nuclide.
- Incrementally introduce into codebase.
- Babel plugin for removing annotations & other cruft.
How it works
-
Creates a dependency graph of your modules.
-
Runs the modules' interactions through a rules engine.
-
Screams loudly if you break the rules.
Why it works for us
-
No more spilled cheetos.
-
Screams loudly when we pass the wrong thing to a function.
-
Know the shape of the payload an action expects.
-
No bugs ever again (LOL). But seriously, no more "cannot read property foo of null".
How to use Flow
cd path/to/your/project
npm install --save-dev flow-bin babel-transform-flow-strip-types
touch .flowconfig
# add // @flow to a file, any file
./node_modules/.bin/flow
Adding types to a file
// @flow
function doABarrelRoll(barrelName: string, timesToRoll: number): string {
return `${barrelName} is doing a barrel role ${timesToRoll} times`;
}
doABarrelRoll({ name: 'Stan' }, 'hi');
flow_test.js:6
6: doABarrelRoll({ name: 'Stan' }, 'hi');
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function call
6: doABarrelRoll({ name: 'Stan' }, 'hi');
^^^^^^^^^^^^^^^^ object literal. This type is incompatible with
2: function doABarrelRoll(barrelName: string, timesToRoll: number): string {
^^^^^^ string
flow_test.js:6
6: doABarrelRoll({ name: 'Stan' }, 'hi');
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function call
6: doABarrelRoll({ name: 'Stan' }, 'hi');
^^^^ string. This type is incompatible with
2: function doABarrelRoll(barrelName: string, timesToRoll: number): string {
^^^^^^ number
Immutable.js types
Get for free when you import immutable.
import { List, Map } from 'immutable';
export const listToArray = (list: List): array => list.toJS();
export const mapToObject = (map: Map): object => map.toJS();
Action Creators
// @flow
import { Map } from 'immutable';
type UserStoryCreatedAction = {
type: string,
payload: Map
};
export function createdUserStory(userStory: Map): UserStoryCreatedAction {
return {
type: 'USER_STORY_CREATED',
payload: userStory
};
};
// @flow
import { createdUserStory } from './userActions';
var userStory = {
title: 'Create a user story',
estimate: 13
};
createdUserStory(userStory);
flow_test.js:9
9: createdUserStory(userStory);
^^^^^^^^^^^^^^^^^^^^^^^^^^^ function call
9: createdUserStory(userStory);
^^^^^^^^^ object literal. This type is incompatible with
7: export function createdUserStory(userStory: Map): UserStoryCreatedAction {
^^^ Map. See: userActions.js:7
Null safety
// @flow
function doABarrelRoll(barrelName: string, timesToRoll: number): ?Object {
return { name: barrelName, roll: timesToRoll };
}
function ultraBarrelRoll(): string {
var barrelRoll = doABarrelRoll('Stan', 3);
return `${barrelRoll.name} is now doing ${barrelRoll.roll} ultra barrel rolls`;
}
ultraBarrelRoll();
flow_test.js:9
9: return `Ultra ${barrelRoll.name} is now doing ${barrelRoll.roll} barrel rolls`;
^^^^ property `name`. Property cannot be accessed
on possibly null value
9: return `Ultra ${barrelRoll.name} is now doing ${barrelRoll.roll} barrel rolls`;
^^^^^^^^^^ null
flow_test.js:9
9: return `Ultra ${barrelRoll.name} is now doing ${barrelRoll.roll} barrel rolls`;
^^^^ property `roll`.
Property cannot be accessed on possibly null value
9: return `Ultra ${barrelRoll.name} is now doing ${barrelRoll.roll} barrel rolls`;
^^^^^^^^^^ null
Custom Types
-
<PROJECT_ROOT>/flow-typed
-
[libs] in .flowconfig
-
Declaration file next to implementation file (why Immtuable types are free)
-
Project Root
-
actions
-
UserStoryActions.js
-
UserStoryActions.js.flow
-
-
-
type UserStoryCreatedAction = {
type: string,
payload: Map
};
.flowconfig
aka Ignore a lot of your node_modules
[version]
Specify a specific version of flow
[version]
0.27.0
[include]
Run analysis on code external to your project
[ignore]
Don't analyze these files. Useful for excluding problems that live in dependencies.
[ignore]
# Malformed json in `test/pkg-bower-json-malformed/bower.json:2`
.*/node_modules/bower-json/.*
# Malformed json in `test/broken.json:11`
.*/node_modules/config-chain/.*
# Malformed json in `test/fixtures/node_modules/invalidPackageJson/package.json: 1`
.*/node_modules/enhanced-resolve/.*
[libs]
Tell flow where your custom types live. As of flow 0.24.0, it will check for custom types in <PROJECT_ROOT>/flow-typed.
[options]
A lot you can do here. tldr; Enable certain es2015+ features.
[options]
# Ignore flow warnings about not supporting decorator types
esproposal.decorators=ignore
# Ignore flow warnings about not support static fields
esproposal.class_static_fields=ignore
# Ignore flow warnings about not supporting instance fields
esproposal.class_instance_fields=ignore
Nuclide
npm install -g flow-bin
apm install nuclide
Flow errors in atom
Autocomplete w/ function signatures
Also Type hinting & jump to definition
npm task
// package.json
"scripts": {
...
"flow": "./node_modules/.bin/flow"
...
}
Results may vary
Speed has been inconsistent. Nuclide can be a pain.
Learn more
Thanks!
@thewillbeard
Flow all up in our javascripts
By willb
Flow all up in our javascripts
- 1,197