Flow

& bringing types to your JavaScript

William Beard

Senior Software Engineer @ CA Agile Central
twitter: @thewillbeard

Team's Stack

  • React
  • Redux
  • Immutable.js
  • Clojure

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

  1. Creates a dependency graph of your modules.

  2. Runs the modules' interactions through a rules engine.

  3. Screams loudly if you break the rules.

Why it works for us

  1. No more spilled cheetos.

  2. Screams loudly when we pass the wrong thing to a function.

  3. Know the shape of the payload an action expects.

  4. 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

  1. <PROJECT_ROOT>/flow-typed

  2. [libs] in .flowconfig

  3. 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

Made with Slides.com