JavaScript
flow
React
S2S
redux

さよならボイラープレート。s2sによる高速reduxアプリケーション構築

まだアクションクリエイターを自分で書いているの?

reduxとflowtypeを使ってフロントエンドアプリケーションを構築していると、ボイラープレートが多く、面倒だと感じることがありませんか?
しかし、もはや、このAST時代の前には過去の悩みでしかありません。

demo.gif (1072×586)

型を書く。それが全てです。
型を書いて、定数を書いて、アクションクリエイターを書いて、一つ変更したら全て変更して、もしくはなんらかのハックを行って型付けして、なんてものは過去のことです。
これからは、アクションクリエイターの作成に5秒以上時間をかけたら怠惰でありましょう。そして、これはs2sの1プラグインでしかありません。

プラグインを組み合わせると以下のようなこともできます。

last.gif

s2s (Source to Source)

これを実現している仕組みをSource to Source(s2s)といいます。

ソースコードからソースコードへのコーディングタイムコンパイルです。

スクリーンショット 2017-09-23 23.48.04.png

仕組み自体は非常に単純です。プロジェクトをs2sが監視し、条件にマッチしたファイルを引数にBabelを実行します。
Babelに代表されるトランスパイラとs2sの違いは、言葉の通り出力がソースコードへ返ってくる点しか違いません。

s2sは概念でありますから、先程のreduxの例のみならず、人が記述可能なものは全て自動で記述できます。例えば、コーディング時にASTに従ってテストコードを自動生成してもいいですし、flowの型定義を書きながらGraphQLのクエリを生成し続けるのもいいでしょう。何より、ボイラープレートだと思われるものは例外なく自動生成可能でしょう。ボイラープレートが忌み嫌われる時代は終わりました。自動生成されるボイラープレートこそが開発に速度とコードの明快さをもたらす福音足り得るでしょう。

また、s2sはエディタにロックインされません。AtomでもVSCodeでも好きなエディタを使うことが出来ます。ただ、実行するだけです。

s2sの構成とConfigについて

s2sの実装はGitHubにあります。実は6ヶ月前からスクリプトとしてずっと使っていたんですが、無職ゆえ自分と知人以外使われてきませんでした。

akameco/s2s
Source to Source

さて、ライブラリとしてのs2sの機能は大きく2つあります。
1つはもう何度も登場しているs2sそのもの。
もう1つはテンプレート展開です。
ところでみなさん、Scaffoldやスニペットは好きですか?
ええ、もちろん大嫌いです。アタマが悪いので覚えれないんですよねコマンド。
テンプレート展開は、ファイルを生成する際にテンプレートで上書きします。特に手間がかからないわりに言葉以上に有効です。

https://gyazo.com/91e13497dfe9692a37d397d6db413826

上記の例では、reducer.jsが生成された瞬間にテンプレートで上書きしています。また、reducer.jsが生成されたことによりs2sもトリガーし、type/state.js以下にStateの型を追加し、ルートのreducer.jscombineReducersに追記します。これでStateの型とreducerがずれるなんてことはありませんね。もちろん、ファイルを削除すれば消えるので、そもそもルートのreducerやstateなどの型定義は開く必要も、意識する必要もないです。

機能の説明はこれで終わりです。

次に、設定ファイルの説明に入ります。
s2sはルートに配置されたs2s.config.jsを設定ファイルとして扱います。webpack.config.jsのようなものだと思ってください。
しかし設定する項目は非常に少ないです。

s2s.config.js
const path = require('path')

const rootDir = path.resolve(process.cwd(), 'src')
const rootReducerPath = path.resolve(rootDir, 'reducer.js')


module.exports = {
  watch: './**/*.js',
  plugins: [
    {
      test: /actionTypes.js$/,
      plugin: ['s2s-action-types', { removePrefix: 'src/containers' }],
    },
    {
      test: /actionTypes.js$/,
      output: 'actions.js',
      plugin: ['s2s-action-creater'],
    },
    {
      test: /reducer.js/,
      input: rootReducerPath,
      output: rootReducerPath,
      plugin: [
        's2s-reducer-root',
        { input: 'src/containers/**/reducer.js', output: rootReducerPath },
      ],
    },
  ],
  templates: [
    { test: /reducer.js/, input: 'reducer.js' },
  ],
}

watch

監視するファイルです。globパターンで指定します。

plugins

プラグインの配列です。いくつかの引数を取ります。

  • test トリガーとなるファイルを正規表現で指定します。他にオプションがなければそのままこのパスに出力されます。actionの型定義がこのパターンです。

  • plugin バベルのプラグインを指定します。.babelrcの書き方と全く同じです。.babelrcと同様に引数を与えることも可能です。このプラグインにs2s特有の概念はありません。全てのBabelPluginをそのまま使え、新し機能を追加したければ、単にBabelPluginを書くだけです。

  • output トリガーと出力するパスが異なる場合に、このオプションを使います。相対パスを渡した場合、トリガーとなったファイルが起点の相対パスになります。root集約などは絶対パスで指定しています。

  • input トリガーとなるファイルと異なるファイルを対象として変換を実行したい場合に指定します。reducerやStateのルート集約などに利用します。

templates

テンプレートの配列です。いくつかの引数を取ります。

  • test トリガーなるパターンです。Pluginのオプションと同じです。
  • input 配置したファイル名を指定します。デフォルトでは、ルートのtemplates以下に配置されたファイルを指定します

templatesDir

テンプレートの起点を変更する際に指定します。例えば、config/templates以下にテンプレートを配置したいときなどです。

見たとおり、設定する項目は少ないです。また、単にBabel Pluginを使うため一切のロックインはありません。

新しくBabel Pluginを書いてみたい場合、以下の記事が参考になります。

Babel Pluginを作成するときに有効な情報まとめ
https://qiita.com/akameco/items/af81882fabf2728f2567

サンプルとreduxについて

はじめましょう。
以下のサンプルをgit cloneしてください。

akameco/s2s:exmaple

$ git clone git@github.com:akameco/s2s.git

よくあるショッピングカートのサンプルがあります。create-react-appを使っています。

$ cd examples/shopping-cart
$ yarn
$ yarn start

s2sを起動しましょう。

$ yarn run s2s

スクリーンショット 2017-09-24 00.30.36.png

さて、実際もう説明することはありません。
好きなように変更してs2sの動きを確認してみてください。
例えば、App以下にActionTypes.jsを新規作成し、Addアクションを追加してみてください。

https://gyazo.com/70b365152ef53158f9720298d35db4bb

コマンドラインには以下のように出力されます。イベントのパスと出力先のパスが表示されます。MacならCmd+クリックで対象のファイルがエディタで開けるので便利かもしれません。

スクリーンショット 2017-09-24 02.18.06.png

さて、この変換を行っているのは、babel-plugin-s2s-action-createrというプラグインです。s2sの責務は、変換の対象とプラグインを接続しイベントをトリガーするだけなので、変換の責務は全てBabelPluginにあります。このプラグインのreadmeを見てましょう。

スクリーンショット 2017-09-24 02.24.43.png

一つ一つ分解すると単純ですね。
このサンプルでは、6つのバベルプラグインを使っています。s2sはプラグイン含めて、モノレポで運用しているので、よかったら確認してみてください。

akameco/s2s-plugins

また、reducerのテストケースの自動生成も可能です。
詳しくは、以下のリンクを読んでください。

s2s: reduxにおけるreducerのテスト。あなたがテストを書く必要はないかも知れない

reducer-test.gif

サンプルのreduxの構成について

話はかわりますが、この生成されるflowの型付け方法やreduxの構成は、個人的なベストプラクティスでもあります。基本的な構成はコンテナベースで、action、reducer含め全てをコンテナに閉じ、同一ディレクトリにreducer.test.jsのような形式でテストを書きます。Actionはディレクトリ名のプレフィックスを付けます。これで完全に一意なアクションになるので、取り違えは起こりえません。またstring型ではなくstring literal型を使います。これについては以下の記事を読んでください。本来であれば書くのが面倒でありますが、s2sがある現在ボイラープレートは味方です。
flowtypeのactionの型付け。String Literal型とString型について
https://qiita.com/akameco/items/e7021e22da4c9e14463a

テンプレートより生成されるreducerはT & $Shape型で型を付けます。詳しくは、以下の記事を確認してください。
Flowtype+reduxにおけるreducerの正しい型付け
https://qiita.com/akameco/items/fe7ba22c158a2593b077

また、reduxのミドルウェアはredux-thunkです。これだけ先に記事を書いておくのを忘れました。なぜredux-sagaを使うことを辞めたのかを含め、後でこの選択をした理由の記事を上げる予定です。

予想される質問とその回答

TypeScriptのサポートは?


11/8 追記
デフォルトでサポートしました。設定なしで、pluginを指定すればtypescriptに対して動きます。

11/06 追記
Yes. サポートしました 🎉


が、まだプラグインがありません。
もし、TS用のプラグインを作成した方がいれば、教えて頂けると嬉しいです。
それと上記のツイートでは、s2s-ts-handlerですが、s2s-handler-typescriptにリネームしました。

追記終わり


No. Just Use FlowType.

Adopting Flow & TypeScript – @thejameskyle

冗談はさておき、Babel6だとTypeScriptは解釈されません。TypeScriptユーザは、Babel7のリリースを待つことになります。

Issueやプルリク

日本語だと嬉しいです。英語でもいいですが、Google翻訳アシストの謎い英文を返すマシーンとなります。

まとめ

s2sについて、概念や使い方についてまとめました。
s2sがあなたのプロジェクトの力になることがあれば嬉しいです。
もし気にいってくだされば、スターを貰えると励みになります。

akameco/s2s
Source to Source

兎にも角にもASTの時代です。人間のやることや書くことがどんどん減っていけば最高ですね。
何かあれば、コメント欄またはツイッターにて議論しましょう。

お知らせ HTML5カンファレンス

本日9/24にあるHTML5カンファレンスでデモ展示をする予定です。一応、一日中いる予定ですので、気軽に声をかけてくださると嬉しいです。自分のデモの内容は一番上に貼ってあるGIFの無限リピートとこの記事へのリンクを張っておくだけなので面白みはないですが、他のメンバーが何か面白い展示をしてくれるはずですのでよろしくお願いします。

10/2 追記 デモに来てくれたみなさん。本当にありがとうございました。非常に勉強になりました。出来る限りフォローを返したつもりですが、もしフォロー返されてないなどありましたら、一声かけてくれると嬉しいです。


スクリーンショット 2017-09-24 00.05.28.png

参考

akameco/s2s - Source to Source

flowtypeのactionの型付け。String Literal型とString型について
https://qiita.com/akameco/items/e7021e22da4c9e14463a

Flowtype+reduxにおけるreducerの正しい型付け
https://qiita.com/akameco/items/fe7ba22c158a2593b077

s2s: reduxにおけるreducerのテスト。あなたがテストを書く必要はないかも知れない
https://qiita.com/akameco/items/66a2232df0e95e5bfe31