React × TypeScript × Vite× ESLint × Prettier × Jest  × GitHub Actionsな環境を構築した

## はじめに
[Clean Architecture 　達人に学ぶソフトウェアの構造と設計](https://www.kadokawa.co.jp/product/301806000678/)を読んだので、これを参考にテスタブルなReactプロジェクトを作ろうと思いました。
ディレクトリ構成について考えがまとまり、いざ作ろう！となった段階で、開発環境の構築で苦労してしまいました。
備忘録的な感じで、まとめておきます。

## 目次
- 環境構築の手順
  - React, TypeScript, Viteでのアプリケーション作成
  - ESLint, Prettierのセットアップ
  - Jestのセットアップ
  - GitHub Actionsのセットアップ


## 環境構築の手順
### React, TypeScript, Viteでのアプリケーション作成
以下のコマンドを実行するだけで、簡単にプロジェクトが作成できます。

```sh
$ yarn create vite my-react-ts-app --template react-ts
```

**参考**

https://vitejs.dev/guide/#scaffolding-your-first-vite-project

### ESLint, Prettierのセットアップ
次に、ESLintとPrettierのセットアップをしていきます。

### ESLint
まずはESLintのセットアップからです。最初に以下のコマンドを実行します。

```sh
$ yarn add eslint --dev
$ yarn create @eslint/config
```

すると、質問がいくつか出てくるので、以下のように答えます。

```sh
✔ How would you like to use ESLint? · problems
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ What format do you want your config file to be in? · JavaScript
The config that you've selected requires the following dependencies:

eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
✔ Would you like to install them now? · No
```

すると以下のESLint用設定ファイルが作成されます。

```.eslintrc.js
module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "react",
        "@typescript-eslint"
    ],
    "rules": {
    }
}
```

nodeの環境でも実行するので以下の変更を加えてください。

```sh
   env: {
     browser: true,
     es2021: true,
+    node: true
   },
```

パッケージマネージャに `yarn` を使う想定で書いています。以下のコマンドを実行してください。（実は質問の中で `npm` を使ってinstallしますか？という質問が存在します。もし `npm` を使っている場合は、そこでyesを選択すると、以下のコマンドは実行しなくても大丈夫です。 ）

```sh
$ yarn add --save-dev eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest
```

これで一通りのESLintの設定は終わりです。

今回はここで、ReactやReact hooksに対するESLint設定も加えておきましょう。

```sh
$ yarn add --dev eslint-plugin-react-hooks
```

`.eslintrc.js` に以下の変更を加えてください。

```sh
-  plugins: ['react', '@typescript-eslint'],
+  plugins: ['react', 'react-hooks', '@typescript-eslint'],
   rules: {
+    'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
+    'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
+    'react/jsx-key': [
+     'error',
+     {checkFragmentShorthand: true, warnOnDuplicates: true},
+   ],
```

この設定を入れることで、Reactでのアプリ作成中に気にしなければいけないルールをESLintが検知してくれるようになります。
ルールに関しては、ドキュメントを添付するので見てみてください！

参考↓

https://eslint.org/docs/user-guide/getting-started

https://ja.reactjs.org/docs/hooks-rules.html

https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-key.md

### Prettier
次にPrettierの設定をしていきます。

```sh
$ yarn add --dev --exact prettier
$ echo {}> .prettierrc.json
$ touch .prettierignore
```

Prettierによってコード整形しなくて良いファイルは `.prettierignore` に記述しておきましょう。

```.prettierignore
# Ignore artifacts:
build
coverage

# Ignore all HTML files:
*.html
```

また、ESLintとPrettierのルール同士が競合してお互いの動作を邪魔することがあります。その競合を抑えるために、 `eslint-config-prettier` をinstallして、 `.eslintrc.js` に変更を加えます。

```sh
$ yarn add --dev eslint-config-prettier
```

```sh
     'plugin:react/recommended',
     'plugin:@typescript-eslint/recommended',
     'plugin:promise/recommended',
+    'prettier',
   ],
```

ではコードの整形を行ってみましょう。

```sh
$ yarn prettier --write .
```

コードが全てのファイルで一定のルールに従って、統一できたかと思います。個人開発用のプロジェクトだったとしても、統一しておくと読みやすいため、Prettierの設定を入れることをお勧めします。

**参考**

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

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

### おまけ
`husky` , `lint-staged` による自動ESLintチェックの設定もついでにご紹介します。以下のコマンドを実行してください。

```sh
$ npx mrm@2 lint-staged
```

そして、`package.json` に変更を加えます。

```sh
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
+   "lint": "eslint .",
~~
     "vite": "^2.9.5"
+  },
+  "lint-staged": {
+    "*.{js,ts,tsx}": "yarn lint"
   }
 }
```

これで、commit時に勝手にESLintによる静的チェックが入るようになります。もしルールに沿っていなければ、勝手にチェックしてくれるので、知らないうちにバグが混入していた、ということを防げる可能性が上がりました。

**参考**

https://github.com/okonet/lint-staged

### Jestのセットアップ
次はJestのセットアップです。後少しです。頑張りましょう！
まずは以下のコマンドを実行して、必要なパッケージをインストールしましょう。

```sh
$ yarn add --dev jest @types/jest ts-jest ts-node
```

※ 注意
jest, ts-jestのメジャーバージョンを揃える必要があります。筆者が試した時は、28がまだリリースされて間もない、ということもあり、27で揃えました。

次に、設定を行います。以下のコマンドを実行してください。

```sh
$ jest --init
```

すると以下のような質問が表示されるので、答えてください。

```sh
✔ Would you like to use Jest when running "test" script in "package.json"? … yes
✔ Would you like to use Typescript for the configuration file? … yes
✔ Choose the test environment that will be used for testing › node
✔ Do you want Jest to add coverage reports? … no
✔ Which provider should be used to instrument code for coverage? › v8
✔ Automatically clear mock calls, instances, contexts and results before every test? … yes
```

すると、 `jest.config.ts` が出来上がります。少し設定を書き換えます。

```jest.config.ts

import {InitialOptions} from '@jest/types/build/Config';

const config: InitialOptions = {
  clearMocks: true,
  // An object that configures minimum threshold enforcement for coverage results
  coverageThreshold: {
    global: {
      statements: 100,
    },
  },
  // A list of paths to directories that Jest should use to search for files in
  roots: ['<rootDir>/src'],

  // The glob patterns Jest uses to detect test files
  testMatch: [
    '**/__tests__/**/*.+(ts|tsx|js)',
    '**/?(*.)+(spec|test).+(ts|tsx|js)',
  ],

  // A map from regular expressions to paths to transformers
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
};

export default config;

```

まずデフォルトで作成される `jest.config.ts` は型が指定されていないため、設定ファイルに変なプロパティが紛れ込む可能性があります。なので、 `InitialOptions` で型を指定してあげましょう。
また、今回は `C0 = 100%` を基準とするため、 `coverageThreshold` を設定しています。
それ以外は、基本的に TypeScript Deep Dive を参考にします。

設定はこれで完了です。
最後に、簡単なテストの実行してみましょう。

```src/foo.ts
export const sum
  = (...a: number[]) =>
    a.reduce((acc, val) => acc + val, 0);
```

```src/foo.test.ts
import { sum } from './foo';

test('basic', () => {
  expect(sum()).toBe(0);
});

test('basic again', () => {
  expect(sum(1, 2)).toBe(3);
});
```

```sh
$ yarn test
```

これでテストが実行され、完了します。

※ 注意
`jest --init` のコマンド実行時に `package.json` に自動でscriptに `"test": "jest"` が追加されているはずです。ただし、追加されていない場合は、追加してから上記のコマンドを叩いてみてください。

次に、Reactのテストを行いたい場合の環境を作っていきます。必要なモジュールをインストールしていきます。

```sh
$ yarn add --dev @testing-library/react @testing-library/jest-dom
```

実際のテストは書きません（別記事で書きたいと思います）が、テストを行う際は以下のように作ります。

```foo.test.tsx
/**
 * @jest-environment jsdom
 */

import React from 'react';
import {render, screen} from '@testing-library/react';
import '@testing-library/jest-dom';

describe('テストの内容', () => {
~~~
});
```

順に説明していきます。

```tsx
/**
 * @jest-environment jsdom
 */
```

テストに使用する環境をブラウザ環境にする、という宣言です。デフォルトではNode.js環境で行うため、Reactのコンポーネントやhooksのテストを行う場合には、テストの最上部にこちらの宣言が必要になります。

```tsx
import React from 'react';
import {render, screen} from '@testing-library/react';
```
Reactのテストに必要なモジュールをimportします。 `render` や `screen` などの `@testing-library/react` からimportできるものは必要に応じて、importしてください。

```tsx
import '@testing-library/jest-dom';
```

`toHaveTextContent` などマッチャーの拡張を行いたい場合は、上記が必要になります。 `@testing-library/jest-dom` をimportすることによって使用できるようになるマッチャーは、

- toBeDisabled
- toBeInTheDocument
- toHaveTextContent 


などです。詳しくはドキュメントでご確認いただければと思います。

**参考**

https://typescript-jp.gitbook.io/deep-dive/intro-1/jest

https://jestjs.io/ja/docs/getting-started

https://testing-library.com/docs/react-testing-library/intro/

https://github.com/testing-library/jest-dom

### GitHub Actionsのセットアップ
ようやく最後の段階にきました！GitHub Actionsのセットアップを行います。
まず、CIで実行したいコマンドを定義しましょう。

```sh
     "dev": "vite",
     "build": "tsc && vite build",
     "preview": "vite preview",
+    "lint": "eslint .",
+    "check": "prettier --check .",
```

次に設定ファイルを作成します。

```sh
$ mkdir -p .github/workflows/
$ touch .github/workflows/on-pull-request.yml
```

設定を記述していきます。

```.github/workflows/on-pull-request.yml
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: On pull request

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x, 14.x, 16.x]
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - uses: actions/cache@v3
        id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-
      - name: Install Dependencies
        run: yarn install --prefer-offline
      - name: Run eslint
        run: yarn lint
      - name: Run prettier
        run: yarn run check
      - name: Run jest
        uses: ArtiomTr/jest-coverage-report-action@v2
        with:
          package-manager: yarn
          skip-step: install
          test-script: yarn test
```

CIでESLintやPrettierによるチェックを行います。
また、テストを実行していますが、実行するだけではなく、coverageも同時にPRへ出力されるようにしたいと思います。
今回は `ArtiomTr/jest-coverage-report-action@v2` を使用して、上記を実現します。依存関係のインストールから、テストの実行までをこちらで行ってくれるのですが、依存関係はすでに前段階でインストール済みなので、 `skip-step: install` というオプションを使います。他にもいくつも設定ができますので、ドキュメントを参照してみて下さいね。以下にURLを添付しておきます。

**参考**

https://github.com/actions/starter-workflows/blob/main/ci/node.js.yml

https://github.com/actions/cache/blob/main/examples.md#node---yarn

https://github.com/ArtiomTr/jest-coverage-report-action

## まとめ
普段はすでに作成されたプロジェクトを使って開発しているため、こういった環境構築は少し慣れない部分もあり、てこずりました。
本来の目的であったテスタブルなReactプロジェクト作成及びテストコードの指針作成に向けて、今回作成したリポジトリで引き続き開発を続けていきます。
次は、テスタブルなReactプロジェクト及びテストコードについて記事を書けたらと思います。