Simpacker: Rails と webpack をもっとシンプルにインテグレーションしたいのです

技術部の外村(@hokaccha)です。Rails で webpack を使うためのシンプルな gem を作ったのでそれについて紹介します。

Webpacker

Rails で webpack を利用した Web フロントエンドの環境を作る場合、最近では Webpacker が選択されることが多いでしょう。Rails 6 からは Webpacker が標準になることもあり、この流れはますます加速すると思われます。

私自身もこれまでいくつかのプロジェクトで Webpacker を利用してきました。Webpacker は webpack を Rails から簡単に利用でき非常に便利なのですが、使っているうちにいくつか不満な点がでてきました。

一番大きい問題として Webpacker が @rails/webpacker という npm パッケージに webpack の設定を隠蔽し、Webpacker 独自の API や webpacker.yml という設定ファイルを通して webpack の設定をする必要があるというところです。

Webpacker のドキュメントに書いてある範囲内であったり、Webpacker にデフォルトで組み込まれている設定であれば webpack のことを知らなくてもなんとなく webpack が使えるという点は便利なのですが、webpack のことをすでに知っていたり、Webpacker のドキュメントにない設定をしようとした場合、webpack の設定を Webpacker に反映する方法を調べなければいけません。なので使っているうちに、「頼むから webpack の設定を直接書かせてくれ!」となります(少なくとも私はなりました)。

また、webpack のバージョンの Webpacker に引きづられてしまうというのも問題の一つです。webpack 4 がリリースされてから、Webpacker の webpack 4 対応バージョンが正式リリースされるまで1年以上の間がありました。

Simpacker

Webpacker は webpack を知らない人でも簡単に使えるというところがいいところだと思うのですが、もう少しシンプルに webpack とのインテグレーションだけをしてくれるツールがほしいと思い作ったのが Simpacker という gem です。クックパッドでもいくつかのプロジェクトで導入し、うまく動いています。

Simpacker は基本的なところは Webpacker と同じです。manifest.json という、ハッシュ値が付与されたファイル名ともとのファイル名のマッピング情報を持つファイルをもとに、JS や CSS を読み込むタグを生成するヘルパーを提供します。Webpacker と違うのは、Simpacker は webpack 側の設定を一切管理せず、出力する manifest.json のパスしか知らないというところです。開発者は好きな webpack のバージョンを使い、直接 webpack.config.js を書いて、Simpacker で設定されたパスに manifest.json を出力すればいいだけです。

そのため、原理的には webpack でなくても Parcel や Rollup などのモジュールバンドラーでも利用できます。実際に Parcel で動くことは確認してはいています1

例えば webpack では webpack-manifest-plugin などのプラグインで manifest.json を出力できます。これによって以下のような JSON が出力されます。

{
  "application.css": "/packs/application-6ebc34fd09dc6d4a87e9.css",
  "application.js": "/packs/application-6ebc34fd09dc6d4a87e9.js",
}

Rails 側では Webpacker と同じように、javascript_pack_tagstylesheet_pack_tagなどが使えます。

<%= stylesheet_pack_tag 'application' %>
<%= javascript_pack_tag 'application' %>

これは manifest.json の値を読んで以下のようなタグが出力れます。

<link rel="stylesheet" href="/packs/application-6ebc34fd09dc6d4a87e9.css" />
<script src="/packs/application-6ebc34fd09dc6d4a87e9.js"></script>

Simpacker の導入

simpackerをGemfileに追加してインストールしたら以下のコマンドで初期設定のファイルがインストールできます。

$ rails simpacker:install 

これで必要最低限の設定とJavaScriptのコードがインストールされるのでnpx webpackでビルドしてjavascript_pack_tag 'application'のヘルパを書けば動きます。

ただし、これでインストールされる webpack.config.js は本当に最低限の設定と manifest.json を出力する設定がしてあるくらいです。Webpacker ではデフォルトで組み込まれる、Babel や PostCSS、file-loader、webpack-dev-server などは、どれも入りません。これは、利用しないものがデフォルトで色々入るのは個人的にあまり好きではないという理由が大きいところです。

また、Webpacker であれば webpacker:install:react などのコマンドで React をインストールしたりできますが、このような機能も提供していません。Babel や TypeScript などのトランスパイラ、React や Vue などのフレームワーク、CSS も 素の CSS から Sass、PostCSS など様々な組み合わせがあり、それらの組み合わせをインストーラーでまかなうのは複雑すぎると思ったからです。

なので開発者自身が自分が使いたいものを選んで webpack.config.js を設定していく必要があります。webpack や各種言語やフレームワークのドキュメントを見て webpack の設定をがんばってください、でもいいのですが、何も知らない状態から webpack を設定するのはまあまあ難しいことが知られているので、各種設定例を用意することにしました。現時点では以下のような設定例を用意しています。

お察しの通り、半分以上 Simpacker は関係なくて単に webpack の設定です。初期の最低限の設定から、必要なものだけをこれらから選んで設定することで webpack の設定についても知ることができるので、webpack の入門にもいいのではないかと思います。単なるドキュメントでなく実際に動く例なので手元に持ってきて色々試すのにも便利です。

Simpacker にない機能

Webpacker が提供している機能で、Simpacker が提供していない機能もいくつかあります。

デプロイ

例えば、デプロイ関連の機能です。Webpacker では webpacker:compile という rake タスクが提供されていて、このタスクは assets:precompile の後に自動で実行されます。したがって、多くの場合はデプロイの設定を変えずにこれまでと同じようにデプロイできるはずです。

Simpacker はどんなコマンドで webpack のコマンドが実行されるか知りませんし、package manager に npm と yarn のどちらを使うのかも知りません。当然そのような設定項目を足せば実現できますが、シンプルさを重視してこの機能は提供しないことにしています。

とはいえ難しいことはなくて、以下のようなコマンドをデプロイフローに足せば済むはずです。

$ npm install
$ NODE_ENV=production ./node_modules/.bin/webpack

デプロイに関しては上記の設定例にもいくつか例を書いています。

リクエスト時のコンパイル

また、Webpacker には webpack --watchwebpack-dev-server などの監視プロセスを立てなくても、リクエストの処理中に Rails 側で webpack のコンパイルを実行するという機能があります。当然リクエストのたびにコンパイルが走ると遅すぎて話にならないので、対象ファイルの変更があったときだけコンパイルが走りますが、フロントエンドを開発しているときは当然毎回ファイルが更新されてコンパイルが走ることになるので、監視プロセスを別途立てるのが一般的です。この機能は普段フロントエンドを開発しない開発者が別途監視プロセスを立てなくても開発できる、というためのものと理解しています。

この機能は便利なケースもあると思うですが、これを実現するには Simpacker が知らないといけない webpack 側の情報がかなり多くなり、設定が大幅に複雑化してしまうので、一旦対応を見送っています。webpack --watchwebpack-dev-server などのコマンドを foreman などで実行すればよいだけなので、そこまで困ることはないと思っています。

まとめ

Rails 自身がそうなのですが、Webpacker も同じように、いかに簡単に使えるか(easyさ)を重視した仕組みと言えます。それに対して Simpacker はひとつのことだけうまくやる、シンプルさを大事にした設計にしました。そのような設計思想の違いがあるので、easy さを好む人は Webpacker を使うほうがいいと思います。シンプルさを求めて webpack の設定を直接書きたいという場合はぜひ Simpacker も検討してみてください。

クックパッドではWebフロントエンドの開発基盤をつくったりすることが好きなエンジニアも募集しています!!!


  1. manifest.json を作るプラグインが .ts.vue などの拡張子でうまく動かなかったり、ファイル名にハッシュ値をつけるためにエントリポイントとして html が必要だったりするので、現時点では現実的な利用は難しいかもしれません。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/