【ESLint】React / TypeScript 環境で Flat Config を利用する

# はじめに
この記事では、React / TypeScript 環境で ESLint の Flat Config を利用する方法について記載します。

https://eslint.org/

元々 ESLint の設定ファイルは `.eslintrc.*` でしたが、v9.0.0（2024年4月リリース）からは Flat Config (`eslint.config.js`) がデフォルトになっています。さらに v10.0.0（2024年末から2025年頭にリリース予定）では、`.eslintrc.*` が削除される予定です。[^1]

[^1]:https://eslint.org/blog/2023/10/flat-config-rollout-plans/


そのため、Flat Config (`eslint.config.js`) を利用した設定方法を調べていきます。

# 開発環境
開発環境は以下の通りです。

- Windows 11
- VSCode
- TypeScript 5.2.2
- React 18.3.1
- Vite 5.3.1
- Node.js 20.15.0
- npm 10.8.1
- ESLint 8.57.0
- @eslint/js 9.6.0
- eslint-plugin-react 7.34.3
- globals 15.8.0
- typescript-eslint 7.15.0

:::note
Node.js は以下いずれかのバージョンにする必要があります。

>  Node.js (^18.18.0, ^20.9.0, or >=21.1.0)[^2]
[^2]:https://eslint.org/docs/latest/use/getting-started#prerequisites
:::

# 事前準備
Vite の `react-ts` テンプレートで React / TypeScript のプロジェクトを作成しておきます。

```powershell
npm create vite@latest my-react-ts-app -- --template react-ts
```

詳細はこちら。

https://qiita.com/Yasushi-Mo/items/a85bb7a90ce96deae879

# ESLint のインストールと自動設定
以下のコマンドで、ESLint 及び必要なパッケージのインストール、設定ファイル `eslint.config.js` の作成ができます。

```powershell
npm init @eslint/config@latest
```

コマンド実行後、いくつか質問されるので、以下の選択をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/b5cc77ba-cacc-2479-3e5b-1c3540ac865c.png)

:::note
React のプラグインは現時点（2024年7月）で ESLint v9 をサポートしていないので、今回は v8 をインストールします。
:::

`package.json` には、以下のパッケージが追加されます。

```package.json
{
  ...
  "devDependencies": {
    "@eslint/js": "^9.6.0",
    "eslint": "^8.57.0",
    "eslint-plugin-react": "^7.34.3",
    "globals": "^15.8.0",
    "typescript-eslint": "^7.15.0",
    ...
  }
}
```

- [@eslint/js](https://www.npmjs.com/package/@eslint/js) : ESLint の `recommended` と `all` ルール
- [ESLint](https://www.npmjs.com/package/eslint) : ESLint そのもの
- [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) : React 向けルール
- [globals](https://www.npmjs.com/package/globals)
    - 異なる JavaScript 環境の識別子
    - ESLint v8 以前で必要
- [typescript-eslint](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) : TypeScript 向けのルール


加えて、設定ファイル `eslint.config.js` が作成されます。

```eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReactConfig from "eslint-plugin-react/configs/recommended.js";

export default [
  { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
  { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
  { languageOptions: { globals: globals.browser } },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  pluginReactConfig,
];

```

ESLint を実行します。

```powershell
npx eslint src
```

ワーニングとエラーが表示されます。

```powershell
Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .

C:\Users\ymori\Documents\GitHub\flat-config\src\App.tsx
  10:5   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  11:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  12:9   error  Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank
  12:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  13:11  error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  15:9   error  Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank
  15:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  16:11  error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  19:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  20:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  21:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  24:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  25:16  error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  28:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope

✖ 14 problems (14 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
```

# ワーニングの修正
まずはワーニングを修正します。

```powershell
Warning: React version not specified in eslint-plugin-react settings.
See https://github.com/jsx-eslint/eslint-plugin-react#configuration .
```

ワーニングメッセージ通り https://github.com/jsx-eslint/eslint-plugin-react を確認すると、`version` の値を `"detect"` にすることで、自動的に利用中の React バージョンを検知してくれるという記載があります。

> // React version. "detect" automatically picks the version you have installed.
> // You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
> // It will default to "latest" and warn if missing, and to "detect" in the future

また、`version"` は `settings` > `react` 内に記載があります。
そのため、`settings` > `react` > `version: "detect"` という構造で、`eslint.config.js` に設定を追加します。

```eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReactConfig from "eslint-plugin-react/configs/recommended.js";

export default [
  {
    files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
    // 追加
    settings: {
      react: {
        version: "detect",
      },
    },
    plugins: {
      pluginReactConfig,
    },
  },
  { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
  { languageOptions: { globals: globals.browser } },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  pluginReactConfig,
];

```

ESLint を実行します。

```powershell
npx eslint src
```

ワーニングが解消されていることを確認できます。

```powershell
C:\Users\ymori\Documents\GitHub\flat-config\src\App.tsx
  10:5   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  11:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  12:9   error  Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank
  12:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  13:11  error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  15:9   error  Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank
  15:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  16:11  error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  19:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  20:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  21:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  24:9   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  25:16  error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope
  28:7   error  'React' must be in scope when using JSX                                                                                                      
                                          react/react-in-jsx-scope

✖ 14 problems (14 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
```


# エラーの修正
エラーは以下の2種類表示されています。

```powershell
'React' must be in scope when using JSX react/react-in-jsx-scope
```

```powershell
Using target="_blank" without rel="noreferrer" (which implies rel="noopener") isa security risk in older browsers: 
see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank
```

## `react-in-jsx-scope`
ひとつ目のエラーについては、React 17 以上では、無効化すべきルールです。今回は React 18 を利用しているので、無効化します。

> If you are using the new JSX transform from React 17, you should disable this rule by extending react/jsx-runtime in your eslint config (add "plugin:react/jsx-runtime" to "extends").

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

ただ、ドキュメントには、`"extends"` に `"plugin:react/jsx-runtime"` を追加するよう記載がありますが、Flat Config には `"extends"` が存在しません。`eslint-plugin-react` には、[Shareable configs](https://github.com/jsx-eslint/eslint-plugin-react?tab=readme-ov-file#shareable-configs-1) という共有設定ファイルが3つあり、その中に `eslint-plugin-react/configs/jsx-runtime` があります。そのため、`eslint-plugin-react/configs/jsx-runtime` をインポートして、`eslint.config.js` に追加します。


```eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReactConfig from "eslint-plugin-react/configs/recommended.js";
import pluginReactJSXRuntime from "eslint-plugin-react/configs/jsx-runtime.js"; // 追加

export default [
  {
    files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
    settings: {
      react: {
        version: "detect",
      },
    },
    plugins: {
      pluginReactConfig,
    },
  },
  { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
  { languageOptions: { globals: globals.browser } },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  pluginReactConfig,
  pluginReactJSXRuntime, // 追加
];

```

ESLint を実行します。

```powershell
npx eslint src
```

`'React' must be in scope when using JSX react/react-in-jsx-scope` エラーが解消されていることを確認できます。

```powershell
C:\Users\ymori\Documents\GitHub\flat-config\src\App.tsx
  12:9  error  Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank
  15:9  error  Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations  react/jsx-no-target-blank

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
```

## `react/jsx-no-target-blank`
もう一つのエラーは、セキュリティ上よろしくない部分なので、コードを修正します。ドキュメントの記載通り `target='_blank'` を使っている `a` タグに `rel='noreferrer'` を追加します。

> When creating a JSX element that has an `a` tag, it is often desired to have the link open in a new tab using the `target='_blank'` attribute. Using this attribute unaccompanied by `rel='noreferrer'`, however, is a severe security vulnerability (see [noreferrer docs](https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer) and [noopener docs](https://html.spec.whatwg.org/multipage/links.html#link-type-noopener) for more details) This rules requires that you accompany `target='_blank'` attributes with `rel='noreferrer'`.

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

ESLint を実行すると、エラーが全て解消されていることを確認できます。

```powershell
npx eslint src
```

# 所感
- `.eslintrc.*` との差異が思ったより大きい
- Flat Config に追いついていない ESLint プラグインもけっこうある（プラグイン自体追いついていない、ドキュメントが追いついていないケースがある）
- `react-in-jsx-scope` デフォルトで無効にしておいて欲しい

# 参考
- [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react)
- [Flat config rollout plans](
https://eslint.org/blog/2023/10/flat-config-rollout-plans/)
- [Getting Started with ESLint](https://eslint.org/docs/latest/use/getting-started)
- [Getting Started | Vite](https://vitejs.dev/guide/)