Reactの開発環境を改めて作ってみた
Reactの開発環境構築
毎回Reactの開発環境構築時にいろんなサイトを彷徨って、結局毎回異なる環境になってしまうため、改めて環境構築をまとめてみました。
構築する環境は以下の通りです。
- VSCode(RemoteContainer)
- React(TypeScript)
- Vite
- Prettier
- ESLint(Airbnb)
- lint-staged
- Husky
- Jest
Node.jsの環境作成
RemoteContainerの説明は割愛します。
devcontainerの設定を追加していきます。
devcontainer.jsonの例
よく使う拡張機能を盛り込んでいます。
{
"name": "React Typescript Starter",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"christian-kohler.path-intellisense",
"mhutchie.git-graph",
"VisualStudioExptTeam.vscodeintellicode",
"formulahendry.auto-rename-tag",
"formulahendry.auto-close-tag"
]
}
}
}
準備ができたらRemoteContainerの機能を使ってコンテナを立ち上げます。
ViteでReactのプロジェクトを作成
Vite公式でコマンドを確認。
私自身がyarn愛好家なので今回はyarnで実施。npmやpnpm派の方は置き換えて実施してください。
yarn create vite
Project name → 任意のプロジェクト名
Select a framework →React
Select a variant →TypeScript
orTypeScript + SWC
プロジェクトの作成が完了したら、ルートディレクトリにProject name
で入力した名でフォルダが作成されます。
今回はルートディレクトリにプロジェクトを配置したいので、フォルダ内のファイル/フォルダをルートに移動します。
この時点では依存パッケージがインストールされていないので、yarn install
でインストールする。node_modules
がルートディレクトリに作成されれば準備完了。
念のため、開発サーバーを立ち上げてページが確認できることを確認しましょう。
yarn dev
Prettier追加
以下のコマンドを実行して、prettierを追加します。
yarn add -D prettier
フォーマットの設定を行いたい場合は、.prettierrc
を作成し設定を追加してください。
VSCodeで保存時に自動でフォーマットをかけてほしいので、vscodeの設定を変更します。
今回はdevcontainer.jsonに追記していきます。
{
"name": "React Typescript Starter",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",
"customizations": {
"vscode": {
+ "settings": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+ },
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"christian-kohler.path-intellisense",
"mhutchie.git-graph",
"VisualStudioExptTeam.vscodeintellicode",
"formulahendry.auto-rename-tag",
"formulahendry.auto-close-tag"
]
}
}
}
コンテナのリビルドが必要なので、リビルドを行います。
リビルドが完了したら、任意のファイルを開きPrettierでフォーマットされるか確認します。
ESLint(Airbnb)追加
ちょっとした開発には制限が厳しいかもしれませんが、TypeScriptの正しい使い方など勉強にもなるので、制約厳しめのAirbnbのlintを使用します。
以前まではESLintの初期化コマンドでAirbnbを選択できていたのですが、今はできないので一旦Standardの設定で初期化し、その後手動でAirbnbの設定を追加していきます。
ESLintの初期化
以下のコマンドを実行し、ESLintをインストールします。
yarn add -D eslint
次に以下のコマンドを実行し、ESLintの初期化を行います。
yarn eslint --init
CLIの入力
How would you like to use ESLint? → To check syntax, find problems, and enforce code style
What type of modules does your project use? → JavaScript modules (import/export)
Which framework does your project use? → React
Does your project use TypeScript? → Yes
Where does your code run? → Browser
How would you like to define a style for your project? → Use a popular style guide
Which style guide do you want to follow? → Standard: ~
What format do you want your config file to be in? → JavaScript
ファイルのフォーマットはご自由に。(今回はJavaScriptを選択しています)
初期化が無事完了したら、ルートディレクトリに.eslintrc.cjs
が作成されます。
Airbnbのスタイルガイドを追加する
初期で導入したStandardのスタイルガイドをアンインストールし、新たにAirbnbのスタイルガイドを導入します。
以下のコマンドを実行して、Standardのスタイルガイドをアンインストールします。
yarn remove eslint-config-standard-with-typescript
続けてAirbnbのスタイルガイドやその他諸々を導入していきます。
yarn add -D @typescript-eslint/parser eslint-config-{airbnb,airbnb-typescript,prettier} eslint-plugin-{react-hooks,jsx-a11y}
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"plugin:react/recommended",
+ "plugin:@typescript-eslint/recommended",
+ "airbnb",
+ "airbnb-typescript",
+ "airbnb/hooks",
+ "prettier",
- "standard-with-typescript",
],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
- sourceType: "module",
+ project: ["./tsconfig.json"],
},
- plugins: ["react"],
+ ignorePatterns: ["vite.config.ts", ".eslintrc.cjs"],
+ settings: {
+ react: {
+ version: "detect",
+ },
+ },
rules: {
+ // Airbnbのルールから一部無効化
+ // Reactの明示的なインポートを不要にする(Reactv17で対応)
+ "react/react-in-jsx-scope": "off",
},
};
.eslintrc.cjsの完成形
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"prettier",
],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
project: ["./tsconfig.json"],
},
ignorePatterns: ["vite.config.ts", ".eslintrc.cjs"],
settings: {
react: {
version: "detect",
},
},
rules: {
// Airbnbのルールから一部無効化
// Reactの明示的なインポートを不要にする(Reactv17で対応)
"react/react-in-jsx-scope": "off",
},
};
上記の設定で保存した後、App.tsxでエラーが発生すれば設定完了です。
Airbnbのガイドに沿って修正したApp.tsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button
type="button"
onClick={() => setCount((prevCount) => prevCount + 1)}
>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
);
}
export default App;
importの自動ソートや不要なimport文を削除するように設定する
ファイルごとにimportの順が平仄あってなかったり、使ってないライブラリがあった場合整理したいなど思うことがあると思います。(少なくとも私は思います)
その問題を解決すべく、プラグインを導入していきます。
yarn add -D eslint-plugin-{import,unused-imports}
インストールできたら、.eslinttc.cjs
を編集します。
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"prettier",
],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
project: ["./tsconfig.json"],
},
ignorePatterns: ["vite.config.ts", ".eslintrc.cjs"],
settings: {
react: {
version: "detect",
},
},
+ plugins: ["import", "unused-imports"],
rules: {
// Airbnbのルールから一部無効化
// Reactの明示的なインポートを不要にする(Reactv17で対応)
"react/react-in-jsx-scope": "off",
+ "import/order": [
+ // 条件に沿ったソート順になっていないと警告する
+ "warn",
+ {
+ groups: [
+ "builtin",
+ "external",
+ "parent",
+ "sibling",
+ "index",
+ "object",
+ "type",
+ ],
+ pathGroups: [
+ // React関係のimportは最上部にソートする
+ {
+ pattern: "{react,react-dom/**,react-router-dom}",
+ group: "builtin",
+ position: "before",
+ },
+ ],
+ alphabetize: {
+ order: "asc",
+ },
+ "newlines-between": "always",
+ },
+ ],
}
};
.eslintrc.cjsの完成形
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"prettier",
],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
project: ["./tsconfig.json"],
},
ignorePatterns: ["vite.config.ts", ".eslintrc.cjs"],
settings: {
react: {
version: "detect",
},
},
plugins: ["import", "unused-imports"],
rules: {
// Airbnbのルールから一部無効化
// Reactの明示的なインポートを不要にする(Reactv17で対応)
"react/react-in-jsx-scope": "off",
"import/order": [
// 条件に沿ったソート順になっていないと警告する
"warn",
{
groups: [
"builtin",
"external",
"parent",
"sibling",
"index",
"object",
"type",
],
pathGroups: [
// React関係のimportは最上部にソートする
{
pattern: "{react,react-dom/**,react-router-dom}",
group: "builtin",
position: "before",
},
],
alphabetize: {
order: "asc",
},
"newlines-between": "always",
},
],
},
};
保存後、App.tsx
で警告が出ていれば正常に設定できています。
ただこのままでは、警告が出ているだけで手動で修正が必要になります。そんなことは手間なのでVSCodeに自動で行ってもらえるように設定しましょう。
devcontainer.json
を以下の通りに編集してください。
{
"name": "React Typescript Starter",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",
"customizations": {
"vscode": {
"settings": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": true,
+ "source.fixAll.eslint": true
+ }
},
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"christian-kohler.path-intellisense",
"mhutchie.git-graph",
"VisualStudioExptTeam.vscodeintellicode",
"formulahendry.auto-rename-tag",
"formulahendry.auto-close-tag"
]
}
}
}
devcontainer.jsonの完成形
{
"name": "React Typescript Starter",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",
"customizations": {
"vscode": {
"settings": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.fixAll.eslint": true
}
},
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"christian-kohler.path-intellisense",
"mhutchie.git-graph",
"VisualStudioExptTeam.vscodeintellicode",
"formulahendry.auto-rename-tag",
"formulahendry.auto-close-tag"
]
}
}
}
保存ができたら、コンテナの再ビルドを行ってください。
App.tsx
を開いて保存をすると、Prettierのフォーマットおよびimportの整理が行われることを確認してください。
lint-staged + husky追加
パッケージのインストール
Gitにコミットするファイルはルールに従った状態のものをだけにしましょう。違反しているものをコミットするとESLintを導入している意味がありませんので。
ステージングしているファイルを確認することができるlint-staged
とコミット時の動作をカスタムできるHusky
を導入していきます。
以下のコマンドを実行して、必要なパッケージをインストールします。
yarn add -D husky lint-staged
lint-stagedの設定
package.json
に設定を追加します。
…
"lint-staged": {
"src/**/*.{js,ts,tsx}": [
// 警告1件でもあればエラー
"yarn eslint --max-warnings 0",
"yarn prettier -w"
]
}
追記が完了したら、実際に動作するか確認します。あえてエラーやフォーマットされていない状態のApp.tsx
を用意しました。
この状態でApp.tsx
をステージングして、以下のコマンドを実行してみます。
yarn lint-staged
main.tsx
も警告がありますが、ステージングされたApp.tsx
だけが静的解析されていることがわかります。
では実際に問題点を修正して、prettierのフォーマットが行われるか確認します。(基本的には保存時にフォーマットされるように設定しているので、あまり意味はないですが…)
無事フォーマットされたことが確認できました。
huskyの設定
以下のコマンドを実行して、huskyの実行ファイルを用意します。
yarn husky install
コマンド実行後、ルートディレクトリに.husky
フォルダが作成されていれば初期セットアップは完了となります。
commit前にlint-stagedのコマンドが実行されるように設定を追加します。
yarn husky add .husky/pre-commit "yarn lint-staged"
コマンド実行が成功すると.husky/pre-commit
ファイルが作成されます。
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged
実際にコミット前にlint-stagedが実行されるか確認します。
実際にコミットを実行しようとすると、静的解析が始まりエラーが表示されコミットが中止されました。
husky install
が適宜実行されるようにpackage.json
にスクリプトを追加しておきます。
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
+ "prepare": "husky install",
},
Jest追加
パッケージ導入・セットアップ
必要なパッケージをインストールします。トランスパイラにts-jest
を使うことが多いですが、コンパイルの時間がかかりがちなので最近ホット?な@swc/jest
を使用します。
yarn add -D jest @types/jest @swc/{core,jest} jest-environment-jsdom @testing-library/{jest-dom,react,react-hooks,user-event}
インストールが完了したら、jestの設定ファイルを作成します。
module.exports = {
roots: ["<rootDir>/test"],
testMatch: ["**/?(*.)+(spec|test).+(js|jsx|ts|tsx)"],
transform: {
"^.+\\.(ts|tsx)$": [
"@swc/jest",
{
sourceMaps: true,
module: {
type: "commonjs",
},
jsc: {
parser: {
syntax: "typescript",
tsx: true,
},
transform: {
react: {
runtime: "automatic",
},
},
},
},
],
},
testEnvironment: "jest-environment-jsdom",
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};
続けて公式に従って設定ファイルを作成します。
import '@testing-library/jest-dom'
作成したファイルはESLintのチェック対象となっており、どちらともエラーが出るので一旦チェック対象外にします。
ignorePatterns: [
"vite.config.ts",
".eslintrc.cjs",
+ "jest.config.cjs",
+ "jest.setup.ts",
],
jest.setup.ts
をtsconfig.json
で読み込みます。
{
"compilerOptions": {
…
},
- "include": ["src"],
+ "include": ["src", "jest.setup.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}
テストケース作成
この後App.tsx
のテストケースを書いていきますが、テスト時に画像ファイルやCSSのインポートに失敗します。モックを準備する必要があるので、以下の記事に従ってモックを作成します。(@Amselさん、有益な情報をありがとうございます)
import { render, screen } from "@testing-library/react";
import App from "../src/App";
test("Vite+ReactのWelcomeページが表示されている", () => {
render(<App />);
screen.debug();
expect(screen.getByText("Vite + React")).toBeInTheDocument();
});
テスト実施
以下のコマンドを実行して、テストを実行します。
yarn jest --config ./jest.config.cjs
テストが成功したので、テスト設定は完了です。
package.jsonの更新
テストのためにコマンドをいちいちたたくのは面倒なので、スクリプトとして定義しておきます。
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"prepare": "husky install",
+ "test": "jest --config ./jest.config.cjs"
+ "test:watch": "yarn test --watch"
},
おわりに
ここまでの内容をGitHubに公開しています。一から構築するのが面倒な方はこちらを使用していただければと思います。(スターをいただけると泣いて喜びます)
今回はテストライブラリにJest
を使いましたが、Vitest
がProductionReadyになれば改めて再構築したいなと考えています。
様々なツールやライブラリがあって、モダンなフロント環境を追うのはなかなか大変ですがやっぱり楽しいですね。他にも便利なものは取り入れていってDX(DeveloperExperience)向上に努めていきたいです。
Discussion