Open9

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を使って構築していく

必要になるまではなるべく独自の実装を避けて環境の複雑化を避けていこう

まるべいじまるべいじ

https://github.com/ElMassimo/vite_ruby/tree/main/vite_rails

ドキュメントに従って構築する

  1. Gemfileにgem 'vite_rails'を追加
  2. $ bundle install
  3. bundle installした時にbundle exec vite upgradeをするようにメッセージが出たので一応やっておく
  4. $ bundle exec vite install

ドキュメントに書いているのはここまで。
--skip-javascriptのオプションの影響でProcfile.devが使えずに、viteとrailsどちらも別ターミナルで起動する必要があるので、まだ起動確認はしない。

まるべいじまるべいじ

--skip-javascriptのオプションの影響で色々調整必要なので対応していく。

Procfile.devを使えるようにする

-j esbuildでrails newした時に生成されるbin/devはProcfile.devを使う前提で作られているものなので、これと同じのを作る。

  1. $ vi bin/dev
  2. 以下のコードを書き込む
#!/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 "$@"
  1. ファイルの実行権限を変える
$ 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が効いているか確認する

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

reactとtsを入れる

  1. rootディレクトリで$ yarn create vite frontend --template react-tsを実行
    viteが用意してくれているコマンドを使ってベースを生成する

  2. 以下は不要なので削除する

  • frontend/public/*
  • frontend/src/*
  • frontend/.gitignore
  • frontend/index.html
  • frontend/README.md
  • frontend/tsconfig.node.json
  1. 以下をルートディレクトリに移動する
  • frontend/.eslintrc.cjs
  • frontend/tsconfig.json
  • frontend/.eslintrc.cjs
  1. vite.config.tsをルートのファイルとマージする

import react from '@vitejs/plugin-react'plugins: [react()]の部分

  1. 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"
  }
}

  1. 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の動作確認

  1. frontend/app/application.tsにする
  2. vite_javascript_tagvite_typescript_tagに変更する
  3. サーバー再起動
  4. 読み込まれることを確認
まるべいじまるべいじ

reactの動作確認

  1. 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>
  1. 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>

  1. app/frontend/src/componets/Test.tsxを作って適当なコンポーネント作る
  2. 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 />);
  1. 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>