これは何
- 2023/04/20 の gotanda.rb#52 の発表資料です
- もともと社内 wiki にメモがてら書いたものを転用しています
自己紹介
- megane42 (Hikaru Kazama)
- giftee
- サーバーサイドエンジニア
- 趣味: かっこいいワンタイムパスワード集め
- 副業: ポケモンのダメージ計算機
概要
- rails でフロントエンドをやっていく方法は、rails のバージョンとともに移り変わっている
- rails 5 くらいまでは sprockets がメイン
- rails 6 くらいからは webpacker がメイン
- rails 7 になってからよくわからなくなった
- 体感、このあたりから「rails でフロントエンドまでやるのやめよう」「逆にサーバーサイドも js でやろう」のような流れが強くなった感覚がある
- この記事では、そんな rails 7 でフロントエンド開発をどう進めるのがよさそうか整理してみた
rails 7 における選択肢
- 以下の 4 つのパターンがある
- Sprockets
- Webpacker
- Import Maps
- Bundling Gems
- Sprockets は昔ながらのやつなので詳細は割愛
- Webpacker は非推奨なので詳細は割愛
- この記事では Import Maps と Bundling Gems について書いていく
- ただし、現時点で触ってみたのが Import Maps だけなので、そちらに文量が偏っている
- 参考
[補足] なんで選択肢が増えたのか?
- rails 脳に染まっていると、「rails 公式がおすすめする唯一の手法を提示してほしすぎる!」となってしまうが、なぜここにきて rails は Import Maps と Bundling Gems という 2 つの道を用意したのか?
- この点について、wantedly の技術ブログに書かれていることがそれっぽかったので引用
Railsは「Rubyでワンストップのエコシステムを提供することにこだわるか」「Node.jsのよくできた(そして多くのフロントエンドエンジニアが使い慣れた)エコシステムに身を委ねるか」というジレンマに悩まされるようになったのだといえます。Webpackerはその中間的な存在であり、お世辞にも上手くいったとは言えないのではないかと思います。
Rails 7.0では、ひとつの方法で全てのユーザーを幸せにすることを諦めて、複数の選択肢を提示するという方向に舵を切ったのでしょう。これは今までのRailsのあり方とは異なるようにも見えますが、フロントエンドエコシステムについてRailsが抱える問題に正面から向き合った結果だとも思います。
Import Maps
- node.js を一切使わずにやっていくパターンの手法
- importmap-rails という gem で実現
- import map という言葉自体は、ブラウザのレイヤーに存在する機能を指している
- 事前処理で import を解決して複数の JS を 1 ファイルにまとめてから配信するのではなく、ブラウザ自身が複数の JS を import して使えるようにする機能
- ようは、最近のブラウザはブラウザ自身で import を解決できるんだから、自前で頑張らずにブラウザに任せちゃえばよくね?という発想
- vite の哲学に近い気がする(曖昧です)
- import をブラウザに任せることで、以下のようなメリットがある
- 事前の bundling (複数の .js を 1 ファイルにまとめる作業) が不要
- 事前の bundling のための node.js の導入が不要
- 例えばアプリが JS パッケージ A と B と C に依存していて、B だけ更新があったときに、B のキャッシュだけ invalidate して最小限の再取得で済ませられる
- 全てを bundling していると、B だけの更新のために A + B + C の巨大な 1 ファイルを再取得する必要がある
- ただし、デメリットも存在する
- モダンなブラウザにしか対応できない
- import map に対応している必要あり
- 複数の js を並列でリクエストできるように、HTTP 2 に対応している必要あり
- これはサーバー側もそう
- トランスパイルもしなくなるので、 例えば JSX をコンパイルできない
- これで必要十分になるくらい JS を使わないプロダクトじゃないと、Import Maps でやっていくのは厳しそう
- モダンなブラウザにしか対応できない
- import map 自体が JS のための仕組みなので、CSS は対象外っぽい
- CSS を使いたい場合は、昔ながらの app/assets/stylesheets 配下に書いて sprockets に任せるとか、後述の tailwindcss-rails を使うとかになる?
- config/importmap.rb というファイルで依存管理する
-
bin/importmap pin <package name>
みたいなコマンドもある
-
config/importmap.rb
pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"
pin "react-dom", to: "https://ga.jspm.io/npm:react-dom@17.0.2/index.js"
pin "object-assign", to: "https://ga.jspm.io/npm:object-assign@4.1.1/index.js"
Bundling Gems
- node を使うパターンの手法
- jsbundling-rails や cssbundling-rails という gem を使って実現
- トランスパイルと bundling だけいい感じにやって、app/assets 配下においてくれる
-
その先は sprockets に任せる
- つまり sprockets を置き換えるものではなく、むしろ協調作業になる
- トランスパイルと bundling をどうやって実現するかは、複数の候補の中から選択できるっぽい
-
その先は sprockets に任せる
- package.json で依存管理する(曖昧です)
Import Maps とあわせて使いたい: tailwindcss-rails
- https://github.com/rails/tailwindcss-rails
- Import Maps パターンのように、node.js を導入したくない場合に検討すべきパターンがこれ
- tailwind は v3 系からビルドの概念が導入されている
- tailwind が提供する巨大なスタイル定義を丸ごと配信する代わりに、実際に参照されているクラスに紐づくスタイルだけを抜き取って css ファイルを生成する処理が導入された
- これまでは似たようなことをするために PurgeCSS というものを使っていた
- tailwind が提供する巨大なスタイル定義を丸ごと配信する代わりに、実際に参照されているクラスに紐づくスタイルだけを抜き取って css ファイルを生成する処理が導入された
- ビルドを行うには node.js が必要になるが、tailwind は公式に stand alone cli なるものを配信していて、これを使えば node.js を導入せずにビルドが可能になる
- 実態としては node.js の処理系を丸ごとパッケージに含めているらしいので、「node.js を使わずに」は厳密には語弊がある
- https://zenn.dev/takahashim/articles/702b165b2a0fd7
- tailwindcss-rails gem は、その stand alone cli をラップしている
- つまりImport Maps & tailwindcss-rails の組み合わせなら、node.js と一切関わらずに rails のフロントをやっていくことができる
- DHH も、tailwindcss-rails をあえて使う理由のひとつに node-less 構成をあげている
- 逆に、Import Maps ではなく Bundling Gems を使っていく場合(= node.js の導入を受け入れる場合)、あえて tailwindcss-rails を使ったりせず、おとなしく node.js に染まって cssbundling-rails を使うほうがよいっぽい
- https://github.com/rails/importmap-rails/issues/107#issuecomment-1049793920
- 現に、rails new するときに
-c
オプションで tailwind の利用を指定する &&-j
オプションで Bundling Gems の利用をしていすると、tailwind が cssbundling-rails 経由で導入される
- 前述の通り Import Maps では CSS の依存関係を扱えないので、こういう gem で管理する感じになっているが、なんだか webpacker 以前に戻ったような?
まとめ
- node.js と関わりたくない場合
- JS は importmap-rails を使う
- CSS は tailwindcss-rails を使う or Sprockets を使う?
- node.js との関わりを受け入れる場合
- JS は jsbundling-rails を使う
- CSS は csbundling-rails を使う