はじめに
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
{
"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という単語が出ましたが、これは3rd partyライブラリでのflowの型定義を管理する場所となっています。
TypeScriptでいうDefinitlyTypedですね。
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ですが、core-jsの一部と、generator構文をブラウザなどでも動くようにするpluginが入っているようです。
「core-jsの一部ってどこまでだよ!」というのは頑張ってコードを追っかけてもらうとして、まぁ動けばどれでも良いと思うので、とりあえずbabel-polyfill使っておきましょう。もし困ったらcore-jsを全部取り込んでおけばいいと思います。
webpack.config.dev.js
こちらはwebpackの設定を記載するものです。
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
{
"presets": [
"react",
"es2015"
],
"plugins": [
"transform-flow-strip-types"
]
}
babelの設定ファイルです。
ここでは、package.jsonで取り込んだpresetの2つと、preset-react
に入ってるはずなのになぜかデフォルトで有効でなかったtransform-flow-strip-types
(jsコードに書かれたflowの型定義を消して普通のjsにするplugin)を有効にしています。
.flowconfig
[ignore]
.*/node_modules/.*
[include]
[libs]
[options]
[version]
>=0.37.0
こちらはflowの設定ファイルです。
- ignore →型チェックしたくないファイル
- include →追加で型チェックしたいファイル
- libs → 型定義などを入れておくところ
- options →何か設定系
- versions →flow-binのバージョン指定
長かったですが設定ファイルは以上です。
source
<!DOCTYPE html>
<html>
<head>
<title>React minimal</title>
</head>
<body>
<div id="app"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
// @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とで分かれています。)
// @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独自のエコシステムでお互いに不思議な依存をしていて、把握しにくいなとちょっと思っています。。