flowの始め方

ということで、[前回](http://qiita.com/kiida/items/405bb07c4b52bfee0219)に引き続きReact Confで紹介されてたものを試すシリーズの第二回です。

[flow](https://flowtype.org)の簡単な設定とeslint/babel/precommitフック/vimとの連携のみです。深い使い方は(今のところは?)書きません。flowは既に色々なところで紹介されてますが、気にせず行きます。

~~ちなみに今回もReactは出てきません。~~(追記で少しだけ触れてしまいました)

# flowとは

* JavaScript用のtypeチェッカー
* ファイル単位で適用できるため、既存のプロジェクトにも気軽に導入できる
* eslintやbabelとも連携できるので既存プロジェクトに導入しやすい
* 色んなエディタ/IDEでもサポートされている
* [この辺](https://discuss.reactjs.org/t/if-typescript-is-so-great-how-come-all-notable-reactjs-projects-use-babel/4887)を見る限りFacebookのデベロッパ的にはTypeScriptはあんまり推奨してないっぽい
* [GitHubのstarヒストリー](http://www.timqian.com/star-history/#facebook/flow&Microsoft/TypeScript)を見る限りはTypeScriptに分がありそう
* 数日程度弄った限りだと基本的なシンタックスは凄く簡単。でも複雑なことも色々できる模様。

# 使い方

[前回](http://qiita.com/kiida/items/405bb07c4b52bfee0219)eslintとprettierを導入した環境に追加していきます。
ソースは[ここ](https://github.com/daikikohara/learning-from-reactconf2017/tree/master/flow)にあります。便宜上ディレクトリは分けました。

## 単体で使う

まずはflowをインストールして初期化します。

```
yarn add -D flow-bin 
flow init
```

空の.flowconfigができるので、以下のようにnode_modulesをチェック対象外にします。

```.flowconfig
[ignore]
.*/node_modules/.*

[include]

[libs]

[options]
```

テスト用にエラーになるjsファイルを作ってみます。

```javascript:index.js
/* @flow */

var a: number = 1;

a = 'string';
```

先頭のコメントはflowのチェック対象にするために必要です。

この状態で`flow check`コマンドを実行すると`number`で宣言した変数に`string`を入れようとしているので、エラーになります。

```bash
$ flow check
index.js:5
  5: a = 'string';
         ^^^^^^^^ string. This type is incompatible with
  3: var a: number = 1;
            ^^^^^^ number


Found 1 error
```

## babelと連携する

babelと連携するには[babel-plugin-transform-flow-strip-types](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-flow-strip-types)が必要です。

babel自体とes2015のpresetと一緒に導入してみます。

```bash
yarn add -D babel-cli babel-preset-es2015 babel-plugin-transform-flow-strip-types
```

そして以下のような内容で.babelrcを作ります。

```json:.babelrc
{
  "plugins": ["transform-flow-strip-types"],
  "presets": ["es2015"]
}
```

そしてjsをletを使うように変更してbabelを実行してみます。

```javascript:index.js
/* @flow */

let a: number = 1;

a = "string";
```

```bash
babel index.js --out-dir dist
```

出力された`index.js`を見ると適切に変換されているのがわかります。

```javascript:dist/index.js
"use strict";

var a = 1;

a = "string";
```

## eslintと連携する

このままだと`: number`などのTypeアノテーションがeslintのチェックを通りません。

eslintと連携するには[babel-eslint](https://github.com/babel/babel-eslint)と[eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype)をインストールする必要があります。

```bash
yarn add -D eslint-plugin-flowtype babel-eslint
```

.eslintrc.jsに以下を追加します。中身は明らかなので説明は要らないかと思います。

```.eslintrc.js
  parser: 'babel-eslint',
  extends: ['plugin:flowtype/recommended'],
  plugins: ['flowtype'],
  settings: {
    flowtype: {
      onlyFilesWithFlowAnnotation: true,
    },
  },
```

## precommitフック

前回作ったprecommitフックにflowを追加して、flowのtypeチェックが失敗したらコミットをabortするようにします。

```json:package.json
  "scripts": {
    "precommit": "lint-staged && flow check"
  },
```

この状態でflowでエラーになるファイルをコミットしようとすると、エラーを吐いて止めてくれます。

```
$ git add index.js
$ git commit -m 'flow test'

> husky - npm run -s precommit

 ↓ Running tasks for gitDir [skipped]
   → No staged files match gitDir
 ✔ Running tasks for *.js
index.js:5
  5: a = "string";
         ^^^^^^ string. This type is incompatible with
  3: let a: number = 1;
            ^^^^^^ number


Found 1 error

> husky - pre-commit hook failed (add --no-verify to bypass)
> husky - to debug, use 'npm run precommit'
```


## js以外のリソースに対応する

js以外のリソース(フロントだとcss、バックエンドだとsql等)があると`Required module not found`というエラーが出てしまいます。
こんなときは以下のようなファイルを作って、

```javascript:flow/SQLAsset.js.flow
/* @flow */
declare export default string
```

`.flowconfig`の`options`に以下を追記すると解消できます。

```.flowconfig
[options]
module.name_mapper.extension='sql' -> '<PROJECT_ROOT>/flow/SQLAsset.js.flow'
```

## エディタ連携

色々なエディタがサポートされてるようですが、とりあえずvim連携を試します。これは公式アカウントが提供している[vim-flow](https://github.com/flowtype/vim-flow)を入れればOKです。

以下はdeinの例

```toml:dein.toml
[[plugins]]
repo = 'flowtype/vim-flow'
on_ft = ['javascript', 'javascript.jsx']
```

flowのパスは適宜init.vimや.vimrc等で設定してください。

```vim
let g:flow#flowpath = l:bin_folder . 'flow'
let g:flow#autoclose = 1 " エラーを表示するquickfixのウィンドウをエラーがなくなり次第閉じる
```

これで保存時に自動でflowのチェックが走り、エラーがあるとquickfixウィンドウにエラーを表示してくれます。

シンタックスハイライトは[vim-javascript](https://github.com/pangloss/vim-javascript)が対応しているので、以下を追加するだけでOKです。

```vim
let g:javascript_plugin_flow = 1
```

## 追記

### flow-typed(2017/03/25)

.flowconfigでnode_modules全体をignoreに指定して外すとnode_modules内のモジュールをimportしたときにエラーになります。
これを解決するには[flow-typed](https://github.com/flowtype/flow-typed/)を使います。
一部のライブラリは既にflow-typedを通じて型情報を提供しているので、`yarn add -D flow-typed`してから`flow-typed install`するとpackage.jsonで指定したライブラリのうちflow-typedに対応している型情報をflow-typedディレクトリに入れてくれます。また対応していないものもany型でのstubが作成されるためflowがエラーを吐かなくなります。なお、これはデフォルトで解決できるようになるので、.flowconfigの[libs]セクションにflow-typedディレクトリを明示的に指定する必要はありません。

```bash
yarn add -D flow-typed
flow-typed install
```

### PropTypesをflowに変換する(2017/5/1)

[Reactv15.5.0からPropTypesが別パッケージになりました](https://facebook.github.io/react/blog/2017/04/07/react-v15.5.0.html#migrating-from-react.proptypes)。
PropTypes使うよりもflowでチェックした方が実行前に型チェックができるのでより安全です。[codemod-proptypes-to-flow](https://github.com/billyvg/codemod-proptypes-to-flow)を使うとPropTypesをflowに自動的に変換してくれます。いくつかの制約はありますが、今後はある程度の制約を受け入れてflowに移行したほうが良いのではないかと思います。インストールして実行するだけなので手順は省略します。

### カバレッジを取得(2017/5/1)

[flow-coverage-report](https://github.com/rpl/flow-coverage-report)を使うとflowのtypeのカバレッジを取得することができます。これまたインストールして実行するだけなので手順は省略します。

# 最後に

以上、参考になれば幸いです。
ちなみにprettierはデフォルトでflowに対応しているので、特に設定不要です。