Noh | エンジニア向け情報共有コミュニティ
Signup / Login

Vite で React の SPA サイトを作成する

javascript
typescript
vite
react

投稿日: 2024/03/12

更新日: 2024/03/15

はじめに

create-react-appが最後にリリースされたのも2022年で、公式ドキュメントからも削除されました。基本的にNext.js等のフレームワークを推奨しているようですが、もっと手軽に動かしたいときも結構あります。そこでVite + Reactの環境を作ります。

基本的に公式ガイドに沿って進めていくので、公式ガイドの方も見ることをお勧めします。

作成したリポジトリです。

https://github.com/yosipy/mui5_example

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

コンポーネント内のボタンをクリックするなどのイベントのテストを行う際に使用できるメソッドにfireEventuserEventがあります。基本的に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しなくてもdescribetest関数が使えるようになります(Jestのように)。

テスト中でもパスエイリアスを動作させるためにalias: { "@": path.resolve(__dirname, "./src") }で設定しています。

まずは単純な関数のテストを書いてみます。src/helpers/example.tssrc/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ブランチにマージできないようにルールを設定しておくといいです(ちなみにパブリックリポジトリは無料でブランチ保護ルールを設定できますが、プライベートリポジトリは無料ではできません)。

yosi

Noh を作ってるエンジニア。