Table of Contents
はじめに
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のデプロイ方法も特に変わりません。
そのためフロントエンドまわりが詳しくない方でも簡単に導入でき、すぐに開発が始められるので、気になる方は是非試してみてください。
Related Posts
Daiki Urata
2024/06/10
Daiki Urata
2023/05/22