Webpackerからjsbundling-rails + esbuildに移行してReactを動かした話（Rails7.0→7.1アップデート）

## 背景
- Rails 7.0系から7.1系にアップデートするにあたり、すでに開発がストップしている Webpacker を乗り換える必要がありました

- Webpackerを使ってReactを動かしていたので、それもビルドできるようにする必要がありました。（React内でCSSも使っていました）

- 一方で、React以外のCSSは Sprockets を使って管理していたので、これはそのまま残したい
  - 参考：[Ruby on Rails で sprockets から Webpacker へ移行し、移行できないものは共存させる方法 \#webpack \- Qiita](https://qiita.com/tatsurou313/items/645cbf0a3af4c673b5df)

## 読んで欲しい人
- 同じような場面に直面した人
- 過去のなーんにもわかってない俺

## 環境について
- Rails 7.0.8
- Ruby 3.2.2
- Webpacker 5.4.4
- sprockets 4.2.1
- React 16.0.

## 簡単に用語の整理

### Sprockets
  - app/assets配下にある、js,css,画像などのアセット群を1つにまとめてくれる。Rails6より前でディフォルトだったぽい。Webpackerとは役割が被っている
### webpackerとは
  - Rails6.0より標準搭載となったWebpackを使用したjs,css,画像などのアセット群を1つにまとめてくれるモジュールバンドラーです。Rails7.0以降には推奨されなくなり、現在では開発もストップしています。
  - Webpackとは？
    - 複数のjsファイルを1つにまとめてくれるモジュールバンドラーです。node.jsのモジュールの1つ
    - js以外のcssや画像ファイルもまとめてくれるが、ややこしくなるのでこの記事ではjs以外はスルーします。
  - node.jsとは
    - https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb
  - モジュールバンドラーとは
    - 複数のJSファイルの依存関係を勝手に解析して、いい感じに1つのファイルにまとめてくれる機能があるやつ
### jsbundling-railsとは
  - Railsとjsバンドラーの連携をいい感じにしてくれる「ブリッジツール」的なもの
  - ビルドしてくれた後のファイルを適切な場所に置いてくれる
  - バンドラーが初期設定を行なってくれる
### esbuild
  - jsのバンドラー
  - jsのバンドルを比較的高速に行ってくれるらしい

:::note warn
jsbundling-railsとesbuild(別のバンドラーでも良いが)の2つが揃って連携して初めて、Webpackerの代わりとして機能します
:::

## 乗り換え

### jsbundlig-rails 
- gemfileに`gem 'jsbundling-rails'`を追記
### esbuild追加

- esbuildのインストール

```
$ ./bin/rails javascript:install:esbuild
```

そうすると下記のファイル生成、変更が行われる

- .gitignoreファイルの更新
- foremanで複数のプロセスを実行するためのProcfile.devファイルの生成
- app/assets/buildsディレクトリ→この時点では.keepファイルだけ
- manifest.jsの更新
- app/javascript/application.jsのエントリーポイントファイル生成
- javascriptのインクルードタグ追記
- bin/devスクリプトファイル生成
- pakage.jsonの更新

#### 生成されたファイルをざっくり説明

- Procfile.devファイル
  - Procfile.devに書かれた複数のプロセスを`foreman start`コマンド1つで一度に立ち上げてくれます
```Procfile.dev
# Railsサーバーの立ち上げ
web: bin/rails server
# バンドラーの実行（後述）
js: yarn build --watch
```
- bin/devスクリプトファイル
  - `bin/dev`とターミナルに打ち込むと`foreman start`を実行してくれるファイル

```
# bin/dev

#!/usr/bin/env bash

# foremanがインストールされていなければインストール
if ! command -v foreman &> /dev/null
  then
    echo "Installing foreman..."
    gem install foreman
fi

# Procfile.devを元にforemanを起動
foreman start -f Procfile.dev "$@"
```

- app/assets/buildsディレクトリ
  - esbuildでビルドした後のファイルをここで管理してくれる
  - この時点では.keepファイルだけ


- pakage.jsonのscript
  - 下記のようなコマンドが追加されている
```package.json
  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets"
  }
```

- app/javascript/application.jsのエントリーポイントファイル
  - esbuildのエントリーポイント。Webpackerでいうところのapp/javascript/packs/application.jsにあたる

### jsの移行

:::note warn
- Sprocketsを利用している
- esbuildでcssファイルもビルドしたい

みたいな場合はapp/javascript/application.jsのエントリーポイントファイル名を、app/javascript/esbuild_application.js(任意の名前)にしてください。詳しくは「詰まった箇所」で後述しますが、既存のSprocketsの生成したcssファイルと名前がかぶるのでSprockets::DoubleLinkErrorsが出てはまります
:::

- ReactをWepackerのエントリーポイントからesbuildのエントリーポイントに移動させます

```app/javascript/packs/application.js

  require("controllers")
  require("@rails/ujs").start();

  import React from 'react';
  import { render } from 'react-dom';
  
  // Reactで使用していたcss 後述
  import 'slick-carousel/slick/slick.css';
  import "slick-carousel/slick/slick-theme.css";
  import 'slick-carousel/slick/slick-theme.css';

  // Reactのコンポーネントimport
  import Slide from './components/Slide';
  import PresentationEditor from './components/PresentationEditor';
```
```app/javascript/application.js または esbuild_application.js

import "./controllers"
import Rails from “@rails/ujs”
Rails.starts()

import React from 'react';
import { render } from 'react-dom';

// Reactで使用していたcss
import 'slick-carousel/slick/slick.css';
import "slick-carousel/slick/slick-theme.css";
import 'slick-carousel/slick/slick-theme.css';

// Reactのコンポーネントimport
import Slide from './components/Slide';
import PresentationEditor from './components/PresentationEditor';
```

app/javascript/packs/application.jsで`require`を使用している部分は`import`に変更してください

### webpackerで使用しているtagを削除する
- webpackerで使用していた`javascript_pack_tag`などをここで`javascript_include_tag`に差し替えます

- esbuildでcssもビルドしている場合は`stylesheet_link_tag`を利用してください。sprocketsを利用している場合はesbuildで読み込むための`stylesheet_link_tag`も必要になります


```application.html.erb
  <%= stylesheet_link_tag 'esbuild_application' %>

  <%= javascript_pack_tag 'application' %>

  <%= javascript_include_tag "application", defer: true %>
    or
  <%= javascript_include_tag "esbuild_application", defer: true %>
```

### Webpacker掃除

- Webpacker関連のファイルやライブラリなどを削除します
  - gemfileからの削除
  - ./bin/webpack
  - ./bin/webpack-dev-server
  - ./config/initializers/webpacker.rb
  - ./config/webpacker.yml
  - ./config/webpack/development.js
  - ./config/webpack/environment.js
  - ./config/webpack/production.js
  - ./config/webpack/test.js

### package.josnのscriptを修正

- pakage.jsonのscriptでReactの拡張子を認識させる
  - scriptに`--loader:.js=jsx`オプションを入れます。jsxファイルをインポートして、依存関係に含めます。そしていい感じにコンパルしてくれます

```package.json
  "scripts": {
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --loader:.js=jsx
  }
```

## 詰まったこと
- 詰まった箇所: Reactで使用しているcssファイル

  ```js
    import 'slick-carousel/slick/slick.css';
    import "slick-carousel/slick/slick-theme.css";
    import 'slick-carousel/slick/slick-theme.css';
  ```

  - ReactのSlick内で使用されているCSSの設定方法で詰まった。
    - CSSだけSprocketsに移動しようとしたができなかったです。原因はいまだに不明
    - cssbundling-railsを利用してcssをビルドしようとしたが、できなかった。原因はすでにesbuildで生成されていることを知らなかったから（esbuild_application.jsからの消し忘れで気づけた）
    - Sprocketsのcssファイル名と競合してSprockets::DoubleLinkErrorsエラーになってしまった。原因はどちらも最終的にはapplication.cssファイルとして読み込まれるので同じファイルが2つあると認識されていた。esbuildで生成されたcssファイルについてはesbuild_application.cssとしてファイル名を変更して解決

## 感想
  - そもそもRailsのフロント周りは移り変わりが激しく色々な単語が登場するのでとても苦戦しました
  - 「Webpacker剥がし」なるものを実際に1人で経験できて前よりもフロントへの解像度が増えてよかったです
## 参考にした記事
https://www.learnenough.com/blog/migrating-away-from-rails-webpacker?srsltid=AfmBOorn6b9XkKuI1X8r7MYKRShPS9i2HlJaYo1K_pjBgM-MD1fwhG2P

https://www.fastruby.io/blog/esbuild/webpacker/javascript/migrate-from-webpacker-to-esbuild.html

https://github.com/rails/jsbundling-rails