React + flow + Webpackの最小構成

## はじめに

flowを使ったことなかったんですが、React Nativeなどを扱うことになってきて、flowを学ばないとまずいと思い、Webでもちょっとやってみました。

それぞれのファイルの意味なども可能な限り説明していければと思います。

## 環境

- React 15.4
- Webpack 2.2-rc
- flow 0.37
- NodeJS 6.X~

## 構成

```
$ tree .
.
├── index.html
├── package.json
├── src
│   ├── Hello.jsx
│   └── Index.jsx
├── .babelrc
├── .flowconfig
├── webpack.config.dev.js
└── webpack.config.prod.js
```

サンプルコードはこちら。
https://github.com/uryyyyyyy/react-redux-js-sample/tree/helloWorld

### package.json

```json:package.json
{
  "name": "react-redux-sample-js",
  "version": "0.1.0",
  "scripts": {
    "build": "webpack --config ./webpack.config.dev.js",
    "build:prod": "webpack --config ./webpack.config.prod.js",
    "flow": "flow",
    "flow-typed": "flow-typed install"
  },
  "license": "MIT",
  "dependencies": {
    "babel-polyfill": "^6.20.0",
    "react": "^15.4.1",
    "react-dom": "^15.4.1"
  },
  "devDependencies": {
    "flow-typed": "^2.0.0",
    "babel-core": "^6.20.0",
    "babel-loader": "^6.2.9",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "flow-bin": "^0.37.0",
    "webpack": "^2.2.0-rc.3"
  }
}
```

- webpack
  - JSのビルドツール。ES Modules（import/export）やCommonJS（require）などのモジュール形式で書かれたファイルをバンドルしてブラウザで動くようにしてくれたりする。
- babel-polyfill
  - babelで生成されたコードを動かす時に便利なpolyfill
- babel-core
  - babelのコア
- babel-loader
  - webpack用のbabelプラグイン
- babel-preset-es2015
  - es2015 -> ES5への変換をするpluginのpreset
- babel-preset-react
  - reactの記法を解釈するpluginのpreset
- flow-bin
  - flowの実行バイナリ
- flow-typed
  - flow-typedから型定義ファイルを良い感じに取得するツール

`npm run build` とすれば、開発用にSourceMap付きでビルドされます。
`npm run build:prod` とすれば、プロダクション向けにminifyしてビルドされます。

### flow-typed

さきほど[flow-typed](https://github.com/flowtype/flow-typed)という単語が出ましたが、これは3rd partyライブラリでのflowの型定義を管理する場所となっています。
TypeScriptでいう[DefinitlyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped)ですね。

TypeScriptと比べると数が明らかに少ないことが懸念としてありますが、Facebookパワーでbabelやreactとの相性はバッチリです。

使い方は`flow-typed install`（上記の僕のリポジトリでは、npmコマンド化しています）で、dependenciesに入っているライブラリの型定義を「もしあれば」取ってきて、`flow-typed`ディレクトリに置かれます。

※ちなみに、上記コマンドを叩いてみるとわかりますが、dom操作系やreactの型定義は入ってきません。しかし、実際にflowコマンドを叩くとちゃんと型チェックを行っています。
これはどういうことかというと、実はflow自体に最初から型定義がいくつか入っているのです。（ここにreactのものまで入っているのはちょっと謎ですが、facebookパワーです。）

最初から入っているコード群はここです。（https://github.com/facebook/flow/tree/v0.37/lib ）

ちょっと見ると、serviceWorkerやPromise系やfetch APIなんかも最初から入っていますね。「古いブラウザの対応は、babelでpolyfill入れるから気にしない」みたいなことなんでしょうか。このあたりもfacebookパワーです。

#### 余談 polyfillについて

まず、fetchに関しては外部のpolyfillを入れてくれとの事っぽいですね。https://github.com/babel/babel/issues/2113

また、Promiseなどのpolyfillとしてよく使われる[babel-polyfill](https://babeljs.io/docs/usage/polyfill/ )ですが、core-jsの一部と、generator構文をブラウザなどでも動くようにするpluginが入っているようです。
「core-jsの一部ってどこまでだよ！」というのは頑張ってコードを追っかけてもらうとして、まぁ動けばどれでも良いと思うので、とりあえずbabel-polyfill使っておきましょう。もし困ったらcore-jsを全部取り込んでおけばいいと思います。

### webpack.config.dev.js

こちらはwebpackの設定を記載するものです。

```js:webpack.config.dev.js
module.exports = {
  entry: './src/Index.jsx',
  output: {
    filename: 'bundle.js',
    path: './dist'
  },
  devtool: "source-map",
  resolve: {
    extensions: [".jsx", ".js"]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      }
    ]
  },
  performance: {
    hints:false
  }
};
```

src/Index.tsx をentry pointとして、`.jsx, .js`ファイルをビルドしてくれます。ビルド時にbabelを使うようにbabel-loaderをmoduleに入れています。
また、ここではソースマップも吐くように設定しています。

performanceのところは、なぜかwebpack2系の途中から、ファイルサイズが大きいと注意してくるようになって邪魔なのでアラートが出ないようにしています。

（webpack.config.prod.jsはここでは省略しますが、本番向けのminify設定などが入っていて、sourcemapは外してあります。）

### .babelrc

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

babelの設定ファイルです。
ここでは、package.jsonで取り込んだpresetの2つと、`preset-react`に入ってるはずなのになぜかデフォルトで有効でなかった`transform-flow-strip-types`（jsコードに書かれたflowの型定義を消して普通のjsにするplugin）を有効にしています。

### .flowconfig

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

[include]

[libs]

[options]

[version]
>=0.37.0
```

こちらはflowの設定ファイルです。
- ignore　→型チェックしたくないファイル
- include →追加で型チェックしたいファイル
- libs → 型定義などを入れておくところ
- options →何か設定系
- versions →flow-binのバージョン指定

長かったですが設定ファイルは以上です。

### source

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <title>React minimal</title>
</head>
<body>
<div id="app"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
```

```js:src/Index.jsx
// @flow
import "babel-polyfill";

import React from 'react';
import ReactDOM from 'react-dom';
import {Hello} from './Hello';

ReactDOM.render(<Hello content="hello world"/>, document.getElementById('app'));
```

ここでは、HelloというReact Componentを、idがappのDOMに配置するように書いています。
DOMに設置するときはReact-domを使います。（React-Nativeとかの場合などがあるので、coreとdomとで分かれています。）

```js:src/Hello.jsx
// @flow
import React, {Component} from 'react';

type Props = {
  content: string
};

type State = {
  count: number
};

export class Hello extends Component<void, Props, State> {
  state: State;

  constructor(props: Props, context: any) {
    super(props, context);
    this.state = {count: 0};
  }

  render() {
    return <div>{`${this.props.content} - ${this.state.count}`}</div>
  }
}
```

単純な例です。
Prosが癖がありますが、上流コンポーネント（今回はIndex.tsx）から、contentフィールドにstring値が渡ってくるので、それを表示するだけの簡単なコンポーネントです。stateは試しに入れてますが、何も変更せず表示するだけです。

## flowを掛けてみる

```
$ npm run flow

> react-redux-sample-js@0.1.0 flow git/react-redux-js-sample
> flow

No errors!
```

こんな感じですね。
ちなみに失敗すると

```
src/Index.jsx:8
  8: ReactDOM.render(<Hello contenta="hello world"/>, document.getElementById('app'));
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `Hello`
 12: export class Hello extends Component<void, Props, State> {
                                                ^^^^^ property `content`. Property not found in. See: src/Hello.jsx:12
  8: ReactDOM.render(<Hello contenta="hello world"/>, document.getElementById('app'));
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `Hello`
```

みたいなのが出ます。

## Buildしてみる

```
$ npm run build
...

Hash: 9cdcb61d73eaef430924
Version: webpack 2.2.0-rc.3
Time: 2835ms
        Asset    Size  Chunks                    Chunk Names
    bundle.js  973 kB       0  [emitted]  [big]  main
bundle.js.map  1.2 MB       0  [emitted]         main

...
```

こんな感じになれば成功です。dist/bundle.js と、そのソースマップが吐かれます。
あとはindex.htmlを開くと、bundle.jsが読み込まれて、画面に「hello world - 0」が表示されます。

consoleを開くと、source mapで元のコードが追えることが確認できます。

## まとめ

flowとかbabelとかは、facebook独自のエコシステムでお互いに不思議な依存をしていて、把握しにくいなとちょっと思っています。。