JMDC TECH BLOG

JMDCのエンジニアブログです

Rails+ViteでHMRを導入、さくさくフロント開発する!得られた知見の共有

医療機関基盤グループでエンジニアをしている堀です。
私が携わるシステムでRailsのバージョンのEOLのためにバージョンアップする必要性がありました。その際にフロントエンド開発用に導入していたShakapackerがすんなりとアップデート対応できず、また使い勝手が悪かったこともありViteに移行することにしました。移行に際してHMRを実現するのに少々手こずりましたので、そこで得れた知見の共有をさせてください。
なおDockerやRailsといったスキルについてある程度理解されてる方を対象に書いています。ご了承ください。

1. HMR (Hot Module Replacement)とは?

HMRを使うと、TypeScript/JavaScript/SCSSといったフロントエンドで開発中のソースの変更をリアルタイムでブラウザに反映することができます。このため編集結果を確認しながらサクサクとプログラムを進めることができるようになります。

ブラウザで自動更新されるサンプル
画像ではブラウザのリロードボタンを押さずに編集した内容が画面に表示されてる様子が確認できます。

2. 環境の構築

dockerでrailsコンテナとvite_devコンテナをそれぞれ立ち上げて、ローカルPCで編集したソースをリアルタイムにブラウザで確認できる環境をサンプルとして作ってみました。

構成図

バージョンは以下の通り。

  • Gemモジュール
    • Rails 7.2.x
    • vite_rails 3.0.x
  • Nodeモジュール
    • vite 6.3.x
    • vite-plugin-ruby 5.1.x
    • @vitejs/plugin-react 4.5.x
      • ※フロントエンドにReactを使っているため必要

細かい構築手順については検索するとたくさん出てきますので割愛します。以降ではポイントのみ記載します。

2-1. コンテナの設定

RailsサーバーとViteサーバーのコンテナを設定した compose.yml になります。

services:
  vite_dev:
    build: .
    volumes:
      - .:/myapp
      - node_modules_cache:/myapp/node_modules
    ports:
      - "3036:3036"
    environment:
      DEBUG: '*vite*'
      RAILS_ENV: development
      VITE_RUBY_HOST: 0.0.0.0
      __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS: vite_dev
    command:
      - /bin/sh
      - -c
      - |
          npm install
          /myapp/bin/vite dev
  rails:
    build: .
    volumes:
      - .:/myapp
      - bundle_data:/usr/local/bundle
      - tmp_shared:/tmp/shared
      - cache:/cache
    depends_on:
      - vite_dev
    ports:
      - "3000:3000"
    environment:
      RAILS_ENV: local_dev
      VITE_RUBY_HOST: vite_dev
      VITE_RUBY_PORT: 3036
      VITE_RUBY_MODE: development
    command:
      - /bin/bash
      - -c
      - |
        chmod 777 /tmp/shared
        rm -f /myapp/tmp/pids/server.pid
        bundle install
        bundle exec rails db:migrate
        bin/rails s -p 3000 -b 0.0.0.0
volumes:
  bundle_data:
  tmp_shared:
  cache:
  node_modules_cache:

Railsプロジェクトをおいたディレクトリからコンテナを立ち上げて、プログラムの修正はホストマシンのエディタで行う想定です。なお、ローカル環境のRAILS_ENVlocal_devとしています。

■ vite_devコンテナ

  • 3036ポートでコンテナ起動時に Viteサーバーを立ち上げる
  • node_modules ディレクトリはヴォリュームを作成して永続化
  • RAILS_ENV でdevelopmentを指定するとviteサーバーがdevelopmentモードで起動される
  • RAILS_ENV を使うとわかりづらい場合は NODE_ENV で設定してもよい
  • __VITE_ADDITIONAL_SERVER_ALLOWED_HOSTS にvite_devコンテナのサービス名を指定
    • この設定がないとcurlコマンドでviteサーバーの疎通確認の際にNOT ALLOWEDエラーとなる(※viteサーバーとの接続確認の項目で後述)

■ railsコンテナ

  • 3000ポートでコンテナ起動時に Railsサーバーを立ち上げる
  • 環境変数 VITE_RUBY_HOSTVITE_RUBY_PORT を設定してvite_devコンテナをViteサーバーに指定
  • 環境変数 VITE_RUBY_MODE を development にする
    • この設定がないと RAILS_ENV の値が使われてしまう
    • 設定しとかないとViteサーバーと接続できずに vite_react_refresh_tag タグが期待通りに出力されない
2-2. Viteサーバーの設定

Viteサーバーの設定は config/vite.jsonvite.config.js でします。Vite Rubyの公式サイトの説明を参考にします。

■ config/vite.json

{
  "all": {
    "sourceCodeDir": "app/javascript",
    "watchAdditionalPaths": []
  },
  "development": {
    "autoBuild": true,
    "publicOutputDir": "vite-dev"
  },
  "test": {
    "autoBuild": true,
    "publicOutputDir": "vite-test"
  }
}

■ vite.config.js

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import react from '@vitejs/plugin-react'
export default defineConfig({
  plugins: [
    RubyPlugin(),
    react()
  ],
  server: {
    host: '0.0.0.0',
    port: 3036,
    strictPort: true,
    hmr: {
      host: 'localhost',
    }
  },
})

3. 設定の際の注意点

ここからは作業を進めるうえで個人的に気になった箇所とハマった箇所について書きます。

3-1. VITE_RUBY_MODEの設定

railsコンテナでは環境変数にVITE_RUBY_MODEでdevelopmentを指定しました。これはvite_devコンテナで立ち上がってるViteサーバーと接続するために必要な設定となります。VITE_RUBY_MODEの設定がないとRAILS_ENVの値が使われますので、stagingやlocal_devといった RAILS_ENV を使っている場合はVITE_RUBY_MODEでdevelopmentの指定がないとviteサーバーと接続できません。

また蛇足ですが、Viteのbuildコマンドで本番環境のデプロイをするときはproductionモードでの実行となります。 Staging環境のようにRAILS_ENVがproduction以外でViteサーバーを立ち上げずにビルド後のファイルを提供したい時があると思います。そのときはVITE_RUBY_MODEにproductionで指定すると良いでしょう。

VITE_RUBY_MODEの設定について公式ページにも記載が見当たらないですが(2025/6/8現在)、サーバーにデプロイするうえでは知っておいた方が良いと思います。

3-2. gem vite:installでインストールされるモジュール

Gemモジュール vite_rails をインストールした後にviteのセットアップをするために一度だけ実行するのがgem vite:installです。 これを実行すると以下のファイルが新規作成もしくは編集されます。

  • bin/vite
  • app/views/application.html.erb
  • config/vite.json
  • vite.config.js

また必要なnodeモジュールもインストールされてデフォルトバージョンのものがインストールされます。 もしViteとは関係なくnodeモジュールのアップデートをしたあとにこのコマンドを実行するとバージョンが元に戻ることがあるので気をつけましょう。私は最初 compose.yml でviteコンテナの起動スクリプトにgem vite:installを含めていたためフロントエンドがうまく動かずにハマりました(汗)

3-3. viteサーバーとの接続確認

config/vite.jsonvite.config.jsで設定された内容が正しく反映されているか、railsコンテナからvite_devコンテナに接続できるかは以下で確認できます。

# rails c
> ViteRuby.config
=>
#<ViteRuby::Config:0x0000ffff62c3d6a8
 @config=
  {"additional_entrypoints"=>["~/{assets,fonts,icons,images}/**/*"],
   "asset_host"=>nil,
   "assets_dir"=>"assets",
   "auto_build"=>true,
   "build_cache_dir"=>#<Pathname:/myapp/tmp/cache/vite>,
   "public_output_dir"=>"vite-dev",
   "config_path"=>"config/vite.json",
   "dev_server_connect_timeout"=>0.01,
   "package_manager"=>"npm",
   "public_dir"=>"public",
   "entrypoints_dir"=>"entrypoints",
   "source_code_dir"=>"app/javascript",
   "skip_compatibility_check"=>false,
   "skip_proxy"=>false,
   "host"=>"vite_dev",
   "https"=>false,
   "port"=>3036,
   "hide_build_console_output"=>false,
   "vite_bin_path"=>nil,
   "watch_additional_paths"=>[],
   "base"=>"",
   "ssr_build_enabled"=>false,
   "ssr_entrypoint"=>"~/ssr/ssr.{js,ts,jsx,tsx}",
   "ssr_output_dir"=>#<Pathname:/myapp/public/vite-ssr>,
   "mode"=>"development",
   "root"=>#<Pathname:/myapp>}>
> ViteRuby.instance.dev_server_running?
=> true  

またコンテナ間で通信ができるかの確認はcurlコマンドでもできます。(vite_devコンテナで__VITE_ADDITIONAL_SERVER_ALLOWED_HOSTSを設定しておく必要があります)

# curl http://vite_dev:3036 -v  #vite_devはコンテナのサービス名
*   Trying XX.XX.XX.XX:3036...
* Connected to vite_dev (XX.XX.XX.XX) port 3036 (#0)
> GET / HTTP/1.1
> Host: vite_dev:3036
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Vary: Origin
< Location: /vite-dev/
< Date: Sun, 17 Aug 2025 02:08:34 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
* Connection #0 to host vite_dev left intact

上記がうまくいかないときは config/vite.jsonvite.config.js の設定を見直します。

3-4. Windowsでのファイルシステムの問題

Windows環境でWSL2にdockerをインストールしてる場合はうまくいかないことがありました。
Windowsのフォルダをコンテナにマウントして立ち上げてるとHMRが機能せずに、ソースコードを変更してもブラウザに変更が反映されませんでした。この原因はWSL2の制約でWindowsのファイルシステム上でおきたファイルの変更がコンテナで検知できないのが原因とのことです。なので永続化したボリュームを作成してコンテナでマウントし、Railsのプロジェクトはそのボリュームに保存しました。Windowsからはそのボリュームを共有フォルダとして編集できるようにセットアップしましょう。

4. 最後に

ハマりどころもありましたが無事HMRの設定が終わり快適に開発を進めれるようになりました。HMRはShakapackerで実現していた時より軽やかに動いてる気がします。トランスパイルも早くViteへの印象はかなり良好です。今回ハマった箇所もありますがドキュメント周りの整備が追いつけば解決していくでしょうし、Viteのツールのポテンシャルを考えると今後さらに普及するかもしれません。

さらに、今回の作業を進めるにあたりGeminiを調査に活用することで時短できました。ただGeminiでのアウトプットはインターネット上から確度の高いものを要約してるようで、vite-Rubyの情報に関しては物足りずにOSSのソースを読む必要がありました。こういった点ではエンジニアとしてのスキルが求められる場面はまだまだありますね。ただAIの進化は早いので今後も積極的に使って見極めていくのは大事だと思いました。

今回得れた知見が少しでも誰かの助力になれば幸いです。


JMDCでは、ヘルスケア領域の課題解決に一緒に取り組んでいただける方を積極採用中です! フロントエンド /バックエンド/ データベースエンジニア等、様々なポジションで募集をしています。 詳細は下記の募集一覧からご確認ください。 hrmos.co

まずはカジュアルにJMDCメンバーと話してみたい/経験が活かせそうなポジションの話を聞いてみたい等ございましたら、下記よりエントリーいただけますと幸いです。 hrmos.co

★最新記事のお知らせはぜひ X(Twitter)、またはBlueskyをご覧ください! twitter.com

bsky.app