こんにちは、@f_subal です。普段はおもに pixivFACTORY のフロントエンドを見ています。
今回は pixivFACTORY において、フロントエンドのビルドに Webpacker を利用するのをやめた話をします。
Webpacker をやめよう
rails/webpacker は Ruby on Rails のプロジェクトに webpack を導入する際に用いられる gem です。必要な webpack の設定ファイルの生成や、Rails のテンプレートからビルド済みの JavaScript ファイルを読み出すために用いるヘルパー関数など、多数の機能を提供します。
結論から言うと、Webpacker を入れてもあまり良いことがありませんでした。単に必要が無いというより、あることによって面倒が増していると感じたので、剥がしました。以下 Webpacker が導入された Rails プロジェクトにおいて、実際に剥がす方法、およびその際に気をつける点について紹介します。
最終地点としては、特定の gem には依存せず、webpack と yarn を Rails プロジェクト内で直接用いることを目指します。
ここに書かれている内容は、プロジェクトメンバーに1人でもフロントエンドエンジニアがいれば簡単に実践可能だと考えます。
なぜ Webpacker をやめるのか
そうは言っても webpack の導入が楽になるのは事実じゃないか、どうしてやめるんだい?と思う方々もいるでしょう。私もその点は十分認めたいと思っています。
私は現在のプロジェクトにこの春から join しました。その時にはすでに Webpacker が導入されていました。当時 Webpacker について調べてみて、以下の点からこれは剥がしたほうが良いと判断しました。
- Webpacker 独自 DSL による config の記述
- npm アップデート時に考慮すべき点が増えること
- Ruby 側の実装が過度に不要な抽象を行っていること
これについてひとつひとつ説明したいと思います。
1.Webpacker 独自 DSL による config の記述
Webpacker を導入した場合、「webpack.config を記述する」という操作は基本的に必要がなくなります。代わりに、「Webpacker の提供するデフォルト設定を上書きするための独自 DSL」が与えられ、それに従って「デフォルト設定を上書きする」というのが基本操作になります。
以下は典型的な config/webpack/environment.js
の内容です。
これは記述を楽にする一方で、いったい今プロジェクト内でどういう設定の webpack が動いているのかを把握するのが困難になります。
確かに webpack.config.js
の記述は学習コストが高く、プロパティの名前もあまり直感的ではなく、まったく人間に優しいものではないというのは認めます(現状これより良いものがないのです)。
しかし個人的には、記述を減らしたいのならこうした差分記述に特化した DSL を提供するのではなく、単にサンプルとしてベストな webpack.config.js を出力する形式をとって欲しかったと感じます1。あるいは facebook/create-react-app のように、”eject” コマンドを提供していればずいぶん印象が違ったでしょう。
もっとも pixivFACTORY の場合は、一応私が join した際には既にこうした DSL は取り除かれ、一から webpack.config.js を書く形式に変わっていました(前任者に感謝です)。
設定の内容そのものにはまだまだ改善の余地がありますが、「コレでよかったんや……」と思ったのを覚えています。しかしこれは同時に、Webpacker への依存度は既に大きく下がっており、はじめから利用する意義は減りつつあったということでもありました。
2.npm アップデート時に考慮すべき点が増える
メジャーアップデートとは常にそうですが、webpack でも時に breaking changes が起きます。公式サイトのマイグレーションガイド は丁寧にその道筋を解説しており、重宝します。
使っているプラグインの変更程度なら先程の DSL を用いていても何とかなるでしょうが、webpack そのものの新しいプロパティへの対応、あるいは今使っている Webpacker のバージョンでビルドそのものが上手くいくかの検証など、通常の webpack のマイグレーションに加えて考慮すべき点が(どれも無意味であるにもかかわらず!)増加します。
一般論として、 npm モジュールのアップデートの際は、npm のことだけ(あるいは当該モジュールのことだけ)考えるようにすべきでしょう。カエサルのものはカエサルに、です。
3.Ruby 側の実装が過度に不要な抽象を行っていること
webpack でビルドした *.js
や *.css
ファイルは、最終的にテンプレートエンジンから呼び出し可能にする必要があります。webpack はそのための仕組みとして manifest.json
を出力する機能を(プラグインという形ではありますが)提供します。
以下は典型的な manifest.json
の内容です。sprockets も同様の仕組みを持っているので Rails エンジニアにも馴染み深いものと想像します。
Rails 側に必要なのは、この manifest.json
を読み込んで、引数で受け取ったファイル名からビルド済みのファイル名を探索し、 <script>
タグなどを描画するビューヘルパー関数だけです。既存の gem を使用するまでもない、実に簡単な仕組みではないでしょうか2。
Webpacker の場合は manifest.json
ファイルを Webpacker::Manifest
クラスで包み、更にそれを Webpacker::Helper
から呼び出すという形で解決していますが、自前で作るぶんには後者だけでも事足ります。
実際に必要な作業
Webpacker を剥がすにあたって、実際に必要な作業は大まかに以下のとおりです。
- gem のアンインストール
- 代わりになるビルド用 npm scripts の作成
- 代わりになる webpack.config.js の作成
- CI の対応(ここでは CircleCI を例にします)
- デプロイ時タスクの修正(ここでは Capistrano を例にします)
- ビューヘルパーの実装
- テストの対応
webpack-dev-server の対応については今回取り扱いません。そちらについては一般的な導入記事をお読みください。
【2018年9月18日 追記】
webpacker を用いずに、Rails と webpack-dev-server を連携させる方法については、スタディストさんによる下記の記事が参考になります。
【追記ここまで】
gem のアンインストール
簡単です。普通に Gemfile から削除して bundle install
しましょう。
代わりになるビルド用 npm scripts の作成
Webpacker は webpacker:install
時に、./bin/webpack
を始めとする実行ファイルを生成します。これらは不要なので削除しましょう。
その後、yarn
からビルドを実行できるように、 package.json
にスクリプトを追加します。
代わりになる webpack.config.js の作成
ここは気合が必要です。pixivFACTORY の場合はすでに DSL を剥がして一から書くようにしていたので問題ありませんでしたが、一番大変かもしれません。とはいえ、経験のあるフロントエンドエンジニアなら過去に書いた webpack.config.js
ぐらいあるだろうと仮定して進めます。
ない場合は、./config/webpack
以下のファイルを消す前に、 Webpacker の environment
のメソッドである Environment#toWebpackConfig
を叩いておき、出力結果をファイルに控えておきましょう。
(参考リンク)
一般的な webpack.config の書き方は専門の記事に譲るとして、Webpacker からのマイグレーションに本質的なところだけを解説します。次の仮定を置いたうえで解説を進めます。
- Rails プロジェクトのルート直下に
webpack.config.js
を置く app/javascript/
以下のファイル構成は特にいじらないcontext:
はapp/javascript/packs
である
entry:
の記述
Webpacker は通常、 app/javascript/packs
以下の全てのファイルを暗黙に entry
とみなします。
この挙動を再現したい場合、次のように entry:
を記述する必要があるでしょう。
ManifestPlugin
の導入
上述の manifest.json
の出力先を指定します。ビルドしたファイルは public/packs
以下に吐かれて欲しいので、publicPath
属性でプレフィックスを /packs/
に指定します。
これでひとまずビルド自体はできるようになりました。
CI の対応
さて、この時点で突然 CI がコケることに気づいた人もいるでしょう。
CI 環境で Webpacker
を動かしていた場合、Webpacker を外したコミット以降 webpack
が存在しないといったエラーが出るはずです。
Webpacker は assets:precompile
の際に node_modules のインストールも行っているのですが、これがなくなったために起こります( 参考リンク )。適当なタイミングで yarn install
を実行しましょう。
CircleCI ならこんな感じで結構です。
デプロイ時タスクの修正
当然ですが、デプロイ時には js のプロダクションビルドを走らせる必要があります。
まずは package.json
にプロダクションビルドの npm scripts を追加しておきます。webpack 4 以上の場合、開発ビルドとの分岐は --mode
を用います。(https://webpack.js.org/concepts/mode/)
capistrano を使用している場合は、deploy.rb
内で次のように呼びましょう。
この辺で一度 staging 環境へのデプロイが成功するか試しておくと良いかもしれません3。まだ完全には動きませんが、とりあえずデプロイができる安心感は得ておくに越したことはありません。
ビューヘルパーの実装
いよいよ、テンプレートエンジンからビルド済みファイルを呼べるようにしたいと思います。
Webpacker は javascript_pack_tag
や stylesheet_pack_tag
という関数を提供しますが、これらの代わりがあれば良いです(参考リンク)
まず、テストを書きます。だいたい以下の仕様を満たせばよいでしょう。_pack_tag
という名前はいまいちなので _bundle_tag
に変更します4。
ヘルパーの実装はこうです。
あとは呼び出し箇所を一斉置換してあげましょう。
テストの対応
もうこれで終了……!と、言いたいところですが、ビューを変更したことによってコントローラーのテストが失敗すると思います。
というのも、CI 環境ではテスト中に public/packs/manifest.json
が存在するとは限らないからです。
コントローラーのテストはレンダリングに成功するかさえ見ればよく、本物のマニフェストは必要ないと思うので、モックしましょう。 spec/support
辺りに次のファイルを作りましょう。
これで準備万端です。
まとめ
ある程度本腰入れて取り組む必要がありますが、やってしまえば1日〜2日の作業でWebpackerを剥がせると思います。 Webpackerを剥がしたことにより、これまで多少躊躇していた webpack のバージョンアップも安心して行えるようになりました。プロジェクト内に JS の専門家がいる場合は剥がすことをオススメします。
Rails Way 自体は良いものではありますが、フロントエンドに Rails Way が存在するという考えはものごとを複雑にします。適切に脱出を図り、開発効率を高めましょう。
ピクシブ株式会社では、積極的に Web フロントエンドの改善を行いたいエンジニアを募集しています!
-
余談ですが経験上、JSON で済むものを敢えてオブジェクト指向で表現する方法は、JavaScript でやるとかえって意味不明になるケースが多いです。 ↩
-
私は以前、PHP のプロジェクトに趣味で webpack を導入したことがあります。このときも非常に簡易な実装で上手く行った経験があり、すぐに Webpacker の解決の仕方に違和感を覚えました。 ↩
-
実際私は一度
execute 'yarn build:production'
と書いて失敗しました。 capistrano/ssh-kit の仕様によれば、execute
はコマンド名をシンボルで与えないとwithin
を無視するようです。 https://github.com/capistrano/sshkit#basic-usage ↩ -
Webpacker は webpack でビルド済みのファイルを指して
"pack(s)"
と呼ぶようですが、その呼び方をしている webpack ユーザーはあまり見たことがありません。 ↩