Rails+Reactアプリをbrowserifyからwebpack基盤に移行した件
ちょうど一年ほど前、「チェンクロ パーティーシミュレーター」(以下ccpts)を、
jQueryを使った制御から、React.js + Bacon.jsで大幅に書き換えました...φ(・ω・`)
そもそも、ccpts自体、
「モバイルで動くように*1、できるだけ今風の技術でクライアント側を構築する」
という目的を持って設計してました
昨年の時点ではReactが十分にメインストリームに乗ったと判断して、
基本的なアーキテクチャをReact+browserifyに載せ替えたわけですが、
「Reactで動くようにする」ことが目的だったので、細かいところは置いておいたわけです
それを今回、フレームワークを見直してきれいにした(している)というお話です(´・ω・)っ
解決したい問題とwebpack
昨年末、チェンクロで第3部が実装され、
システムが大幅にアップデートされました
こうなると、ccptsのシステムも3部対応で大幅に書き換えがいるわけですが、
以前構築してからここまで運用してみて、
最大の問題は「デプロイに時間がかかりすぎる」ということです
サーバサイドのデータを書き換える分には問題ないのですが、
クライアント側のjsをいじるとデプロイ時にbuildが必要になり、
私が使っているVPSだと10分以上かかってしまいますΣ(゚Д゚)ガーン
3部対応するにあたり、クライアント側を頻繁にいじる必要があるので、
さすがにこれは許容できません
そもそも、これだけコストがかかると、
「クライアントをいじらずにどうにかできないか?」と考えるようになってしまい、
本来的な設計から遠ざかってしまいます(´-ω-)
そこで、2016年末の時点での「今風のやり方」を探すことにしましたのですが、
その時点で主流になっていた(ように見えた)のがwebpackです
そもそも、Railsは「Asset Pipeline」がよくできすぎています
これを自前で実装するとコストがかかるので、
何とかして今風のコードと組み合わせて乗っけたい・・・というのがポイントになります
調べた結果、ざっくりとこんな感じのやり方が主流になっているように見えました...φ(・ω・`)
- クライアント側のコードを閉じた形で独立して構築する(Railsの管理外)
- webpackで一つのjsにまとめて、Railsのassets管理下に置く(これはpureなjs)
- あとはRailsのprecompile等に素直に任せる
当初、これを自前で書こうと思ったのですが、
もっと楽ができないのか・・・と思ったら、
「React on Rails」という良いものがあったわけです
1年前の記事でも最後に取り上げていましたが、
この時点では何が主流になるのかさっぱりわからなかったので、
そこまでは踏み込みませんでした(´-ω-)
このやり方の最大の決定打は、Rails5.1におけるDHHの方針です
Rails用に仕組みを構築しちゃうあたりがさすがですが、
このwebpackerにreactのオプションも入っているくらいで、
もう迷う要素はなくなりました( ゚д゚)o彡゚
まだRails5.1はリリースまで時間がかかりますが、リリースされたとしても、
React on Railsにはreactのサーバサイドレンダリングという仕組みがありますので、
最終的にはRails5.1の上でReact on Railsという形になると思います
とはいえ、サーバサイドレンダリングをいきなり使うと、
React on Railsに依存しすぎてしまうので、まずは使わずに、
「今風の開発フローを構築するための基盤」として利用しております
実際の段取り
CoffeeScriptの排除とnpmからyarnへの移行
「できるだけクライアントのコードを client 以下に移す」というのが方針になりますが、
es6でクラス定数が使えない等々で、CoffeeScriptで書いたmodelが残っている箇所がありました
これらはAsset Pipelineとbrowserifyで動作していたわけですが、
webpackで一つにまとめるためには、全てes6で書き換える必要があります(´-ω-)
そこで、es6で書くことを優先して、クラス定数の方をあきらめました
(例としてmodelを一つ)
https://github.com/parrot-studio/cc-pt-viewer/blob/69c8e88185536e9c9c8af67c2b5e5caa53069f66/client/app/bundles/ccpts/model/Favorites.js
今回は目的が違うので、この手の妥協は仕方ありません(´・ω・`)
ついでに、npmによるパッケージ管理をやめて、yarnに切り替えました
Railsでbundlerを使っているわけで、この仕組みは全く違和感がありません
import/exportを正しく使う
以前は各modelやcomponentを呼び出す前に、
ブラウザのグローバルにBaconやReactのような各ライブラリを突っ込んでおき、
暗黙的に参照するというやり方でとりあえずやってました
これはCoffeeScriptとes6を共存させるのに必要だったのですが、
CoffeeScriptを排除したので、もう不要です
全部import/exportで書き換えました...φ(・ω・`)
問題は、どうしてもimport漏れが出てくることです
当初、動かしてみてはエラーを見て追加・・・みたいな、
超絶に効率の悪いことをしていたのですが、
ESLintで全部解決しました(`・ω・´)
ルールは自分の直感に反しないレベルでこんな感じに(´・ω・)っ
cc-pt-viewer/.eslintrc at master · parrot-studio/cc-pt-viewer · GitHub
これでエラーが出ないようにガンガン修正していきました
rucobopと同じく、自動で修正する機能もあるので、とても楽です
jQueryの取り扱い
これは1年前と何も変わってませんが、Reactを使うからといって、
jQueryを "使うべきではない" という意見には賛同できません( ゚Д゚)y─~~
たしかに、jQueryで "DOMの操作をすべきではない" ですが、
jQuery界隈には大量のライブラリやノウハウの蓄積があり、
それを全てReactの文脈に落とし込めるとは思えないわけです*2
実際の現場で、jQueryなら数分でできたことが、
Reactだと3日かかります・・・といって、
企画側に納得してもらえませんよね(´・ω・`)
ギリギリまでjQueryを使わないようにしつつ、
どうしてもjQueryが優位な箇所だけ使っていく・・・
それは1年前の時点と変わっていません
しかし、以前はライブラリを全部グローバルに定義していたので問題ありませんでしたが、
全部閉じた環境に持っていこうとすると、jQueryの取り扱いがとたんに難しくなります(lll゚Д゚)
ここに関しては情報も少なく、相当試行錯誤したのですが、
結果的に「古いライブラリは古いやり方、新しいライブラリは新しいやり方」と、
完全に切り分けることにしました
具体的にはこんな感じです...φ(・ω・`)
どうしてもjQueryの文脈が必要なものだけassets以下で管理して、
Reactを含む他の一切をwebpackで一つにまとめたわけです
React側からは「$」がグローバルアクセスできるので、
あとはReactのrefで生DOMにアクセスしてjQueryのアニメーションを適用するだけです
(1年前の記事参照)
グローバル変数があるとESLintが警告を出すのですが、
.eslintrcで「$というグローバル変数だけ許可して」と定義しているので問題ありません
むしろこれにより、「$というグローバル変数が存在する」ということが明確になります(`・ω・´)
置き換えた結果と今後
方針を決めたのが11月で、12月に入ってからは実装されたキャラデータの暫定登録を進めて、
そこから基板入れ替え作業を進めましたが、12月末にはざっくり作業が完了しました
それ以降の開発速度は目に見えて速くなりまして、
やはり心理的な障壁がぐっと下がったのはでかいです(`・ω・´) b *3
コストが下がったことで、棚上げしていた問題、
例えば、Becon.jsのStreamをもっと整理したいとか、
そもそもUIを大きくいじりたいとか、そういうところにも手を出せるようになりました
でも、一番改善したいのはReact on Railsの機能である、
「Reactのサーバサイドレンダリング」なのですが、
そのあたりは今後作業予定なので、できあがったらまた書きます...φ(・ω・`)