🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

0からはじめるFlow Part.1

こないだReactで書きなおしたウデマエアーカイブに、Flowをいれてみようと思って、しばらく色々と試したことのメモ、もとい奮闘記です。

Flow | A static type checker for JavaScript

事前準備

npm i --save-dev babel-plugin-transform-flow-strip-types
npm i -g flow-bin

まず最初に、`babel-plugin-transform-flow-strip-types`をいれる。
型についての記載を実コードから削ってくれるやつ。

次に、グローバルに`flow-bin`をインストールしておく。

最後にエディタをいい感じに。
開発はVimでやるので、SyntasticとFlowを連携させる。

let g:syntastic_javascript_checkers = ['eslint', 'flow']

ってな具合に指定するだけ。

というわけで現在Flowを使うには、Babelを使う前提があるというわけではある

事前準備の補足

公式とか検索して出てくるものは、`transform-flow-strip-types`さえいれればOK!ってなってるけど、ちょっと罠があります。

それは、使ってるモジュールの仕組みがES Modulesか、CommonJSかで違います。
あと、`transform-class-properties`を使ってるかどうかも関係するので、以下の表で当てはまるやつを準備する。

Module transform-class-properties どうすればいいか
CommonJS してる `transform-flow-strip-types`足す
CommonJS してない `transform-class-properties`も足す OR `passPerPreset`で先に`transform-flow-strip-types`する
ES Modules してる `transform-flow-strip-types`足す
ES Modules してない `transform-class-properties`するしかない(`passPerPreset`で先に`transform-flow-strip-types`してもNG)

ちなみにコレ、Classに型をつけるまでは問題になりません、ってのが罠やと思うの・・。

`passPerPreset`

Babel 6.5から入ったExperimentalな機能。

babel/CHANGELOG.md at master · babel/babel · GitHub

{
  passPerPreset: true,
  presets: [
    {
      "plugins": [ "babel-plugin-transform-flow-strip-types" ]
    },
    "es2015",
    "react"
  ]
}

`.babelrc`にこう書けばいい。

.flowconfig

準備ができたらいざ型付け!
ということで、Flowの設定ファイル`.flowconfig`を用意する。

flow init

Flowをグローバルにインストールしたらこのコマンドが使えるので、これで生成してもいい。
ちな現在の`.flowconfig`はこんな感じ。

[ignore]
.*/node_modules/.*
.*/dist/.*

[include]

[libs]
./src/script/_decls

[options]
suppress_comment= \\(.\\|\n\\)*\\flow-disable-line

ハマりどころ1

これを生成した時点では、`.flowconfig`が置かれたルートから全ファイルをチェックしにいってしまう。
もちろん`node_modules`配下も例外ではないわけで、だいたい型が指定されてないよエラーで爆散するはず。

というわけで`ignore`する。
buildしたファイルも見なくていいので`ignore`する。

ここのパスの書き方がOCaml正規表現らしく、ちょっと慣れない感じ。

flow ls

これでいまFlowがターゲットとしてるファイルのリストが出るので、確認しながら調整すればOK。

ハマりどころ2

`node_modules`を無視したので、読み込んだライブラリにも自分で型をつける必要が出てくる。
その定義を置いておくのが`libs`で指定したディレクトリ。

この指定したディレクトリ配下においた`**.js`を、型定義として読み込んでくれる。
1つのファイルに全部書くぜ!って場合は、「ファイル名をフルパス」で指定すればOK。

[libs]
./src/script/_decls    # ディレクトリ指定なら
./src/script/_decls.js # ファイル指定なら

ハマりどころ3

せっかく型を付けられるようになったし、できれば全ファイルで`@flow`って書きたいところ。
でも中にはどうしようもないやつもいて・・、たとえばGoogleAnalyticsのコード。

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments);},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m); })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

これがすごい怒られる。
1行ずつ分解してあるべきところに型をつけて・・ってやっていくと通るけど、なんでそんなことをせなあかんのかという気持ちが強い。

そこで、ESLintにある「この行はLintしなくていいよコメント」みたいなのないかなー?と思ってたらあった。
`.flowconfig`の`options`に、`suppress_comment`ってのを指定する。

[options]
suppress_comment= \\(.\\|\n\\)*\\flow-disable-line

こう書いたうえで、

// flow-disable-line
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments);},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m); })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

これで無視できるようになった。

いざ型付け!

ファイルの先頭に`@flow`コメントを入れると、Flowがチェックしてくれるようになる。

むしろ、このコメントがないとチェックしてくれない。

行コメントでもいいし、複数行コメントでもいいけど、他のどんなコード記述よりも前である必要がある。

/**
 * このファイルは....
 * なんたらかんたら...
 *
 * @flow
 */

これでもOK。

というわけで、

  • 1ファイルずつ、`@flow`コメントいれる
  • Flowに怒られる部分があるので、そこをなおす
  • より厳密にしたい部分に型を付けていく

という手順でやっていきます。

やってみての学びは、また次のPart.2に書きます!