Create a React SPA site with Vite
投稿日: 2024/03/12
更新日: 2024/07/02
This article is a translation of the following article.
Introduction
The last release of create-react-app was in 2022 and it has been removed from the official documentation. While frameworks like Next.js are generally recommended, there are times when you want a more lightweight setup. So, we will create an environment with Vite + React.
Basically, we will proceed according to the official guide, so it is recommended to also refer to the official guide.
This is the repository I created.
I used node version 20.11.0.
I used anyenv for installing node, but any environment where node can run is acceptable around here.
$ nodenv local 20.11.0 $ nodenv -v nodenv 1.4.1+79.15375bb $ node -v v20.11.0 $ npm -v 10.2.4
Here is the list of versions of the packages being used this time.
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" }
Setting Up Vite and React Environment
I will create a project using a template. This time, I used react-ts
, but there are also various other options available.
It seems that when using npm 7 or higher, four hyphens are required as follows.
$ npm create vite@latest vite-project -- --template react-ts
Run npm install
, and start the development server by running 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
When you access http://localhost:5173/
, you can see the page. (It might be http://localhost:3000/
.)
Setting up prettier code formatter
I want to enable automatic formatting when saving, so I will introduce Prettier.
Please follow the instructions in the documentation if you want to format using a command.
I will install extensions because I automatically format when saving in VSCode.
I will change the settings of VSCode so that the file is formatted when saved after adding extensions.
Select Settings
icon.
Search for defaultformatter
and change the Default Formatter to Prettier.
Search for format on save
and check the box for Format On Save
.
Add .prettierrc.json
at the end. Please configure it as you like.
.prettierrc.json
{ "printWidth": 80, "tabWidth": 2, "useTabs": false, "semi": false }
Path alias when importing
By adding the following settings
tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } },
vite.config.ts
export default defineConfig({ resolve: { alias: [{ find: "@/", replacement: `${__dirname}/src/` }], },
When you add a Example component like the following, you will be able to write import { Example } from "@/components/Example"
in App.tsx
.
src ├── App.tsx ├── components │ └── Example.tsx
I will install @types/node
because a warning Cannot find name '__dirname'.ts(2304)
is displayed.
npm i -D @types/node
By the way, separate settings are required to run it for testing, and they will be described later in the testing section. If you find these settings troublesome, using vite-tsconfig-paths seems like a good option.
Test
Introduction of Vitest and React Testing Library
This time, we will be using Vitest. Jest is also fine, but I really like Vitest because it works with concise configuration.
In order to write tests for React, we will also introduce React Testing Library. jsdom is installed as it is necessary for testing components.
npm install -D vitest @testing-library/react @testing-library/user-event jsdom
Next, we will add configurations to the package.json
so that you can run the tests using the npm run test
command.
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 and userEvent
There are fireEvent
and userEvent
methods that can be used to test events such as clicking a button within a component. It is generally recommended to use userEvent
.
fireEvent
is a low-level event trigger function that dispatches DOM events, while userEvent
provides a higher-level simulation to more accurately replicate actual user interactions.
Write actual tests
First, I will add the necessary settings for the test.
The contents written in vitest.config.ts
can also be written in vite.config.ts
, but I believe that separating the test settings into a separate file makes it look cleaner, so I have separated them.
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"],
By adding globals: true
and "types": ["vitest/globals"]
, you will be able to use describe
and test
functions in each test file without explicitly importing them, similar to Jest.
I am setting up alias: { "@": path.resolve(__dirname, "./src") }
to make path aliases work even during testing.
First, we will write a test for a simple function. Add src/helpers/example.ts
and 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") }) })
Next, I will write tests for the components. I will add src/App.test.tsx
.
This time, I am testing whether a specific string is displayed when a component is rendered and whether the count of characters increases when a button is pressed.
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") }) })
I will execute the test (by the way, it is very convenient because the test will be automatically run again when you save the test file).
$ 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
Page Routing with React Router
Since there is only a homepage, I will enable the ability to display other pages.
Installing React Router
Install React Router.
npm i react-router-dom
Introduction
You just need to write as follows. You can add routing inside 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> )
Example
Let's try displaying the App component that was initially used.
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> )
You can view it at http://localhost:5173/app
.
CI
Set up to automatically run tests when pushing using Github Actions.
.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
It feels good.
Here we are not currently doing it, but it is a good idea to cache the result of npm install
using actions/cache
. Additionally, it is recommended to set up rules so that you cannot merge into the main branch when tests fail (by the way, you can set up branch protection rules for free in public repositories, but not in private repositories for free).
Table of Contents