Railsにvite+react+typescript入れて高速に開発できる環境を目指す

Railsを--skip-javascriptでJS周りなしで作成する
.gitignoreがjs周りの記述ないので、-j esbuild
オプション付きでrails newした時の.gitignoreに書き換える
内容
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore all environment files (except templates).
/.env*
!/.env*.erb
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/
!/tmp/storage/.keep
/public/assets
# Ignore master key for decrypting credentials and more.
/config/master.key
/app/assets/builds/*
!/app/assets/builds/.keep
/node_modules

viteを1から設定するのもありだが、やること多いのでvite_railsを使って構築していく
必要になるまではなるべく独自の実装を避けて環境の複雑化を避けていこう

ドキュメントに従って構築する
- Gemfileに
gem 'vite_rails'
を追加 $ bundle install
-
bundle install
した時にbundle exec vite upgrade
をするようにメッセージが出たので一応やっておく $ bundle exec vite install
ドキュメントに書いているのはここまで。
--skip-javascript
のオプションの影響でProcfile.devが使えずに、viteとrailsどちらも別ターミナルで起動する必要があるので、まだ起動確認はしない。

--skip-javascript
のオプションの影響で色々調整必要なので対応していく。
Procfile.devを使えるようにする
-j esbuild
でrails newした時に生成されるbin/dev
はProcfile.devを使う前提で作られているものなので、これと同じのを作る。
$ vi bin/dev
- 以下のコードを書き込む
#!/usr/bin/env sh
if ! gem list foreman -i --silent; then
echo "Installing foreman..."
gem install foreman
fi
# Default to port 3000 if not specified
export PORT="${PORT:-3000}"
exec foreman start -f Procfile.dev "$@"
- ファイルの実行権限を変える
$ chmod 755 bin/dev
$ bin/dev
で起動して、http://localhost:3000 で開けるか確認する

viteの設定や動作確認するための仮のページを作る
1 $ rails g controller home index
2. routes.rbでルートパスに設定するroot "home#index"
3. testとhelperはいらないのでファイル削除

viteのHMRが効いているか確認する
- bin/devでRailsを起動する
- ルートパスにアクセス
- エディタで
app/frontend/entrypoints/application.js
を開く - console.logやalertなど変化がわかるようなコードを書き込む
- 画面をリロードせずに変更したコードが実行される

reactとtsを入れる
-
rootディレクトリで
$ yarn create vite frontend --template react-ts
を実行
viteが用意してくれているコマンドを使ってベースを生成する -
以下は不要なので削除する
frontend/public/*
frontend/src/*
frontend/.gitignore
frontend/index.html
frontend/README.md
frontend/tsconfig.node.json
- 以下をルートディレクトリに移動する
frontend/.eslintrc.cjs
frontend/tsconfig.json
frontend/.eslintrc.cjs
-
vite.config.ts
をルートのファイルとマージする
import react from '@vitejs/plugin-react'
とplugins: [react()]
の部分
-
package.json
をルートのファイルとマージする
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^5.0.7",
"vite-plugin-ruby": "^5.0.0",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.2.0",
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"typescript": "^5.2.2"
}
}
- tsconfigを調整
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
- "include": ["src"],
+ "include": ["app/frontend/**/*"]
- "references": [{ "path": "./tsconfig.node.json" }]
}

tsの動作確認
-
frontend/app/application.ts
にする -
vite_javascript_tag
をvite_typescript_tag
に変更する - サーバー再起動
- 読み込まれることを確認

reactの動作確認
-
application.html.erb
に<%= vite_react_refresh_tag %>
を追加
これを追加してくれる。
参考:https://vitejs.dev/guide/backend-integration.html
<script type="module">
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
-
application.html.erb
を変更する
<!DOCTYPE html>
<html>
<head>
<title>App</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 %>
<%= vite_typescript_tag 'application' %>
+ <%= yield :custom_script %>
</head>
<body>
<%= yield %>
</body>
</html>
-
app/frontend/src/componets/Test.tsx
を作って適当なコンポーネント作る -
app/frontend/entrypoints/test.tsx
を作成
import { createRoot } from "react-dom/client";
import { TestApp } from "../src/componets/Test";
const domNode = document.getElementById("root");
if (!domNode) {
throw new Error("No root element found");
}
const root = createRoot(domNode);
root.render(<TestApp />);
-
app/views/home/index.html.erb
を変更
<% content_for :custom_script do %>
<%= vite_typescript_tag 'test.tsx' %>
<% end %>
<div id="root"></div>
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>