2022年秋版　最新React/Typescript開発環境の作成

:::note info
vite4がリリースされましたが、プロジェクト作成時に「vite@latest」を指定しているため問題ありません。

vite4は、以下を参照してください。
https://vitejs.dev/blog/announcing-vite4.html
:::



:::note info
コンポーネントにアロー関数を使えるよう.eslintrc.cjsへルールを追加しました。
:::

## はじめに
Reactの開発環境は、CRA(Create React APP)がよく使われますが、CRAは沢山のモジュールの整合性の確認などのため、アップデートがリリースされるまでの間隔が結構あります。

また、使われているモジュールをアップデートするとエラーに悩まされたりします。

ゼロからReactの開発環境を作成していたのですが、いろいろとトライを繰り返すうちに以下の方法に落ち着きました。

比較的短時間で環境が作成できますし、全て最新のモジュールが使えます。


## Vite

https://vitejs.dev/

nodejsを用いたJavaSriptプロジェクトは、、Webpackなどでプロジェクト内のJavaScriptモジュールをひとつのファイルにまとめるバンドルと呼ばれる作業が必要でした。

これは、ブラウザがESモジュールに対応していないことや、ひとつのHTMLファイルから沢山のjsファイルをダウンロードさせると、その数だけアクセスが必要となり実行までに時間がかかることを避けるための作業でした。

つまり、効率重視。

しかし、プロジェクトの規模が大きくなるとバンドル時間が長くなり、ホット・リロードを利用しても描画までの時間がある程度必要で開発の効率は落ちていきます。

[vite](https://vitejs.dev/)は、予め「node_modules/.vite」にいくつかのバンドルファイルを作成しておき、IEがいなくなったモダンブラウザのESモジュールに対応を利用し、複数のファイルを並行ダウンロードさせることで速度を出しています。

プロジェクトを開始するのも簡単です。ターミナルに「npm create vite@latest」を入力しエンターキーを押します。

質問されますので、答えていきます。

```console
npm create vite@latest

// プロジェクト名を入力しエンターキー
? Project name: › vite-project

// 使用するフレームワークを矢印キーで選択しエンターキー
? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
Vue
> React
Preact
Lit
Svelte

// JavaScript/TypeScriptの選択
? Select a variant: › - Use arrow-keys. Return to submit.
JavaScript
❯   TypeScript

// 以上でプロジェクト完了
Done. Now run:
```

プロジェクトが出来上がりましたので、動作確認を行います。以下のコマンドをそれぞれターミナルに入力しエンターキーを押します。

```console
// プロジェクトフォルダへ移動
cd vite-project
// 必要なパッケージをインストール
npm install
// デバッグ開始
npm run dev

VITE v3.1.6  ready in 439 ms

➜  Local:   [http://127.0.0.1:5173/](http://127.0.0.1:5173/)
➜  Network: use --host to expose

```

ブラウザを開き、「<http://127.0.0.1:5173>」へアクセス。これだけでReactアプリが出来ます。

![vite_run.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4849/475e4b3b-acae-97bb-1f03-0a0498078902.png)

## ESlint

設定ルールを元にコード入力の間違いを指摘・修正してくれる「Eslint」をインストールします。

### ESlintのインストール
ターミナルに以下を入力しエンターキーを押します。
```console
 npm install -D eslint
```

これで、ESlintのインストールが完了です。

### ESlint設定ファイルの作成
ターミナルに「npx eslint --init」を入力しエンターキーを押します。

質問されますので、それぞれ答えていきます。

```console
 npx eslint --init

// ESlintをどのように使いますか？
You can also run this command directly using 'npm init @eslint/config'.
? How would you like to use ESLint? …
To check syntax only
> To check syntax and find problems
To check syntax, find problems, and enforce code style

// どのタイプのモジュールを使いますか？
? What type of modules does your project use? …
> JavaScript modules (import/export)
CommonJS (require/exports)
None of these

// どのフレームワークを使いますか？
? Which framework does your project use? …
> React
Vue.js
None of these

// プロジェクトでTypeScriptを使いますか？
Does your project use TypeScript? › No / Yes

// どの環境で動作させますか？
? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
  Node

// どのフォーマットで設定ファイルを保存しますか？
? What format do you want your config file to be in? …
JSON
> JavaScript
YAML

// 以下のパッケージをインストールしますか？
eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
? Would you like to install them now? › No / >Yes

// パッケージマネージャーを選択してください。
? Which package manager do you want to use? …
> npm
yarn
pnpm
```

これで、依存関係をもつパッケージがインストールｓれ、プロジェクトフォルダに「.eslint.cjs」ファイルが作成されます。

### 現時点でのルールの確認
現在の設定ファイルで、どのようなルールが適応されているのかを確認します。

```console
npx eslint --print-config .eslintrc.cjs > 出力ファイル名.txt
```

何かプラグインなどを追加した場合、「extends」に加えた後に再度出力しdiffを見れば、どんなルールが追加されたかを確認できます。


### airbnbの設定ファイルをインストール
ESlintは、ルールに基づいてコードの間違いやコーディングルールからハズれた部分を指摘してくれます。そのルールは、偉い先人達が残したモノを使うと良いと思っています。

まずは、有名は[airbnb](https://airbnb.io/javascript/react/)です。
![airbnb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4849/b9168c13-48f5-1ecd-95b5-19ecaaed1962.png)

インストールするものは、こちら。

https://www.npmjs.com/package/eslint-config-airbnb

ターミナルに以下を入力しエンターキーを押します。

```console
npx install-peerdeps --dev eslint-config-airbnb
```

プロジェクトはTypeScriptですので、airbnbのTypeScript用もインストールします。

https://www.npmjs.com/package/eslint-config-airbnb-typescript

```console
npm install -D eslint-config-airbnb-typescript
```

設定ファイル(.eslintrc.cjs)を変更します。以下が修正後です。設定ファイル内のextendsにあるものは、ルールのひとかたまりです。同じ設定があると後から読み込んだもので上書きされます。

extendsにあるルールを読み込んだ後、この設定ファイルにある「rules:」が読み込まれます。

parserOptionsに、「project: './tsconfig.json'」を追加します。

```javascript
extends: [
  'airbnb',
  'airbnb-typescript',
  'airbnb/hooks'
  'plugin:react/recommended',
  'plugin:@typescript-eslint/recommended'
],
parserOptions: {
  ecmaVersion: 'latest',
  sourceType: 'module',
  project: './tsconfig.json'
}

```

最後に「./tsconfig.json」の「include:」にESlintの設定ファイルを追加します。

```json
 "include": ["src", ".eslintrc.cjs"],
```

 
## Prettier
Prettierは、フォーマット(整形)ツールです。

https://prettier.io/

 * タブをスペースに変換したり
 * タブ幅を決めたり
 * ダブルクォーテーション・シングルクォーテーションを統一したり
 * 文末セミコロンのあり・なし指定

など、保存する際にフォーマットできます。一部ESlintと機能がダブるため、プラグインで調整します。

以下をターミナルに入力しエンターキーを押します。

```console
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
```

### Prettierの設定ファイル

Prettierの設定ファイルは、

 * "prettier" キーをpackage.json内に記述する。
 * ファイル名「prettierrc」でJSONまたはYAML形式で。
 * .prettierrc.json .prettierrc.yml .prettierrc.yaml .prettierrc.json5のどれか。
 * module.exportを使用した、.prettierrc.js .prettierrc.cjs prettier.config.js prettier.config.cjsのどれか。
 * .prettierrc.toml

のどれかを使用します。

私の設定は、

.prettierrcにJSON形式です。
```json
{
  "trailingComma": "es5",
  "printWidth": 100,
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true
}
```

設定のオプションは、以下のページにあります。

https://prettier.io/docs/en/options.html

また、ESlintとの調整を以下のページを参照して行います。

https://github.com/prettier/eslint-plugin-prettier#recommended-configuration

「.eslintrc.cjs」の

 * extendsに「plugin:prettier/recommended」を追加
 * pluginsに「prettier」を追加します。


## Vitest
vitesは、viteプロジェクトの一貫でのテスト環境です。Jestに比べ相当早いと感じています。

https://vitest.dev/

インストールは、ターミナルに以下のコマンドを入力しエンターキーを押します。

```console
npm install -D vitest
```

設定ファイルは、viteの設定ファイルに記述することもできますし、別にすることもできます。

今回は、vitestのページにあるサンプルプロジェクトから設定ファイルをコピーします。

![vitest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4849/17652828-f62b-5033-25d7-7e4237be217e.png)

リンク先のプロジェクトの設定ファイルは、こちらです。

https://github.com/vitest-dev/vitest/blob/main/examples/react-testing-lib-msw/vite.config.ts

これを現在の「vite.config.ts」に上書きします。

コマンドでテストが開始できるように、「package.json」の「scripts」へ追加します。

```json
"scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest"
  },
```

## tsconfig.json
ここまでやると、eslintが指摘してきます。

 * .eslint.cjs
 * vite.config.ts

を、tsconfig.jsonの「include」へ追加します。

### testing-library
プロジェクトは、Reactです。Reactのコンポーネントをテストするためには、コンポーネントを描画する必要があります。

ブラウザを使用せずコンポーネントでDOMを作成するために、「testing-library/react」を使用します。また、追加で

 * 描画のための「jsdom」
 * クリックなどのユーザ操作を行う「testing-library/user-event」
 * アサーションの拡張「testing-library/jest-dom」

も合わせてインストールします。

ターミナルに以下を入力しエンターキーを押します。

```console
npm install --D @testing-library/react jsdom @testing-library/user-event @testing-library/jest-dom 
```

### vitestとjest-domを上手く使うために
jest-domで拡張したアサーションを、テストファイル作成の度にimportするのがメンドウです。

https://markus.oberlehner.net/blog/using-testing-library-jest-dom-with-vitest/

ここを参考に、「src/setupTest.ts」を作成し、以下を書き込みます。

```ts
import matchers from '@testing-library/jest-dom/matchers';
import { expect } from 'vitest';

expect.extend(matchers);
```

> 注意
>  
> vite.config.tsにコピーした
> setupFiles: ['./src/setup.ts']
>    ↓
> setupFiles: ['./src/setupTest.ts']
> 
> に変更するのを忘れないでください。

以上でテスト環境の作成も完了です。

### 更に便利に

「React Testing Library のよくある間違い」

https://qiita.com/kobanyan/items/126512ec3e8d76c538b3

を参考に、

 * [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library)
 * [eslint-plugin-jest-dom](https://github.com/testing-library/eslint-plugin-jest-dom)

をインストールします。

ターミナルに以下を入力しエンターキーを押します。

```console
npm install -D eslint-plugin-testing-library eslint-plugin-jest-dom
```

#### .eslintrc.cjsの変更

上記2つのプラグインのページを参考に修正します。

最終の「.eslintrc.cjs」です。

「import React from 'react'」は、17から不要になりましたが、eslintに怒られるので、ルールを追加しています。

> 追記 2022/10/13
>
> アロー関数が使えないと言われましたので
>
> 
> 'react/function-component-definition':[2, { "namedComponents": "arrow-function" }]
>　をrulesへ追加しました。
>
> 以下のように自動で修正されます。
> const Component: React.FC<Props> = (props) => {
>   return <SomeComponent />;
> };
>
> 

```javascript
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'airbnb',
    'airbnb-typescript',
    'airbnb/hooks',
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'plugin:testing-library/react',
    'plugin:jest-dom/recommended',
  ],
  overrides: [],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    project: './tsconfig.json',
  },
  plugins: ['react', '@typescript-eslint', 'prettier', 'testing-library', 'jest-dom'],
  rules: {
   'react/react-in-jsx-scope': 'off',
　　'react/function-component-definition':[2, {"namedComponents": "arrow-function" }],
 },
};
```

ついでに、「tsconfig.json」です。

```ts
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src", ".eslintrc.cjs", "vite.config.ts"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

## テストしてみる。

作成した環境を実行すると、下図のように表示されます。ボタンに「count is 0」とあり、ボタンをクリックすると数字が上がっていきます。


![vite_run.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4849/475e4b3b-acae-97bb-1f03-0a0498078902.png)

このページのテストを書きます。

 1. ページを表示したときに、「count is 0」と表示されたボタンはあるか？
 2. ユーザーがボタンをクリックする。
 3. カウントアップされボタンの表示が「count is 1」となるか？

を試します。

　「src/App.test.tsx」ファイルを作成します。

```ts
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';

test('should first', async () => {
  // Appコンポーネントを描画
  render(<App />);

　// テキスト「count is」を持っているボタンを検索
  let button = screen.getByRole('button', { name: /count is/i });
　// 検索したボタンのテキストが「count is 0」を確認
  expect(button).toHaveTextContent('count is 0');

　// イベントを発火するユーザをセット
  const user = userEvent.setup();
  // 上記の検索したボタンをクリック
  await user.click(button);
　
　// 再描画されたボタンを取得 取得には正規表現も使えます。
  button = await screen.findByRole('button', { name: /count is \d/ });
　// 取得したボタンのテキストを確認
  expect(button).toHaveTextContent('count is 1');
});

```

テストパス。

![test_results.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4849/08150404-e78b-4a93-fd8e-47eac4822448.png)




以上でReact/TypeScriptの開発環境を作成できます。