I have been using flow to add type safety to my JavaScript for a few months now. I have been using it mainly with my React projects.
Flow is a static typechecker for JavaScript written by facebook. It follows the Unix philosophy of do one thing and do it well.
The precursor to flow
is PropTypes, which is built directly into react
.
PropTypes
why do I want to use flow
?The three key differences between PropTypes and flow are:
flow
can be used in all JavaScript files and not just in React
componentsPropTypes
and at compile time with flow
PropTypes
are quite limited in comparison to flow
For example, it’s possible to specify that a prop is some kind of function with
PropTypes
, but not what parameters that function accepts or what kind of value it might return.
flow
?This is out of the scope of this post, I might write something later, but in the meantime, the docs are a pretty good place to start.
PropTypes
to flow
So you have decided to start introducing flow
into your codebase and migrate
from using PropTypes
to flow
definitions.
Where flow
is opt-in, it means that you can migrate your components one at a
time. This approach does mean that you might only have partial coverage as you
migrate over, which means that you might miss things that would otherwise error.
These errors would also not be caught at runtime by PropTypes
as they will
have been removed as part of the migration.
One solution is to write both PropTypes
and flow
definitions, but let’s be
honest that sucks!
PropTypes
from flow
definitionsChances are that if you are using flow
you will also be using babel.
Thankfully there is a babel
plugin, babel-plugin-flow-react-proptypes, that
will automatically add PropTypes
from your flow
definitions, so you don’t
have to.
It works the same as any other babel
plugin, so you start by installing it as
a dependency.
yarn add --dev babel-plugin-flow-react-proptypes
Then by adding it to your babelrc
.
{
"presets": ["..."],
"plugins": ["flow-react-proptypes"]
}
With this plugin enabled you have maximised your type checking coverage to
include both compile time (flow
) and runtime (PropTypes
), this also has the
added affect of fixing the issue with migration that was mentioned above.
PropTypes
from flow
definitions in npm
packagesI have extended my use of the babel-plugin-flow-react-proptypes
to include
adding PropTypes
from my flow
definitions, for my React
components that I
publish to npm
.
This works pretty much the same as when you are building a website, but where
you would normally be using something like webpack to bundle, watch and update
your bundles, instead you use the babel-cli to compile your code down to ES5
JavaScript, ready for publishing to npm
.
This can be achieved by first installing the babel-cli
and
babel-plugin-flow-react-proptypes
.
yarn add --dev babel-cli babel-plugin-flow-react-proptypes rimraf
rimraf is the
rm -rf
util for node.js
Then by setting up your babelrc
.
{
"presets": ["..."],
"plugins": ["flow-react-proptypes"]
}
And finally setting up builds command in your package.json
.
{
"scripts": {
"build": "npm run build:clean && npm run build:dist",
"build:clean": "rimraf dist",
"build:dist": "babel src --out-dir dist"
}
}
That’s all there is to it!
This is taken from a react component I built that renders markdown, react-markdown-renderer.
/* @flow */
import React from 'react';
import Remarkable from 'remarkable';
type PropsType = {
markdown: string,
options?: Object,
};
export default function MarkdownRenderer({
markdown,
options = {},
...props
}: PropsType) {
const remarkable = new Remarkable(options);
const html = remarkable.render(markdown);
return <div {...props} dangerouslySetInnerHTML={{ __html: html }} />;
}
Source.
function MarkdownRenderer(_ref) {
var markdown = _ref.markdown,
_ref$options = _ref.options,
options = _ref$options === undefined ? {} : _ref$options,
props = _objectWithoutProperties(_ref, ['markdown', 'options']);
var remarkable = new _remarkable2.default(options);
var html = remarkable.render(markdown);
return _react2.default.createElement(
'div',
_extends({}, props, { dangerouslySetInnerHTML: { __html: html } })
);
}
MarkdownRenderer.propTypes = {
markdown: require('react').PropTypes.string.isRequired,
options: require('react').PropTypes.object,
};
Compiled Source. (Some code has been removed to make reading easier).
npm
packagesNow that we have shipped PropTypes
in our npm
packages we can extend our
packages to also include our flow
definitions.
You start by install flow-copy-source.
yarn add flow-copy-source --dev
And finally setting up builds command in your package.json
.
{
"scripts": {
"build": "npm run build:clean && npm run build:dist",
"build:clean": "rimraf dist",
"build:dist": "babel src --out-dir dist",
"build:flow": "flow-copy-source src dist"
}
}
flow-copy-source
will (by default) copy all *.js
files in src
and copy
them into the target directory dist
, all while preserving the original
directory hierarchy. The files that are copied get a different file-ending,
called *.js.flow
.
This works because flow
resolves modules in the same way as node
does. If
you are importing my-lib
, by default flow
will look into my-lib
and try to
find the type definitions, but unfortunately all of our files get compiled into
ES5 code, so we loose all of our type definitions.
Luckily if flow
finds files with a *.js.flow
file ending, it will prefer
them over the actual *.js
file. So by including the additional files, we will
inform flow
about the types that are exposed by our ES5 file.
ryyppy wrote a great article on this, it’s well worth a read.
flow
PropTypes
to flow