Create React App の ESLint のカスタマイズと VSCode での使用 〜Prettier を添えて〜

# 本記事について

Create React App（以下 CRA）で作成されたアプリの ESLint を Visual Studio Code（以下 VSCode）で使えるように設定する機会があった。
また合わせて ESLint のカスタマイズもできるようにした。さらに Prettier も一緒に使えるようにした。

そのときは開発中のアプリの設定をいじる形だったので、1 から CRA でアプリを作成する場合にも再現できるように手順を整理したのが本記事。
結局 1 から作成してみた場合もやったことは変わらなかったので、開発中のアプリの ESLint のカスタマイズも本記事のやりかたと同様にできるはず。

# 環境

- OS: macOS

# アプリ作成

Create React App でアプリを作成する。
以下は、パッケージ管理に Yarn を使い、開発言語に TypeScript を使う場合のコマンド。

```bash
$ yarn create react-app cra_ts --template typescript
```

このとき使われた Create React App のバージョンは v5.0.1 だった。

# ESLint のインストール

CRA は ESLint を含んでいる。そのため自分で ESLint をインストールする必要はない。

# ESLint をカスタマイズするときの注意

CRA を使っていると、CRA を起動（ `yarn start` ）したときの標準出力に ESLint の実行結果が出る。
これは CRA の lint 機能である。

CRA のドキュメントによると、ESLint の設定をカスタマイズしても、CRA の lint 機能はカスタマイズした設定を使ってくれないらしいので注意。

> Note that even if you customise your ESLint config, these changes will only affect the editor integration.

[Setting Up Your Editor | Create React App](https://create-react-app.dev/docs/setting-up-your-editor)

しかし、実際にアプリを起動（ `yarn start` ）してみると、カスタマイズした設定を CRA の lint 機能も使ってくれていた。また、同じドキュメントによるとブラウザの開発者ツールのコンソールにも ESLint の実行結果が出るらしいが、出なかった。ドキュメントが古いのだろうか？
どのみち VSCode で lint するので、ここでは CRA の lint 機能については深追いしないことにする。


# カスタマイズの方針

CRA では最初から ESLint の設定を持っている。
設定は package.json に書かれている。

```json
{
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  }
}
```

この `react-app` と `react-app/jest` に具体的な設定が書かれている。これらのドキュメントとソースは 
[`eslint-config-react-app` の GitHub](https://github.com/facebook/create-react-app/tree/main/packages/eslint-config-react-app) を参照。

さて、この `eslint-config-react-app` の設定を拡張していくのが良いのか、無視しても良いのか。
[CRA のドキュメント](https://create-react-app.dev/docs/setting-up-your-editor/)では以下のように書かれている。

> We highly recommend extending the base config, as removing it could introduce hard-to-find issues.

これに従って `eslint-config-react-app` を拡張していく方針でいくことにする。

# ESLint をカスタマイズするための準備

## `package.json` の `eslintConfig` を `.eslintrc.js` に移す

これは必須ではない。ただ ESLint の設定を専用のファイルで書いたほうが個人的に管理しやすいため、そうする。

`.eslintrc.js` をアプリのルートディレクトリに作成して、以下のように記載する。

```js
/**
 * ESLint の設定です。ドキュメントは以下です。
 * https://eslint.org/docs/latest/use/configure/
 */
module.exports = {
  extends: [
    /**
     * 以下 2 つは Create React App のデフォルトの ESLint の設定です。
     * Create React App ではこれを拡張する形で設定することを推奨しています。
     * そのため以下 2 つは消さずに、config を追加する際はその下に記載してください。
     * 詳細は以下ドキュメントを参照してください。
     * https://create-react-app.dev/docs/setting-up-your-editor
     * https://github.com/facebook/create-react-app/tree/main/packages/eslint-config-react-app
     */
    "react-app",
    "react-app/jest",
  ],
  ignorePatterns: [
    "/build/", // ビルド出力
  ]
};
```

`ignorePatterns` を追加している。 `/build/` はビルド（ `yarn build` ）の出力に対して lint しないように記載している。ちなみに `node_modules` ディレクトリは特に設定しなくても lint 対象から除外される（[ESLint のドキュメント](https://eslint.org/docs/latest/use/configure/ignore)）。

除外ファイルは `.eslintignore` ファイルでも設定できるが 、`.eslintignore` ファイルは ESLint v8.25.0 で廃止されるので、`ignorePatterns` で除外ファイルを指定している。

`package.json` の `eslintConfig` は削除する。

# VSCode で ESLint の自動修正を実行する設定

まず、VSCode 拡張機能 [ESLint（`dbaeumer.vscode-eslint`）](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) をインストールする。

そして `.vscode/settings.json` ファイルを作成し以下を記載する。

```json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}
```

これでファイル保存時に ESLint の自動修正が実行される。
また、エディタ上で ESLint の指摘を見ることができるようになる。

この設定をチームに強要させたくない場合には `.gitignore` で除外しておく。

```diff
+ /.vscode/settings.json
```

# Prettier の導入

ESLint を使いつつ、Prettier で整形する場合は以下のように設定する。

まず、 `prettier` と `eslint-config-prettier` をインストール。

```bash
$ yarn add -D prettier eslint-config-prettier
```

`eslint-config-prettier` （[GitHub](https://github.com/prettier/eslint-config-prettier)）は Prettier 公式の ESLint config。
ESLint の設定のうち、Prettier と競合するものをオフにしてくれるので入れる。

VSCode 拡張機能 [Prettier - Code formatter（`esbenp.prettier-vscode`）](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) もインストール。

そして `.eslintrc.js` を更新。

```diff_javascript
    extends: [
      /**
       * 以下 2 つは Create React App のデフォルトの ESLint の設定です。
       * Create React App ではこれを拡張する形で設定することを推奨しています。
       * そのため以下 2 つは消さずに、config を追加する際はその下に記載してください。
       * 詳細は以下ドキュメントを参照してください。
       * https://create-react-app.dev/docs/setting-up-your-editor
       * https://github.com/facebook/create-react-app/tree/main/packages/eslint-config-react-app
       */
      "react-app",
      "react-app/jest",
+
+     "prettier",
   ],
```

`prettier` は `extends` の一番最後に書く（[Prettier のドキュメント](https://github.com/prettier/eslint-config-prettier)）。

`.vscode/settings.json` も更新。

```diff_json
  {
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
-   }
+   },
+   "editor.defaultFormatter": "esbenp.prettier-vscode",
+   "editor.formatOnSave": true
```

これで、ファイル保存時に Prettier が実行される。

# Docker で開発しているときの注意

ESLint や Prettier の VSCode 拡張機能は、ローカルにインストールしてある eslint や prettier があればそれを使う。
Docker で開発していて、ローカル側に（eslint や prettier を含む）パッケージをインストールしていない場合、拡張機能が内蔵している eslint や prettier が使われてしまう。また場合によっては使えるものが見つからずエラーする。
これを回避する簡単な方法は、ローカルでもパッケージをインストールしておく（ `yarn install` を実行する）こと。
（工夫すれば Docker コンテナ内の eslint や prettier を使う方法があるかもしれない。）

また、Docker Compose を使っていて、例えば `frontend` ディレクトリ配下に React アプリがある場合は `.vscode/settings.json` に以下の設定が必要。

```diff_json
  {
    "editor.codeActionsOnSave": {
      "source.fixAll.eslint": true
    },
    "editor.defaultFormatter": "esbenp.prettier-vscode",
-   "editor.formatOnSave": true
+   "editor.formatOnSave": true,
+   "eslint.workingDirectories": ["./frontend"]
  }
```

# ESLint の config を追加してみる

試しに Airbnb の config を追加してみる。

```bash
$ yarn add -D eslint-config-airbnb eslint-config-airbnb-typescript
```

`.eslintrc.js` を更新。 

```diff_javascript
    extends: [
      /**
       * 以下 2 つは Create React App のデフォルトの ESLint の設定です。
       * Create React App ではこれを拡張する形で設定することを推奨しています。
       * そのため以下 2 つは消さずに、config を追加する際はその下に記載してください。
       * 詳細は以下ドキュメントを参照してください。
       * https://create-react-app.dev/docs/setting-up-your-editor
       * https://github.com/facebook/create-react-app/tree/main/packages/eslint-config-react-app
       */
      "react-app",
      "react-app/jest",
  
+     "airbnb",
+     "airbnb-typescript",
      "prettier",
    ],
+   parserOptions: {
+     project: "./tsconfig.json",
+   },
    ignorePatterns: [
+     "/.eslintrc.js",
      "/build/", // ビルド出力
    ],
```

ここでも `prettier` は extends の一番最後に書くこと（[Prettier のドキュメント](https://github.com/prettier/eslint-config-prettier)）。

`airbnb-typescript` を追加したとき、 `parserOptions` を追加しないと以下のエラーになる。ちなみにこれは VSCode ならコマンドパレット（ `shift+cmd+p` ）で `ESLint: Show Output Channel` を実行すると見られる。

```
Error: Error while loading rule '@typescript-eslint/dot-notation': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.
```

`airbnb-typescript` を追加したとき、 `ignorePatterns` に `/.eslintrc.js` を追加しないと、 `.eslintrc.js` 自体が lint されて以下のようなエラーになる。今回は上記の `.eslintrc.js` のように、 `.eslintrc.js` を lint しないように設定することでエラーを回避した。[typescript-eslint のドキュメント](https://typescript-eslint.io/linting/troubleshooting/#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file)も参照のこと。

```
Parsing error: ESLint was configured to run on `<tsconfigRootDir>/.eslintrc.js` using `parserOptions.project`: /{path_to_app}/tsconfig.json
However, that TSConfig does not include this file.
```

# 軽く動作確認

`src/App.tsx` に以下を記載してみる。

```diff_tsx
  function App() {
+      var re = /foo   bar/
  
    return (
```

そして保存すると以下のように修正される。

```diff_tsx
  function App() {
-      var re = /foo   bar/
+   const re = /foo {3}bar/;

    return (
```

より詳細に言うと以下が行われている。

- Prettier によるフォーマット
  - インデント調整
  - `;` の付加
- ESLint による自動修正
  - `no-regix-spaces` の違反の修正
    - `{3}` になっているところ
  - `no-var` の違反の修正
    - `var` が `const` になっているところ
    - これは `eslint-config-airbnb` によってチェックされるようになった項目（[ソースは多分ここ](https://github.com/airbnb/javascript/blob/5c01a1094986c4dd50a6ee4d9f7617abdfabb58a/packages/eslint-config-airbnb-base/rules/es6.js#L100)）

さらに ESLint によって以下の指摘もしてくれている。

```
're' is assigned a value but never used.
```

# 最終的な設定ファイル

`.eslintrs.js`

```javascript
/**
 * ESLint の設定です。ドキュメントは以下です。
 * https://eslint.org/docs/latest/use/configure/
 */
module.exports = {
  extends: [
    /**
     * 以下 2 つは Create React App のデフォルトの ESLint の設定です。
     * これを拡張する形で設定するため、config を追加する際はこの下に記載してください。
     * 詳細は以下ドキュメントを参照してください。
     * https://create-react-app.dev/docs/setting-up-your-editor
     * https://github.com/facebook/create-react-app/tree/main/packages/eslint-config-react-app
     */
    "react-app",
    "react-app/jest",

    "airbnb",
    "airbnb-typescript",
    "prettier",
  ],
  parserOptions: {
    project: "./tsconfig.json",
  },
  ignorePatterns: [
    "/.eslintrc.js",
    "/build/", // ビルド出力
  ],
};
```

`.vscode/settings.json`

```json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true
}
```

# 参考サイト

上記では最終的には公式のドキュメントを参照先にしていますが、調べる過程で以下の記事が役に立ちました。ありがとうございます。

- [ESLint 設定を作成する技術](https://zenn.dev/januswel/articles/402774d76424e71ac906)
- [TypeScrip + ESLint + Prettier + Jest のプロジェクト設定にハマる - かもメモ](https://chaika.hatenablog.com/entry/2021/07/10/083000)
- [[VSCode] TypeScriptにESLintを入れると Parsing error: “parserOptions.project” has been set for @typescript-eslint/parser. が表示される │ wonwon eater](https://wonwon-eater.com/ts-eslint-import-error/)