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/.gitignorefrontend/index.htmlfrontend/README.mdfrontend/tsconfig.node.json
- 以下をルートディレクトリに移動する
frontend/.eslintrc.cjsfrontend/tsconfig.jsonfrontend/.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>