Top View


Author Daiki Urata

vite_railsをRailsに導入してReact/Vite環境を構築

2022/07/07

はじめに

Rails7になってからjsbundling-rails が使われるようになり、esbuild/rollup/wepackなどJavaScriptをバンドルしてRailsアプリで配信する方法の選択肢は増えました。

これらを使うのも良いですが、より強力なフロントエンドツールであるViteを使うことで効率的なフロントエンド開発を行うことができます。

さらにvite_railsというgemを導入することで以前のwebpackerのようなTag HelperによるJSファイルの読み込みや、Rakeタスクassets:precompile でのViteアプリのビルドを行ってくれるようになります。

今回はvite_railsを使って、Vite + React(TypeScript)環境の構築を目指します。

Railsアプリを作成

まずは rails new でRailsアプリを作成します。

$ rails new vite-rails-app --minimal
$ bundle install
$ rails db:create

vite_railsをインストール

次にvite_railsをインストールして、vite_railsで提供されているvite install コマンドでVite環境のセットアップを行います。

$ bundle add vite_rails
$ bundle exec vite install

この時既存のファイルを消すか聞かれると思いますが、消しても問題ありません。 frontendディレクトリが作成されたり、vite.config.tsファイルが作成されたりすると思います。

Reactアプリを作成

Vite環境ができたら次はReactが動くようにします。

$ cd app/frontend
$ npm create vite@latest . -- --template react-ts
$ rm package.json vite.config.ts index.html src/main.tsx

Viteセットアップ時に作成されたファイルと被ったり、不要なファイルがあるので削除しています。

package.jsonとvite.config.tsファイルは被ってしまったのでプロジェクトルートに統一して、以下のように統合します。

package.json

{
  "name": "react",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.0.0",
    "@types/react-dom": "^18.0.0",
    "@vitejs/plugin-react": "^1.3.0",
    "typescript": "^4.6.3",
    "vite": "^2.9.13",
    "vite-plugin-ruby": "^3.0.12"
  }
}

vite.config.ts

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    RubyPlugin(),
    react(),
  ],
})

package.jsonを編集したのでプロジェクトルートへ戻り、以下を実行します。

$ npm install

そして今回はReactアプリでHMRを使うためにさらに vite_react_refresh_tag をapp/views/application.html.erbに追加します。

<!DOCTYPE html>
<html>
  <head>
    <title>ViteRailsApp</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application" %>
    <%= vite_client_tag %>
    <%= vite_react_refresh_tag %>
    <!--
      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>
    <%= yield %>
  </body>
</html>

さらにエントリポイントファイルとしてhome.tsxをapp/frontend/entrypoints以下に用意します。

app/frontend/entrypoints/home.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '../src/App'
import '../src/index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

これでReact環境が整いました。あとはRailsのViewから読み込むだけです。

Viewを作成

Rails側でControllerとViewを用意します。

$ rails g controller home index

app/views/home/index.html.erb

<div id="root"></div>
<%= vite_javascript_tag 'home.tsx' %>

最後にroutes.rbを編集

Rails.application.routes.draw do
  root 'home#index'
end

これで準備は整いました。動かしてみましょう。

起動

RailsサーバーとViteサーバーをそれぞれ立ち上げても良いですが、一緒に立ち上げたいので foreman をインストールします。

$ gem install foreman

そして、以下コマンドで起動できます。

$ foreman start -f Procfile.dev

それぞれ起動したらブラウザでアクセスするとReactの初期画面が見えるはずです。

ControllerからReactへデータを渡す

SPAでアプリを作る場合はRails側でAPIを用意してReact側とデータをやりとりすれば良いですが、各ViewからそれぞれReactのエントリポイントを読み込んで使う場合、Rails側で用意したインスタンス変数を渡したい場合もあると思います。

そのやり方を少しご紹介します。

まずはController側で @message というのを用意してこれをReactのコンポーネントへ渡してみます。

app/controllers/home_controller.rb

class HomeController < ApplicationController
  def index
    @message = "Hello, world!"
  end
end

content_tag ヘルパーを使ってJS側にデータを伝えます。

app/views/home/index.html.erb

<%= content_tag :div, "", id: "root", data: { message: @message }  %>
<%= vite_javascript_tag 'home.tsx' %>

エントリポイントファイルで受け取ったデータをReact側へ渡します。

app/frontend/entrypoints/home.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from '../src/App'
import '../src/index.css'

const element = document.getElementById('root')

ReactDOM.createRoot(element!).render(
  <React.StrictMode>
    <App {...element?.dataset} />
  </React.StrictMode>
)

Reactのコンポーネント側ではpropsに渡ってくるので、表示してみます。

app/frontend/src/App.tsx

import './App.css'

type Props = {
  message?: string;
}

function App(props: Props) {
  return (
    <div className="App">
      {props.message}
    </div>
  )
}

export default App

これで各Viewごとにエントリポイントファイルをapp/frontend/entrypoints以下に用意して、Viewでは vite_javascript_tag ヘルパーでそれぞれ呼び出すような使い方もできそうです。

おわりに

vite_rails(vite_ruby) を使うことで、Railsに簡単にReactを導入することができ、しかも高速な開発サーバーや便利なViteプラグインが使えるので効率の良いフロントエンド開発が行えます。

さらにvite_railsではデプロイ時も通常通り assets:precompile コマンドを実行するだけでViteでのビルドが行われるため、Railsのデプロイ方法も特に変わりません。

そのためフロントエンドまわりが詳しくない方でも簡単に導入でき、すぐに開発が始められるので、気になる方は是非試してみてください。

Daiki Urata

Daiki Urata

Twitter X

フロントエンド/モバイルアプリなどを主に開発しています。