Rails6のアプリをRails7にして、hotwireとimportmapを有効にする

前置き

上二つの続き。

Rails7 -> Ruby3.1時たら今度はhotwireとimportmapじゃろうということで、Rails6->Rails7にしたアプリをwebpackerからhotwireに移行する。

hotwireって?

basecampが作った新しい手法。

Javascriptの利用を抑え、Jsonを送信せず、htmlのやりとりに抑える。turbolinksと呼ばれていた機能はTurboという形でhotwireに組み込まれた。これにStimulusが追加されてhotwireって呼ばれている。

元はhotwired_railsとかいうGemがあって、rails6で導入するためにはrails newするときに–skip-javascriptが必要だったんだけど、Rails7からはrails newすると自動的にhotwireのセットが入る。

Stimulusって?

HTMLのレンダリングをしない小さなJavaScriptフレームワーク。READMEにあるけど本当にhtmlの描画をしないので、Turboと組み合わせて使うことで力を発揮するように作られている。

importmapって??

import句でモジュールを取得する際の取得先URLを制御する仕組み。import句で読み込み先をコントロールする。これの導入でWebpackを使わずRailsでJavaScriptを扱うことが可能になる。

対象アプリ

僕が今作ってるアプリはAPIの管理画面で、そちらはRails WayなCRUDの画面がいっぱいある。bootstrapでデザインしてて、モーダルや画像表示などは完全にbootstrapに依存している。bootstrapのインストールについてはこっちの記事で書いた。

ログイン画面。

サイトの更新情報を追加する。他人にみせても一番無難なページ。

更新情報を追加を押すとこんな感じでモーダルとフォームが現れる。ここはbootstrap依存。

これをhotwire / importmapに移行して、bootstrap5のデザインが維持されているところまでがゴール。

Rails6->Rails7にしたあとのGemfile

3.1対応まだなのでRails7.0。このあと7.0.1にする記事を書く予定。

gem 'rails', '7.0.0'
gem 'sass-rails', '>= 6'
## TurbolinksはもうActiveではない。HotWiredに移行すること。
# https://github.com/turbolinks/turbolinks 
gem 'turbolinks', '~> 5' <<<<< 今回はここを変えていく

gem 'webpacker'
gem 'bootsnap', '>= 1.4.2', require: false

こうする

gem 'rails', '7.0.0'
gem 'sass-rails', '>= 6'

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"

gem 'bootsnap', '>= 1.4.2', require: false

インストールする。

$ bundle install

turboを有効にする

不要なファイルを消す。Rails7からはnodeが不要になるので、js関連のファイルは不要。

$ rm -rf package.json yarn.lock node_modules postcss.config.js babel.config.js

app/javascriptの場所を変える。Webpackも同時に不要なのでpack以下ではなくjavascript配下に置く。

$ mv app/javascript/pack/application.js app/javascript/application.js
$ mkdir app/javascript/controller
$ rm -rf pack

application.jsを編集する。

import "@hotwired/turbo-rails"
import "controllers"
require("@rails/activestorage").start()
require("channels")
import "bootstrap"
import "./stylesheets/application"
const images = require.context('./images', true)
const imagePath = (name) => images(name, true)

そしたらimportmapの設定ファイルを作成する。turboのインストールに必要。

touch config/importmap.rb

最後にコマンドをうつ。これでturboがpinづけされて利用が可能になる。

$ rails turbo:install

Import Turbo
      append  app/javascript/application.js
Pin Turbo
      append  config/importmap.rb

このImport何とかとPinについてはこのあとのimportmapで説明する。

Importmapを有効にしてbootstrapを呼び出す

設定ファイルを見てみる。

# config/importmap.rb
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true

turboはhotwiredのjsにpinづけされてる。
こうして名前と呼び出し元をマッピングすることでapplication.js内でimportするだけで呼び出しが可能になる。
$ rails importmap:install
Default application.html.erb is missing!
        Add <%= javascript_importmap_tags %> within the <head> tag in your custom layout.
Create application.js module as entrypoint
    conflict  app/javascript/application.js
Overwrite /app/app/javascript/application.js? (enter "h" for help) [Ynaqdhm] Y
       force  app/javascript/application.js
Use vendor/javascript for downloaded pins
      create  vendor/javascript
      create  vendor/javascript/.keep
Ensure JavaScript files are in the Sprocket manifest
      append  app/assets/config/manifest.js
Configure importmap paths in config/importmap.rb
    conflict  config/importmap.rb
Overwrite /app/config/importmap.rb? (enter "h" for help) [Ynaqdhm] Y
       force  config/importmap.rb
Copying binstub
      create  bin/importmap

必要なものをimportする。app/javascript/application.jsを開いて、以下の形に書き換える。上でpinつけしているので、これで外部のCDN が呼び出される。

import "@hotwired/turbo-rails"
import "bootstrap"

ただこれだとJavaScriptは呼ばれてもCSSは呼ばれない。

CSSは別途インストールする。

Gemに以下を追加。

gem "cssbundling-rails"
gem "foreman"

Rails7はCSSをいくつか選んでインストールできるので、好きなの選ぶんだけど、今回はbootstrapを選ぶ。

# コマンドサンプル
./bin/rails css:install:[tailwind|bootstrap|bulma|postcss|sass]

# 今回はこう
./bin/rails css:install:bootstrap

package.jsonに以下を追加。こうしないとbootstrapがビルドされない。これnode_modulesを使わない方法があるのかもしれないので、それはまた別途。

"scripts": {
  "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules"
}

あとはいつも通りdocker-compose upで起動すれば画面にbootstrapがあたっている。

まとめ

なんだかんだいって最後にyarn installが走っているので、nodeとyarnを使わない方式にできるならやりたいと思った。

少なくても今回でWebpackerは脱出できた。

次はbootstrapをtailwindcssにリニューアルしてみようと思う。