Rails7でViteを使う - 開発環境での Hot Module Replacement 編
概要
- Rails を採用しているプロジェクトは2024年末時点ではまだまだ多い
- 中でも数年間運用してきたものは言語やフレームワーク自体のバージョンアップなどが必要
- 今回は担当している案件で、Rails のフロントエンドのビルドツールに Vite を使用するようになったのでその素振りとまとめをここでする
目的
- Vite と Rails のインテグレーションのハンズオンを実施し、理解を深めること
目標
- ローカル開発環境で HMR(Hot Module Replacement) を有効にすること
環境
技術 | バージョン |
---|---|
Ruby | 3.4.1 |
Rails | 7.2 |
MySQL | 8.0 |
Vite | 5.4 |
- ローカル開発環境はMacを想定しており、Docker Desktop for Mac を利用
- ホストOS側に Ruby の環境は整備しない
事前知識
アセットプリコンパイルの復習
- Railsはフルスタックフレームワークを標榜しているのでフロントエンドも担える
- RailsがAPIモードで動作していない時はフロントエンドの開発環境が有効
- フロントエンドでの実装は、JavaScriptやCSSなどが絡む
- アセットとは、JavaScript、CSS、画像、フォントなどの外部リソースのこと
- アセットプリコンパイルとは、Rails がアセットを最適化して、本番環境で効率的に配信するためのプロセス
- 最適化とは具体的に以下を意味する
- 必要に応じて複数のファイルを 1 つにまとめる「統合」
- ファイルサイズを削減するため、空白や改行を削除する「圧縮」
- キャッシュ対策として、ファイル名に一意のハッシュを付加する「ハッシュ化」
- 最適化によりリクエスト数の削減(10 個の JavaScript ファイル → 1 ファイルに結合して配信)、キャッシュ効率化、レスポンス速度の向上などの福利がある
- 但し、前項は本番環境などの話で開発時は分けて管理した方が、実装の利便性が上がる
- ファイルが分割されていることで保守性が上がる
- 例えばViteは分割されたファイルの個々が編集された時にそれだけを読み込んでくれるホットリロードが効く
- 圧縮されたコードなんてもちろんそのままじゃ読めないし・・・
- ファイルが分割されていることで保守性が上がる
- したがって、開発時はファイルを分割し、プリコンパイルなどはスキップする、デプロイ時などではアセットプリコンパイルでまとめるということになる
- 最適化とは具体的に以下を意味する
Railsにおけるモジュールバンドリングツールやビルドツールとの統合
Railsアプリの初期化
`rails new` 実行までの手順
リポジトリ初期化
$ cd /path/to/workdir
$ mkdir rails_vite_example
$ cd $_
$ git init
$ git commit -m 'first commit' --allow-empty
Docker関連のファイルの追加
以下のファイルを各々追加する
FROM ruby:3.4
RUN mkdir -p /app
WORKDIR /app
RUN gem install rails -v 7.2
RUN apt-get update -qq && apt-get install -y nodejs npm
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
networks:
- rails-vite-network
ports:
# 自由に利用できるとされる動的・プライベート ポート番号は 49152–65535 らしいので、これらのうち5万番を利用する
# 以下、ウェルノウンポート番号 + 50000 という規約で設定していく
- "53000:3000"
volumes:
- .:/app
- gem-cache:/usr/local/bundle
- node-modules-cache:/app/node_modules
working_dir: /app
stdin_open: true
tty: true
command: bash -c "rm -f tmp/pids/server.pid && rails s -b 0.0.0.0"
volumes:
gem-cache: {}
node-modules-cache: {}
networks:
rails-vite-network:
driver: bridge
rails new
実行
$ docker compose run --rm app rails new . --skip-javascript --no-deps --skip-active-record
$ git add .
$ git commit -m 'rails new'
- Rails7系から導入された
importmap-rails
などは、Vite で用いる ESBuild や Rollup と競合してしまうので、--skip-javascript
オプションを付加してインストールされないようにしている - ActiveRecord がない Rails アプリには多大な違和感があるが、今回は簡便のため
--skip-active-record
を付与した
※ 以降、 git commit
の記載は省略する
アプリケーションサーバーの起動
docker compose up
を実行するだけで http://localhost:53000/
にアクセスするとRailsの初期画面が表示されるようになる
$ docker compose up
vite_rails
のインストールと初期化
- 今回は
vite_rails
を採用する - 理由は、このようなラッパーを利用することで Vite に関する設定をスクラッチで行う必要がなくなるため
vite_rails provides similar functionality as webpacker does for webpack, without all the configuration overhead and dependencies.
cite: https://vite-ruby.netlify.app/guide/introduction.html#introduction
以下のコマンドで gem のインストールと初期化処理を行う
$ docker compose exec app bundle add vite_rails
$ docker compose exec app bundle exec vite install
Creating binstub
Check that your vite.json configuration file is available in the load path:
No such file or directory @ rb_sysopen - /app/config/vite.json
Creating configuration files
Installing sample files
Installing js dependencies
changed 2 packages, and audited 33 packages in 1s
6 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Adding files to .gitignore
Vite ⚡️ Ruby successfully installed! 🎉
上記を実行すると、設定ファイルが生成されたり、レイアウト(app/views/layouts/application.html.erb
) にヘルパーメソッドが追加されたりする
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: .gitignore
modified: Gemfile
modified: Gemfile.lock
modified: app/views/layouts/application.html.erb
modified: config/initializers/content_security_policy.rb
modified: node_modules/.package-lock.json
Untracked files:
(use "git add <file>..." to include in what will be committed)
Procfile.dev
app/frontend/
bin/vite
config/vite.json
package-lock.json
package.json
vite.config.ts
no changes added to commit (use "git add" and/or "git commit -a")
vite.json
にはソースコードディレクトリがどこかや、エントリーポイントをどのサブディレクトリに配置するのかなどを設定する
デフォルトではソースコードディレクトリは app/frontend
で、エントリーポイントはソースコードディレクトリからの相対パスで entrypoints
となる
実際に初期化後は以下のようなディレクトリレイアウトになっている
$ tree app/frontend
app/frontend
└── entrypoints
└── application.js
レイアウトの差分は以下
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<%= stylesheet_link_tag "application" %>
+ <%= vite_client_tag %>
+ <%= vite_javascript_tag 'application' %>
+ <!--
+ If using a TypeScript entrypoint file:
+ vite_typescript_tag 'application'
+
+ If using a .jsx or .tsx entrypoint, add the extension:
+ vite_javascript_tag 'application.jsx'
+
+ Visit the guide for more information: https://vite-ruby.netlify.app/guide/rails
+ -->
+
</head>
<body>
vite_client_tag
はHMR(Hot Module Replacement)を有効にするためのヘルパー
vite_client_tag: Renders the Vite client to enable Hot Module Reload
cite: https://vite-ruby.netlify.app/guide/rails.html#enabling-hot-module-reload-🔥
vite_javascript_tag
はエントリーポイントをロードするためのヘルパーで、今回だと app/frontend/entrypoints/application.js
が読み込まれる
HMR(Hot Module Replacement)の有効化
Vite 開発サーバー用のコンテナを追加
前節で見たように、HMRを有効にするためのヘルパーは追加されている
ただ、これだけではHMRが作用しないので、Viteの開発サーバーを起動してアプリのフロントエンド(ブラウザ)がVite側に疎通できるように設定する必要がある
まず、docker-compose.yml
を以下の内容に置き換える
x-app-base: &app-base
build:
context: .
dockerfile: Dockerfile.dev
networks:
- rails-vite-network
volumes:
- .:/app
- gem-cache:/usr/local/bundle
- node-modules-cache:/app/node_modules
working_dir: /app
stdin_open: true
tty: true
services:
app:
<<: *app-base
build:
context: .
dockerfile: Dockerfile.dev
ports:
# 自由に利用できるとされる動的・プライベート ポート番号は 49152–65535 らしいので、これらのうち5万番を利用する
# 以下、ウェルノウンポート番号 + 50000 という規約で設定していく
- "53000:3000"
command: bash -c "rm -f tmp/pids/server.pid && rails s -b 0.0.0.0"
environment:
VITE_RUBY_HOST: "vite"
vite:
<<: *app-base
ports:
- "3036:3036"
environment:
VITE_RUBY_HOST: 0.0.0.0
depends_on:
- app
command: bin/vite dev
volumes:
gem-cache: {}
node-modules-cache: {}
networks:
rails-vite-network:
driver: bridge
これで docker compose down/up
を実行する
$ docker compose down
$ docker compose up
これで Vite の開発サーバーが起動する
今回は設定ファイル等は初期化処理実行後から編集していないので http://localhost:3036/vite-dev/
にブラウザからアクセスすれば以下のような画面が表示される
動作確認
適当にアクションを作る
$ docker compose exec app bin/rails g controller home index
これをルートに割り当てる
Rails.application.routes.draw do
+ root "home#index"
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
エントリーポイントを適当に以下のように編集する[3]
document.addEventListener('DOMContentLoaded', () => {
document.body.innerHTML = '<h1>Vite ⚡️ Rails</h1>'
})
こうすると、トップページのレンダリングは以下のようになる
文字が小さくて恐縮だが、以下の画像のようにVSCodeでJavaScriptを編集して保存したら、自動的にブラウザの表示内容も更新されるようになる(速い⚡️)
Discussion