Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flow typechecking as a webpack plugin #1152

Closed
wants to merge 75 commits into from
Closed
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
10ec304
Setup a plugin to run a flow checking lifecycle
rricard Dec 4, 2016
c7eacf8
Make flow fail on CI builds
rricard Dec 4, 2016
b0c39e7
Run flow as a server and check with status
rricard Dec 4, 2016
de16269
Add flow ignore comment to the start hints
rricard Dec 4, 2016
31b2e04
Add flow-typed resolution at start
rricard Dec 4, 2016
d4763a5
Reset server stderr on restart
rricard Dec 4, 2016
2f34a67
Move options as explicit props of the plugin
rricard Dec 4, 2016
79b3b86
Use arrow functions
rricard Dec 4, 2016
d004f2d
Refactor using promises and no class attrs
rricard Dec 4, 2016
ec8d230
Add documentation for out-of-the-box flow
rricard Dec 5, 2016
bb39b19
Schedule project init to complete before 1st check
rricard Dec 5, 2016
8878502
Process code-based flow checks
rricard Dec 5, 2016
6a5cfc1
Get flow version internally without config
rricard Dec 5, 2016
513c0a9
Check global flow version
rricard Dec 5, 2016
719ac82
Add flow-typed to gitignore automatically
rricard Dec 5, 2016
c76f15e
Add optional flow to end to end testing
rricard Dec 5, 2016
ba7a05a
Run flow even if init failed
rricard Dec 5, 2016
2362346
Remove fbjs ignores now that it's fixed
rricard Dec 5, 2016
f83fa6a
Remove flow-typed lib import (known by default!)
rricard Dec 5, 2016
f42c5cc
In e2e: assert flow is correctly showing errors
rricard Dec 5, 2016
f9ae1f0
Only change gitignore if we create a flowconfig
rricard Dec 8, 2016
c5e2102
Ensure the init promise is correctly triggered
rricard Dec 9, 2016
e4e2111
Don't create an uncatched promise
rricard Dec 9, 2016
f9bf33d
Don't reinit flow in a child compilation
rricard Dec 9, 2016
4448e98
Remove flow-typed/ dir assertion
rricard Dec 12, 2016
db929a1
Reapply a lost change from #1233 during rebase
rricard Dec 12, 2016
eac318d
Consistent ESLint capitalization
rricard Dec 12, 2016
054357f
Run flow version check before the rest
rricard Dec 12, 2016
b3e5609
Transmit via warnings why Flow was disabled
rricard Dec 12, 2016
cabeadb
Don't fail on tarball react-scripts
rricard Dec 12, 2016
fa6d3c8
Celebrate when the flow tests pass!
rricard Dec 12, 2016
05ae460
Show npm commands in cyan
rricard Dec 12, 2016
e30a959
"Hide" flow err suppression in dev utils
rricard Dec 12, 2016
3ed5f2c
Only highlight the npm word
rricard Dec 12, 2016
026e581
Make flow-typed provision sequential
rricard Dec 13, 2016
6e44124
Redisign & simplify flow e2e tests
rricard Dec 13, 2016
3047218
Correctly ignore invalid comp errors
rricard Dec 13, 2016
09c2c13
Add ref to an issue comment on the silencing
rricard Dec 13, 2016
3949fb3
Remove debugging command in e2e.sh
rricard Dec 13, 2016
159638b
Remove planning to add the feature in doc[ci skip]
rricard Dec 13, 2016
5105c29
No build test for flow (duplicate)
rricard Dec 13, 2016
4fa4569
Simpler second flow test
rricard Dec 13, 2016
ba483c6
After eject, the .flowconfig should stay
rricard Dec 14, 2016
a48e8c3
Use Flow 0.37.0
rricard Dec 14, 2016
9e1957e
Directly target the flow-typed executable
rricard Dec 14, 2016
c834095
Revert "Directly target the flow-typed executable"
rricard Dec 14, 2016
f5d3982
Find flow-typed in the npm flat struct
rricard Dec 15, 2016
cfc1115
Define flow-typed path with project root
rricard Dec 15, 2016
030f7c1
Resolve flow-typed via module resolution
rricard Dec 15, 2016
37444f2
Remove unneeded complexity
rricard Dec 15, 2016
08ca783
Make run flow-typed executable
rricard Dec 15, 2016
51cca10
Don't put flow-typed on gitignore
Gregoor Mar 18, 2017
7e33f9d
Merge branch 'master' into flow
rricard Apr 22, 2017
a3a78f7
Appease linter
Timer Apr 22, 2017
eb9e341
Update flow
Timer Apr 22, 2017
137f59f
Add basic flow type checking
Timer Apr 22, 2017
2fd2dbc
Adjust format function
Timer Apr 22, 2017
7c5749d
Remove flow-typed stub
Timer Apr 22, 2017
f4299ef
Use access instead of stat to prevent dual-compile
Timer Apr 23, 2017
a3fb2df
Add some friendly comments
Timer Apr 23, 2017
de99610
Lock down flow version
Timer Apr 23, 2017
29628ef
Update comment logs
Timer Apr 23, 2017
30494dc
Remove old method
Timer Apr 23, 2017
f850266
Remove from e2e
Timer Apr 23, 2017
8d1f515
Check flow version for performance
Timer Apr 23, 2017
1df2cb5
Remove warning prefix for flow messages
Timer Apr 23, 2017
70ad8dd
Update README
Timer Apr 23, 2017
45841cd
Don't double compute
Timer Apr 23, 2017
dab6f9d
Fix linter errors
Timer Apr 23, 2017
1cb808e
Use default perm check
Timer Apr 23, 2017
e94387c
Handle already running server since it exits with a non-zero
Timer Apr 24, 2017
45e6c29
Reduce nesting
Timer Apr 24, 2017
fb4735c
Simulate a mutex for startFlow
Timer Apr 24, 2017
800af94
Fix unhandled rejection
Timer May 4, 2017
ad625f1
Upgrade flow
Timer May 4, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions packages/react-dev-utils/FlowTypecheckPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
var fs = require('fs');
var path = require('path');
var chalk = require('chalk');
var childProcess = require('child_process');
var flowBinPath = require('flow-bin');
const flowTypedPath = path.join(__dirname, 'runFlowTyped.js');

function stripFlowLoadingIndicators(message) {
var newMessage = message;
var launchingIndex = newMessage.indexOf("Launching Flow server for");
if (launchingIndex >= 0) {
newMessage = newMessage.slice(0, launchingIndex);
}
var stillIndex = newMessage.indexOf("flow is still initializing");
if (stillIndex >= 0) {
newMessage = newMessage.slice(0, stillIndex);
}
var notRespIndex = newMessage.indexOf("The flow server is not responding");
if (notRespIndex >= 0) {
newMessage = newMessage.slice(0, notRespIndex);
}
return newMessage;
}

function execOneTime(command, args, options) {
return new Promise((resolve, reject) => {
var stdout = new Buffer("");
var stderr = new Buffer("");
var oneTimeProcess = childProcess.spawn(
command,
args,
options
);
oneTimeProcess.stdout.on('data', chunk => {
stdout = Buffer.concat([stdout, chunk]);
});
oneTimeProcess.stderr.on('data', chunk => {
stderr = Buffer.concat([stderr, chunk]);
});
oneTimeProcess.on('error', error => reject(error));
oneTimeProcess.on('exit', code => {
switch (code) {
case 0:
return resolve(stdout);
default:
return reject(new Error(
Buffer.concat([stdout, stderr]).toString()
));
}
});
});
}

function writeFileIfDoesNotExist(path, data) {
return new Promise((resolve, reject) => {
fs.exists(path, exists => {
if (!exists) {
fs.writeFile(path, data, err => {
if (err) {
return reject(err);
}
resolve(true);
});
} else {
resolve(false);
}
});
});
}

function writeInFileIfNotPresent(path, contentToAssert, contentToAppend) {
return new Promise((resolve, reject) => {
fs.exists(path, exists => {
if (!exists) {
fs.writeFile(path, contentToAppend, err => {
if (err) {
return reject(err);
}
resolve(true);
});
} else {
fs.readFile(path, (err, existingContent) => {
if (err) {
return reject(err);
}
if (existingContent.indexOf(contentToAssert) < 0) {
fs.appendFile(path, contentToAppend, err => {
if (err) {
return reject(err);
}
resolve(true);
});
} else {
resolve(false);
}
});
}
});
});
}

function getFlowVersion(options) {
return execOneTime(
(options || {}).global ? "flow" : flowBinPath,
['version', '--json']
)
.then(rawData => JSON.parse(rawData))
.then(versionData => versionData.semver);
}

function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) {
const flowconfigPath = path.join(projectPath, '.flowconfig');
const gitignorePath = path.join(projectPath, '.gitignore');
return getFlowVersion().then(localVersion =>
getFlowVersion({global: true}).catch(() => localVersion)
.then(globalVersion =>
globalVersion !== localVersion ?
Promise.reject(new Error(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does global flow version matter?

'Flow integration was disabled because the global Flow version does not match.\n' +
'You may either remove the global Flow installation or install a compatible version:\n' +
' ' + chalk.cyan('npm') + ' install -g flow-bin@' + localVersion
)) :
localVersion
)
)
.then(localVersion => Promise.all([
writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n'))
.then(wroteFlowconfig => wroteFlowconfig ?
writeInFileIfNotPresent(gitignorePath, 'flow-typed', 'flow-typed/npm') :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we need to not do this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it should always use the local version? I'm trying to wrap my head around the remaining issues to see if I can help get this merged 🙃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this comment: #1152 (comment)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Dan! I dropped the lines that added flow-typed to the gitignore. Now I'm not completely sure what the protocol is for changes on a PR, but since it looked like a new PR is my only option, that's what I did:
#1854

false
),
execOneTime(
flowTypedPath,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if flow-typed is super valuable, and I dislike that it dirties the application directory and modifies the .gitignore automatically.
Not to mention, does this break offline development? A save will cause package definitions to be downloaded/installed.

Can you provide me with a strong argument why a flow user can't scaffold this themselves?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, developing in the package seems to behave incorrectly:

Joes-MacBook-Pro:create-react-app joe$ pwd
/Users/joe/Documents/Development/OSS/create-react-app
Joes-MacBook-Pro:create-react-app joe$ ls flow-typed/npm/
babel-eslint_vx.x.x.js             eslint-plugin-react_vx.x.x.js
eslint-config-react-app_vx.x.x.js  eslint_vx.x.x.js
eslint-plugin-flowtype_vx.x.x.js   jest_v17.x.x.js
eslint-plugin-import_vx.x.x.js     lerna-changelog_vx.x.x.js
eslint-plugin-jsx-a11y_vx.x.x.js   lerna_vx.x.x.js

I would expect this under packages/react-scripts/template, and with react-scripts's packages.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the alternative to using flow-typed? AFAIK it is explicitly encouraged by Flow team as the official way to add typings for dependencies, and in some cases it is intentional that you can modify it by hand. I don't think hiding it would be appropriate.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the flow-typed directory is not supposed to be gitignored, flow-typed is not meant to be a package manager, the end goal is to be a workflow tool to consume and contribute to the flow-typed repository.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there is an alternative but there's not many packages in the repository anyway. It's no where near what DefinitelyTyped contains.

I vote we remove it unless there's a compelling reason people wouldn't do it themselves. And I believe James just said we're using it wrong anyway.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Timer It'll eventually have all of DefinitelyTyped inside as well as ones that were better vetted.

I would not remove flow-typed from the repository– that would be a mistake. All you need to do is remove it from the .gitignore.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thejameskyle okay, thanks for the information! Sounds like it's staying then. Thanks. :)

['install', '--overwrite', '--flowVersion=' + localVersion],
{ cwd: projectPath }
)
// This operation will fail if react-scripts is a path to a tarball in the
// package.json (like in End To End testing!). So we swallow this error.
// See https://github.com/flowtype/flow-typed/issues/399#issuecomment-266766678
.catch((e) => /(invalid comparator)|(unable to rebase the local cache repo)/i.test(e.message) ? true : Promise.reject(e))
.then(() => Promise.all(
Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime(
flowTypedPath,
[
'install',
packageName + '@' + otherFlowTypedDefs[packageName],
'--overwrite',
'--flowVersion=' + localVersion
],
{ cwd: projectPath }
))
))
]));
}

function flowCheck(projectPath) {
return execOneTime(
flowBinPath,
['status', '--color=always'],
{ cwd: projectPath }
);
}

function FlowTypecheckPlugin(options) {
options = options || {};
// Contents of the generated .flowconfig if it doesn't exist
this.flowconfig = options.flowconfig || [];
// Load up other flow-typed defs outside of the package.json (implicit packages behind react-scripts)
// Key is the package name, value is the version number
this.otherFlowTypedDefs = options.otherFlowTypedDefs || {};
}

FlowTypecheckPlugin.prototype.apply = function(compiler) {
var flowActiveOnProject = false;
var flowInitialized = false;
var flowInitError = null;
var flowInitializationPromise;
var flowShouldRun = false;
var flowErrorOutput = null;

// During module traversal, assert the presence of an @ flow in a module
compiler.plugin('compilation', (compilation, params) => {
compilation.plugin('normal-module-loader', (loaderContext, module) => {
// We're only checking the presence of flow in non-node_modules
// (some dependencies may keep their flow comments, we don't want to match them)
if (module.resource.indexOf("node_modules") < 0) {
// We use webpack's cached FileSystem to avoid slowing down compilation
loaderContext.fs.readFile(module.resource, (err, data) => {
if (data && data.toString().indexOf('@flow') >= 0) {
if (!flowActiveOnProject) {
flowInitializationPromise = (!compiler.parentCompilation ?
initializeFlow(
compiler.options.context, this.flowconfig, this.otherFlowTypedDefs
) : Promise.resolve()
)
.then(() => {
flowInitialized = true;
}, e => {
flowInitError = e;
return Promise.reject(e);
});
flowActiveOnProject = true;
}
flowShouldRun = true;
}
});
}
})
});

// While emitting, run a flow check if flow has been detected
compiler.plugin('emit', (compilation, callback) => {
// Only if a file with @ flow has been changed
if (flowShouldRun) {
flowShouldRun = false;
(flowInitialized ?
(flowInitError ? Promise.reject(flowInitError) : Promise.resolve()) :
flowInitializationPromise)
.then(
() => flowCheck(compiler.options.context),
e => Promise.reject(e) // don't run a check if init errored, just carry the error
)
.then(() => {
flowErrorOutput = null;
compilation.flowPassed = true;
}, error => {
flowErrorOutput = stripFlowLoadingIndicators(error.message);
compilation.warnings.push(flowErrorOutput);
})
.then(callback);
} else {
// Output a warning if flow failed in a previous run
if (flowErrorOutput) {
compilation.warnings.push(flowErrorOutput);
} else {
compilation.flowPassed = true;
}
callback();
}
});
};

module.exports = FlowTypecheckPlugin;
18 changes: 15 additions & 3 deletions packages/react-dev-utils/formatWebpackMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ function isLikelyASyntaxError(message) {
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
}

// Used to detect flow errors we want to swallow
function isFlowIntegrationErrorMessage(message) {
return /^flow integration was disabled/i.test(message);
}

// Cleans up webpack error messages.
function formatMessage(message) {
var lines = message.split('\n');
Expand Down Expand Up @@ -113,15 +118,22 @@ function formatMessage(message) {
}

function formatWebpackMessages(json) {
var formattedErrors = json.errors.map(function(message) {
var formattedErrors = json.errors
.filter(m => !isFlowIntegrationErrorMessage(m))
.map(function(message) {
return 'Error in ' + formatMessage(message)
});
var formattedWarnings = json.warnings.map(function(message) {
var formattedWarnings = json.warnings
.filter(m => !isFlowIntegrationErrorMessage(m))
.map(function(message) {
return 'Warning in ' + formatMessage(message)
});
var flowIntegrationError = [].concat(json.errors, json.warnings)
.find(isFlowIntegrationErrorMessage);
var result = {
errors: formattedErrors,
warnings: formattedWarnings
warnings: formattedWarnings,
flowIntegrationError: flowIntegrationError
};
if (result.errors.some(isLikelyASyntaxError)) {
// If there are any syntax errors, show just them.
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
},
"files": [
"clearConsole.js",
"FlowTypecheckPlugin.js",
"runFlowTyped.js",
"checkRequiredFiles.js",
"formatWebpackMessages.js",
"getProcessForPort.js",
Expand All @@ -26,6 +28,8 @@
"ansi-html": "0.0.5",
"chalk": "1.1.3",
"escape-string-regexp": "1.0.5",
"flow-bin": "^0.37.0",
"flow-typed": "^2.0.0",
"html-entities": "1.2.0",
"opn": "4.0.2",
"sockjs-client": "1.0.3",
Expand Down
4 changes: 4 additions & 0 deletions packages/react-dev-utils/runFlowTyped.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node
var flowTyped = require('flow-typed');

flowTyped.runCLI();
9 changes: 8 additions & 1 deletion packages/react-scripts/config/webpack.config.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
var FlowTypecheckPlugin = require('react-dev-utils/FlowTypecheckPlugin');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the other imports uses const, so this not also? :)

var getClientEnvironment = require('./env');
var paths = require('./paths');

Expand Down Expand Up @@ -226,7 +227,13 @@ module.exports = {
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebookincubator/create-react-app/issues/186
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
// Trigger some typechecking if a file matches with an @ flow comment
new FlowTypecheckPlugin({
otherFlowTypedDefs: {
jest: "17.0.0"
}
})
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
Expand Down
7 changes: 7 additions & 0 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var ManifestPlugin = require('webpack-manifest-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var FlowTypecheckPlugin = require('react-dev-utils/FlowTypecheckPlugin');
var url = require('url');
var paths = require('./paths');
var getClientEnvironment = require('./env');
Expand Down Expand Up @@ -268,6 +269,12 @@ module.exports = {
// having to parse `index.html`.
new ManifestPlugin({
fileName: 'asset-manifest.json'
}),
// Run Flow only if we see some @ flow annotations, will error on CI
new FlowTypecheckPlugin({
otherFlowTypedDefs: {
jest: "17.0.0"
}
})
],
// Some libraries import Node modules but don't use them in the browser.
Expand Down
12 changes: 12 additions & 0 deletions packages/react-scripts/scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ function setupCompiler(host, port, protocol) {

if (isSuccessful) {
console.log(chalk.green('Compiled successfully!'));
if (messages.isFlowIntegrationErrorMessage) {
console.log(chalk.yellow('Flow checks were skipped.'));
}
if (stats.compilation.flowPassed) {
console.log(chalk.green('Flow checks have passed.'));
Copy link
Contributor

@Timer Timer Feb 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if saying that flow checks have passed is particularly useful. Looks like noise in my output, especially since it shows up even when I haven't decorated anything with /* @flow */. It gives you an unsafe sense of security.

Can we just remove this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagined that Flow users unaware of the extent of integration might think it didn't run. However, they'd probably make errors often enough to notice those are being reported. So maybe you're right.

}
}

if (showInstructions) {
Expand Down Expand Up @@ -131,6 +137,12 @@ function setupCompiler(host, port, protocol) {
console.log('You may use special comments to disable some warnings.');
console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.');
console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.');
console.log('Use ' + chalk.yellow('// $FlowFixMe') + ' to ignore flow-related warnings on the next line.');
}

// We print why flow was disabled
if (messages.isFlowIntegrationErrorMessage) {
console.log(messages.isFlowIntegrationErrorMessage);
}
});
}
Expand Down
12 changes: 5 additions & 7 deletions packages/react-scripts/template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,17 +479,15 @@ Now you are ready to use the imported React Bootstrap components within your com

Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept.

Recent versions of [Flow](http://flowtype.org/) work with Create React App projects out of the box.
Flow typing is now supported out of the box. All you have to do is add the `/* @flow */` comment on top of files you
want to typecheck. If no `.flowconfig` is present, one will be generated for you. The script will also download type
definitions from [flow-typed](https://github.com/flowtype/flow-typed) automatically.

To add Flow to a Create React App project, follow these steps:
Flow errors will show up alongside ESLint errors as you work on your application.

1. Run `npm install --save-dev flow-bin`.
2. Add `"flow": "flow"` to the `scripts` section of your `package.json`.
3. Add `// @flow` to any files you want to type check (for example, to `src/App.js`).
>Note: If your global flow installation version differs from react-scripts's flow version, you may experience slower compilation times while going back and forth between your app and your IDE (that may use your global flow). Ensure you have the same `flow-bin` version [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-dev-utils/package.json).

Now you can run `npm run flow` to check the files for type errors.
You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience.
In the future we plan to integrate it into Create React App even more closely.

To learn more about Flow, check out [its documentation](https://flowtype.org/).

Expand Down
Loading