importmap とか jsbundling-rails とか tailwindcss-rails とか何なんだよ！

# これは何

- 2023/04/20 の [gotanda.rb#52](https://gotanda-rb.connpass.com/event/279674/) の発表資料です
    - もともと社内 wiki にメモがてら書いたものを転用しています

# 自己紹介

- megane42 (Hikaru Kazama)
- giftee
- サーバーサイドエンジニア
- 趣味: かっこいいワンタイムパスワード集め
    - [2022 年 9 月に全クリ](https://twitter.com/megane42/status/1570677528653869057)
- 副業: ポケモンのダメージ計算機
    - https://sv.pokesol.com/calc

# 概要

- 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 だけなので、そちらに文量が偏っている
- 参考
    - https://discuss.rubyonrails.org/t/guide-to-rails-7-and-the-asset-pipeline/80851/16
    - https://medium.com/@pietropugliesi/javascript-bootstrap-asset-bundling-in-ruby-on-rails-7-3640a220f2ce

## [補足] なんで選択肢が増えたのか？

- rails 脳に染まっていると、「rails 公式がおすすめする唯一の手法を提示してほしすぎる！」となってしまうが、なぜここにきて rails は Import Maps と Bundling Gems という 2 つの道を用意したのか？
- この点について、wantedly の技術ブログに書かれていることがそれっぽかったので引用
    - https://en-jp.wantedly.com/companies/wantedly/post_articles/354873
        - 1番下の「所感」の部分

> Railsは「Rubyでワンストップのエコシステムを提供することにこだわるか」「Node.jsのよくできた(そして多くのフロントエンドエンジニアが使い慣れた)エコシステムに身を委ねるか」というジレンマに悩まされるようになったのだといえます。Webpackerはその中間的な存在であり、お世辞にも上手くいったとは言えないのではないかと思います。
> 
> Rails 7.0では、ひとつの方法で全てのユーザーを幸せにすることを諦めて、複数の選択肢を提示するという方向に舵を切ったのでしょう。これは今までのRailsのあり方とは異なるようにも見えますが、フロントエンドエコシステムについてRailsが抱える問題に正面から向き合った結果だとも思います。

## Import Maps

- node.js を一切使わずにやっていくパターンの手法
- [importmap-rails](https://github.com/rails/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](https://github.com/rails/jsbundling-rails) や [cssbundling-rails](https://github.com/rails/cssbundling-rails) という gem を使って実現
- トランスパイルと bundling だけいい感じにやって、app/assets 配下においてくれる
    - **その先は sprockets に任せる**
        - つまり sprockets を置き換えるものではなく、むしろ協調作業になる
    - トランスパイルと bundling をどうやって実現するかは、複数の候補の中から選択できるっぽい
- package.json で依存管理する（曖昧です）

## Import Maps とあわせて使いたい: tailwindcss-rails

- https://github.com/rails/tailwindcss-rails
- Import Maps パターンのように、node.js を導入したくない場合に検討すべきパターンがこれ
- tailwind は v3 系からビルドの概念が導入されている
    - tailwind が提供する巨大なスタイル定義を丸ごと配信する代わりに、実際に参照されているクラスに紐づくスタイルだけを抜き取って css ファイルを生成する処理が導入された
        - これまでは似たようなことをするために PurgeCSS というものを使っていた
- ビルドを行うには 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 構成をあげている
        - https://github.com/rails/rails/issues/43531#issuecomment-985296279
- 逆に、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 経由で導入される
        - https://techracho.bpsinc.jp/hachi8833/2021_12_28/114730
- 前述の通り Import Maps では CSS の依存関係を扱えないので、こういう gem で管理する感じになっているが、なんだか webpacker 以前に戻ったような？

# まとめ

- node.js と関わりたくない場合
    - JS は importmap-rails を使う
    - CSS は tailwindcss-rails を使う or Sprockets を使う？
- node.js との関わりを受け入れる場合
    - JS は jsbundling-rails を使う
    - CSS は csbundling-rails を使う