React環境構築(Vite + Typescript + ESLint + Prettier + TailwindCSS + Vitest + RTL + α)

最近`Vite`を使用した`React`の環境構築をする機会があり、その時に作成したメモを簡易的ではありますが共有します。

# バージョン
```package.json
{
  "name": "my-vite-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "test": "vitest",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
    "format": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
    "eslint": "eslint --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
    "eslintfix": "eslint --fix --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.1.2",
    "@testing-library/react": "^14.0.0",
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "@vitejs/plugin-react-swc": "^3.3.2",
    "autoprefixer": "^10.4.15",
    "eslint": "^8.45.0",
    "eslint-config-prettier": "^9.0.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "jsdom": "^22.1.0",
    "postcss": "^8.4.28",
    "prettier": "^3.0.2",
    "prettier-plugin-tailwindcss": "^0.5.3",
    "tailwindcss": "^3.3.3",
    "typescript": "^5.0.2",
    "vite": "^4.4.5",
    "vitest": "^0.34.3"
  }
}
```

# アプリ作成
- `npm create vite@latest my-vite-app --template react-ts`
- "react" を選択
- "Typescript + SWC" を選択  
この時点で`Typescript` `ESLint`の導入は完了

### import自動並び替え
```.vscode/settings.json
{
    "editor.codeActionsOnSave": {
        "source.fixAll": true,
        "source.organizeImports": true
    }
}
```

# Prettier導入
- `npm install -D prettier eslint-config-prettier`
```diff:.eslintrc.cjs
module.exports = {
    root: true,
    env: { browser: true, es2020: true },
    extends: [
        'eslint:recommended',
        'plugin:@typescript-eslint/recommended',
        'plugin:react-hooks/recommended',
+       'prettier'
    ],
    ignorePatterns: ['dist', '.eslintrc.cjs'],
    parser: '@typescript-eslint/parser',
    plugins: ['react-refresh'],
    rules: {
        'react-refresh/only-export-components': ['warn', { allowConstantExport: true }]
    }
}
```

- Prettierの設定(任意)
```.prettierrc
{
    "trailingComma": "all"
 }
```

- VSCodeのファイル保存時に自動でコードを整形する設定
```diff:.vscode/settings.json
{
    "editor.codeActionsOnSave": {
        "source.fixAll": true,
        "source.organizeImports": true
    },
+   "editor.formatOnSave": true,
+   "editor.defaultFormatter": "esbenp.prettier-vscode"
}
```

- scriptの設定(任意)
```diff:package.json
{
    ...
    "scripts": {
        "dev": "vite",
        "build": "tsc && vite build",
        "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
        "preview": "vite preview",
+       "format": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
+       "eslint": "eslint --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
+       "eslintfix": "eslint --fix --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'"
    },
    ...
}
```

# TailwindCSS導入
- `npm install -D tailwindcss postcss autoprefixer`
- `npx tailwindcss init -p`
```diff:tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
-   content: [],
+   content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
    theme: {
        extend: {},
    },
    plugins: [],
}
```

```diff:./src/index.css
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
...
```

- VSCode上にTailwindのインテリセンスを表示させる
```diff:.vscode/settings.json
{
    ...
+   "editor.quickSuggestions": {
+       "strings": true
+   }
}
```

### Tailwindクラス名を自動並び替え
- `npm install -D prettier-plugin-tailwindcss`
```diff:.prettierrc
{
    "printWidth": 120,
    "tabWidth": 2,
    "semi": false,
    "trailingComma": "all",
    "singleQuote": true,
+   "plugins": ["prettier-plugin-tailwindcss"]
}
```

# テスト環境構築(Vitest + React-Testing-Library)
- `npm install -D @testing-library/jest-dom @testing-library/react @testing-library/user-event @vitejs/plugin-react-swc jsdom vitest`
```vite.config.ts
/// <reference types="vitest" />
/// <reference types="vite/client" />

import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
    test: {
        globals: true,
        environment: 'jsdom',
        setupFiles: ['./src/__test__/setup.ts'],
        css: true,
    },
})
```

```diff:ts.config.json
{
    "compilerOptions": {
        ...
+       "types": ["vitest/globals"]
    }
}
```
- package.json の script に`"test": "vitest",`追加
- `src/__test__/setup.ts` 作成
 ```src/\_\_test__/setup.ts
 import '@testing-library/jest-dom'
 ```

- scriptの追加
```diff:package.json
{
    ...
    "scripts": {
        "dev": "vite",
        "build": "tsc && vite build",
        "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
        "preview": "vite preview",
        "format": "prettier --write --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
        "eslint": "eslint --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
        "eslintfix": "eslint --fix --ignore-path .gitignore './**/*.{js,jsx,ts,tsx}'",
+       "test": "vitest"
    },
    ...
}
```

# [おまけ]絶対パスでインポート
```diff:ts.config.json
{
    "compilerOptions": {
        ...
+       "baseUrl": ".",
+       "paths": {
+           "@/*": ["./src/*"]
+       }
    ...
    }
}
```

```diff:vite.config.ts
/// <reference types="vitest" />
/// <reference types="vite/client" />

import react from '@vitejs/plugin-react-swc'
+ import * as path from 'path'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [react()],
+   resolve: {
+       alias: {
+           '@': path.resolve(__dirname, 'src'),
+       },
+   },
    test: {
        globals: true,
        environment: 'jsdom',
        setupFiles: ['./src/__test__/setup.ts'],
        css: true,
    },
})
```

- VSCodeのクイックフィックスでのimportを絶対パスに設定
```diff:.vscode/settings.json
{
    ...
+   "typescript.preferences.importModuleSpecifier": "non-relative"
}
```