Vite で React の SPA サイトを作成する
投稿日: 2024/03/12
更新日: 2024/03/15
はじめに
create-react-appが最後にリリースされたのも2022年で、公式ドキュメントからも削除されました。基本的にNext.js等のフレームワークを推奨しているようですが、もっと手軽に動かしたいときも結構あります。そこでVite + Reactの環境を作ります。
基本的に公式ガイドに沿って進めていくので、公式ガイドの方も見ることをお勧めします。
作成したリポジトリです。
nodeのバージョンは 20.11.0 を使いました。
nodeのインストールにanyenvを使いましたが、この辺りはnodeが動く開発環境があれば何でもいいです。
$ nodenv local 20.11.0 $ nodenv -v nodenv 1.4.1+79.15375bb $ node -v v20.11.0 $ npm -v 10.2.4
また、今回使用しているpackagesのバージョン一覧です。
package.json
"dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3" }, "devDependencies": { "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^14.5.2", "@types/node": "^20.11.26", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "jsdom": "^24.0.0", "typescript": "^5.2.2", "vite": "^5.1.6", "vitest": "^1.3.1" }
ViteとReactの環境構築
テンプレートを用いてプロジェクトを作成します。今回はreact-ts
を使いましたが、他にもいろいろ準備されています。
npm 7以上のときは以下のように4つのハイフンが必要なみたいです。
$ npm create vite@latest vite-project -- --template react-ts
npm installして、npm run dev
で開発用のサーバーを起動します。
$ cd vite-project $ npm i $ npm run dev > [email protected] dev > vite VITE v5.1.6 ready in 96 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h + enter to show help
http://localhost:5173/
にアクセスするとページが確認できます。(http://localhost:3000/
かもしれないです )
prettier コードフォーマッターの設定
保存時に自動フォーマットしたいのでPrettierを導入します。
コマンドでフォーマットしたい人はドキュメントに従って導入してください。
僕はVSCodeで保存時に自動フォーマットするので、拡張を入れます。
拡張を入れたらファイル保存時にフォーマットされるようにVSCodeの設定を変更します。
設定アイコン
-> Settings
を選択します。
defaultformatter
で検索し、Default FormatterをPrettierにします。
format on save
で検索し、Format On Save
にチェックを入れます。
最後に.prettierrc.json
を追加します。お好みで設定してください。
.prettierrc.json
{ "printWidth": 80, "tabWidth": 2, "useTabs": false, "semi": false }
import 時のパスエイリアス
以下のように設定を追加することで
tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } },
vite.config.ts
export default defineConfig({ resolve: { alias: [{ find: "@/", replacement: `${__dirname}/src/` }], },
以下のようなExampleコンポーネントを追加したときに、App.tsx
中でimport { Example } from "@/components/Example"
と書けるようになります。
src ├── App.tsx ├── components │ └── Example.tsx
Cannot find name '__dirname'.ts(2304)
というワーニングが出るので@types/node
をインストールします。
npm i -D @types/node
ちなみにテストで動作させるために別設定が必要なので、テストの項目で後述します。この辺の設定が面倒であればvite-tsconfig-pathsを使うのもよさそうです。
テスト
Vitest と React Testing Library の導入
今回はVitestを使用します。Jestでもいいですが、Vitestは簡潔な設定で動作し非常に気に入ってます。
Reactのテストを書くために
React Testing Libraryも導入します。jsdomはコンポーネントのテスト時に必要なので入れています。
npm install -D vitest @testing-library/react @testing-library/user-event jsdom
次にpackage.json
に設定を追加してnpm run test
のコマンドでテストを実行できるようにします。
package.json
"scripts": { "dev": "vite", "build": "tsc && vite build", + "test": "vitest", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" },
fireEvent と userEvent
コンポーネント内のボタンをクリックするなどのイベントのテストを行う際に使用できるメソッドにfireEvent
とuserEvent
があります。基本的にuserEvent
を使用しておけばいいです。
fireEvent
はDOMイベントを発行(dispatch)する低レベルなイベントトリガー関数で、userEvent
はより高レベルなシミュレーションを提供し実際のユーザーの操作をより正確に再現します。
実際にテストを書く
まずはテストに必要な設定を追記します。
vitest.config.ts
に書いている内容はvite.config.ts
に書くこともできますが、テスト用の設定は別ファイルに分けた方がきれいに見えると思い僕は分けています。
vitest.config.ts
import * as path from "path" import { defineConfig } from "vitest/config" export default defineConfig({ test: { globals: true, environment: "jsdom", alias: { "@": path.resolve(__dirname, "./src"), }, }, })
tsconfig.json
"paths": { "@/*": ["./src/*"] - } + }, + + /* For Vitest*/ + "types": ["vitest/globals"] }, "include": ["src"],
globals: true
と"types": ["vitest/globals"]
を追記したことで、各テストファイル内で明示的にImportしなくてもdescribe
やtest
関数が使えるようになります(Jestのように)。
テスト中でもパスエイリアスを動作させるためにalias: { "@": path.resolve(__dirname, "./src") }
で設定しています。
まずは単純な関数のテストを書いてみます。src/helpers/example.ts
とsrc/helpers/example.test.ts
を追加します。
src/helpers/example.ts
export const example = () => { return "example" }
src/helpers/example.test.ts
import { example } from "./example" describe("example", () => { test("文字列を返す", () => { expect(example()).toEqual("example") }) })
次にコンポーネントのテストを書いてみます。src/App.test.tsx
を追加します。
今回は試しにコンポーネントレンダリング時に任意の文字列が表示されているかのテストと、ボタンを押したときにcountの文字が増えるかどうかのテストをしています。
src/App.test.tsx
import { render, screen } from "@testing-library/react" import userEvent from "@testing-library/user-event" import App from "./App" describe("App", () => { test("renders App component", () => { render(<App />) screen.getByText("Click on the Vite and React logos to learn more") }) test("change count", async () => { const user = userEvent.setup() render(<App />) await user.click(screen.getByRole("button", { name: /count is 0/i })) screen.getByText("count is 1") }) })
テスト実行します(ちなみにテストファイルを保存したりすると自動でテストが再実行されてすごく便利です)。
$ npm run test > [email protected] test > vitest DEV v1.3.1 /home/user_name/projects/github.com/yosipy/mui5_example ✓ src/helpers/example.test.ts (1) ✓ src/App.test.tsx (2) Test Files 2 passed (2) Tests 3 passed (3) Start at 19:23:03 Duration 751ms (transform 166ms, setup 0ms, collect 214ms, tests 45ms, environment 313ms, prepare 251ms) PASS Waiting for file changes... press h to show help, press q to quit
React Router でのページルーティング
トップページしかないので、他のページを表示できるようにします。
React Router のインストール
React Routerをインストールします。
npm i react-router-dom
導入
以下のように書くだけです。createBrowserRouter
の中にルーティングを追加していくことができます。
src/main.tsx
import React from "react" import ReactDOM from "react-dom/client" import { createBrowserRouter, RouterProvider } from "react-router-dom" import "./index.css" const router = createBrowserRouter([ { path: "/", element: <div>Hello world!</div>, }, ]) ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <RouterProvider router={router} /> </React.StrictMode> )
例
最初に使っていたAppコンポーネントを表示してみます。
src/main.tsx
import React from "react" import ReactDOM from "react-dom/client" import { createBrowserRouter, RouterProvider } from "react-router-dom" import "./index.css" import App from "./App" const router = createBrowserRouter([ { path: "/", element: <div>Hello world!</div>, }, { path: "/app", element: <App />, }, ]) ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <RouterProvider router={router} /> </React.StrictMode> )
http://localhost:5173/app
で表示できます。
CI
Github Actionsを使ってPush時に自動的にテストが実行されるようにします。
.github/workflows/ci.yml
name: CI on: push: jobs: frontend-test: name: Frontend test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" - name: npm ci run: npm ci - name: Lint run: npm run lint - name: Run test run: npm run test
いい感じです。
ここではやっていませんが npm installした結果をactions/cache
を用いてキャッシュしておくといいです。
また、テストが落ちたときはmainブランチにマージできないようにルールを設定しておくといいです(ちなみにパブリックリポジトリは無料でブランチ保護ルールを設定できますが、プライベートリポジトリは無料ではできません)。