ESLint Flat Config に eslintrc から移行せねばならない

# はじめに

フロントエンド界隈では JavaScript 静的コード解析ツールである ESLint の導入が必須とも言えますが、v10 バージョンアップが控えている今、新しい Config である Flat Config 移行が避けられなくなっています。その理由と方法についてまとめてみました。

https://eslint.org/

# Flat Config の導入が必要になるワケ

ESLint v9 系で Flat Config がデフォルトになり、v10 系(2024 年末か 25 年初頭予定)では従来の Config ファイルである eslintrc が完全削除予定であると発表されました。[^1]

私は「v9 系が発表されたのが今年 2024 年 4 月なんだからまだ移行を焦らなくてもいいや！」と考えていた矢先、なんと **ESLint v8 系が 2024/10/05 に EOL(End Of Life;サポート終了) を迎えている** ではありませんか。🔥[^2] このままでは v8 系でセキュリティエラーを検知してもパッケージ更新してくれないので、諦めて v9 にあげる必要があります。

# Flat Config 導入方法 と 設定 を知る

Flat Config の導入は手作業で実施しても良いですが、以下コマンドで基本となる eslint.config.js のベース作成、自動変換を行うことができます。

```
npx @eslint/migrate-config .eslintrc[.js/json/yml]
```

ただ動作保証はされていないため、各設定確認が必要です。[^3]

:::note info
本記事は公式ドキュメント元に要点をまとめたものです。(執筆時点; 2024/12)
詳細は ↓ をご参照ください。
:::

https://eslint.org/docs/latest/use/configure/migration-guide

https://eslint.org/docs/latest/use/configure/configuration-files

# Flat Config と 従来の eslintrc の共通点 / 違い

Config 移行にあたり押さえておきたい 共通点 と 違い について触れていきます。

## 共通点

Config に直接関わる共通点は以下の通りです。

- rules 設定の構文記法
- processor 設定の構文記法
- グローバル変数を同様に定義できる（ただし、プロパティは別）

※他にも CLI コマンドは基本的に同じですが、ここでは割愛。
詳細気になる方は[こちら](https://eslint.org/docs/latest/use/configure/migration-guide#cli-flag-changes)をご参照のこと。

## 違い

※Flat Config/eslintrc 各設定例を交えて説明するので二窓推奨。

### 1. Lint 設定の適用範囲、プラグイン設定

Flat Config では、[Configuration Objects](https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects) と呼ばれる設定情報を要素とする配列で表現し、Lint 対象は `files` プロパティで定義できます。なお `overrides` プロパティは廃止され、代わりにすべての設定がその性質をもつことで、**配列要素順**に設定が override して適用されます。

:::note info
`files`プロパティが未指定の場合、`"**/*.{js,mjs,cjs}"`が Lint 対象です。
:::

またプラグインは、JavaScript のオブジェクトとして扱うことで設定します。前述の通り、`rules` の記法は同じです。

```js:eslint.config.js
import js from "@eslint/js";

export default [
  js.configs.recommended, // files指定なし；すべてのファイルに適用
  // 以下、設定をoverride
  {
    files: ["src/**/*", "test/**/*"], // Lint対象ファイル指定
    rules: {
      semi: ["warn", "always"],
    },
  },
  {
    files: ["**/*.js"],
    plugins: {
      jsdoc: jsdoc,
    },
    rules: {
      "jsdoc/require-description": "error", // rulesの記法は、eslintrcに同じ
      "jsdoc/check-values": "error",
    },
  },
  // ...other configurations
];
```

<details><summary>.eslintrc.js - plugins, rules, overrides</summary>

eslintrc の Lint 設定は、eslintrc ファイルが配置されているディレクトリとその配下すべてのファイル（.eslintignore で除外されている場合を除く）に適用されます。
もしファイル別に設定を定義したい場合は、`overrides` プロパティで指定が必要でした。

またプラグインは、`plugins` プロパティにて string ベースで読み込んでいました。

```js:.eslintrc.js
module.exports = {
  // ...other config
  plugins: ["jsdoc"], // stringベースでプラグイン定義
  rules: {
    "jsdoc/require-description": "error",
    "jsdoc/check-values": "error",
  },
  overrides: [
    {
      files: ["src/**/*", "test/**/*"], // Lint対象ファイル指定
      rules: {
        semi: ["warn", "always"],
      },
    },
  ],
  // ...other config
};
```

</details>

### 2. 事前定義 / 共有定義 Config の利用（extends 廃止）

Flat Config では npm パッケージ名やローカルフォルダのパスを指定することでモジュールとして import することで利用することができます。

```js:eslint.config.js
import js from "@eslint/js";  // モジュールインポート
import customConfig from "./custom-config.js";
import myConfig from "eslint-config-my-config";

export default [
  js.configs.recommended,
  myConfig,
  {
    ...customTestConfig,
    files: ["**/*.test.js"], // Config を適用する対象ファイルを制限
  },
  {
    rules: {
      semi: ["warn", "always"],
    },
    // ...other config
  },
];
```

<details><summary>.eslintrc.js - extends</summary>

eslintrc では extends を用いて定義済みの設定ファイルを利用していた。

```js:.eslintrc.js
module.exports = {
  // ...other config
  extends: [
    "eslint:recommended",
    "./custom-config.js",
    "eslint-config-my-config",
  ],
  rules: {
    semi: ["warn", "always"],
  },
  // ...other config
};
```

</details>

### 3. LanguageOptions（globals, parserOptions および カスタム Parser）

`globals` および `parserOptions` は `languageOptions` キー配下に統合されました。
そして `env` プロパティは削除され、代わりに特定のランタイム環境設定は、[globals](https://www.npmjs.com/package/globals)（npm パッケージ）からインポートするようにします。

またカスタム Parser は、モジュールとしてインポートした上で、languageOptions.parser に割り当てます。

```js:eslint.config.js
import globals from "globals";  // JavaScriptランタイム環境用グローバル変数
import babelParser from "@babel/eslint-parser"; // カスタム Parser モジュール

export default [
  {
    languageOptions: {
      ecmaVersion: 2022, // parserOptions相当を定義
      sourceType: "module",
      globals: {
        ...globals.browser, // env廃止; globalsから展開
        ...globals.node,
        myCustomGlobal: "readonly",
      },
      parser: babelParser, // カスタムParser; languageOptions.parserに割当て
    },
    // ...other config
  },
];
```

<details><summary>.eslintrc.js - env/globals, parserOptions / カスタムParser</summary>

従来の eslintrc では、globals および parserOptions はそれぞれ個別定義。
env プロパティで特定のランタイムを設定していました。

またカスタム Parser は、プラグインに類似して string ベースで個別定義していた。

```js:.eslintrc.js
module.exports = {
  env: {
    browser: true,
    node: true,
  },
  globals: {
    myCustomGlobal: "readonly",
  },
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: "module",
  },
  parser: "@babel/eslint-parser", // stringベースで定義
  // ...other config
};
```

</details>

### 4. Processors

Flat Config では eslintrc 同様、プロセッサをプラグインから名前参照できることに加え、直接設定に挿入することもできるようになりました。またプロセッサが自動的に設定されることはありません。

例えば以下のようなカスタムプラグインを定義していた場合、

```js:e.g.) node_modules/eslint-plugin-someplugin/index.js
module.exports = {
    processors: {
        ".md": {  // 「.」で始まる拡張子名プロセッサ：eslintrcでは ESLint が自動設定した
            preprocess() {},
            postprocess() {}
        },
        "someProcessor": {
            preprocess() {},
            postprocess() {}
        }
    }
};
```

Flat Config では、名前参照や直接参照することができます。

```js:eslint.config.js
import somePlugin from "eslint-plugin-someplugin";

export default [
    {
        plugins: { somePlugin },
        processor: "somePlugin/someProcessor" // 名前参照
    },
    {
        plugins: { somePlugin },
        processor: somePlugin.processors.someProcessor  // 直接参照
    },
    {
        processor: somePlugin.processors.someProcessor
    }
];
```

<details><summary>.eslintrc.js - processor</summary>

プロセッサはプラグインで定義され、string ベースで名前参照する方法しかなかった。

```js:.eslintrc.js
module.exports = {
    plugins: ["someplugin"],
    processor: "someplugin/someProcessor",
    // ↓ ESLintが自動設定するプロセッサ（実際はコード上に現れない）
    overrides: [{
        files: ["**/*.md"],
        processor: "someplugin/.md"
     }]
};
```

</details>

### 5. ignore 設定

ESLint の対象から外す(ignore)させるためには、`ignore` プロパティのみを持つオブジェクトを定義することで実現できます。これまでの `.eslintignore` を読み込むことはできないので、注意が必要です。

なお、`["**/node_modules/", ".git/"]` はデフォルト設定(明示無しで適用)されます。[^4]

```js:eslint.config.js
export default [
  // ...other config
  {
    // ignore以外のプロパティを持たないオブジェクト
    ignores: ["**/temp.js", "config/*"],
  },
];
```

<details><summary>.eslintignore, eslintrc.js - ignore設定</summary>

`.gitignore` 同様に、プロジェクトルートに `.eslintignore` ファイルを定義する。
もしくは、eslintrc で個別設定することで ignore 設定が可能。

```config:.eslintignore
temp.js
config/*
# ...other ignored files
```

```js:.eslintrc.js
module.exports = {
  // ...other config
  ignorePatterns: ["temp.js", "config/*"],
};
```

</details>

:::note alert
Flat Config では、デフォルトでドットファイル(e.g. `.dotfile` )無視されないので、
無視したい場合は、`"**/.*"` のように明示的な設定が必要です。
:::

### 6. Linter 動作設定

`linterOptions` プロパティを default 直下、最上位に定義することができます。
`linterOptions` は、`noInlineConfig` と `reportUnusedDisableDirectives` を含めることができ、それぞれ従来の eslintrc から存在するプロパティです。

```js:eslint.config.js
export default [
  {
    // ...other config
    linterOptions: {
      noInlineConfig: true,
      reportUnusedDisableDirectives: "warn",
    },
  },
];
```

<details><summary>.eslintrc.js - Linter 設定</summary>

linterOptions としてまとめられておらず、他 Config 設定が存在する場合に可読性が低かった。

```js:.eslintrc.js
module.exports = {
  // ...other config
  noInlineConfig: true,
  reportUnusedDisableDirectives: true,
};
```

</details>

# どうも eslintrc → Flat Config 移行できないあなたに

今すぐに移行できない場合、あるいは共有された eslintrc ファイルを Flat Config として利用したい場合、`FlatCompat` を利用すると Flat Config で eslintrc を読み込むことができます。

まず `@eslint/eslintrc` を install します。

```shell
npm install @eslint/eslintrc --save-dev
```

そして、eslintrc フォーマットの設定ファイル（例として `eslint-config-my-config` ）を用いるため、以下のように FlatCompat を用いて変換するインスタンスを定義します。

`FlatCompat#extends()` が Flat Config の設定を行う配列に変換し、展開してくれます。

```js:eslint.config.js
import { FlatCompat } from "@eslint/eslintrc";
import path from "path";
import { fileURLToPath } from "url";

// mimic CommonJS variables -- not needed if using CommonJS
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

export default [
  // mimic ESLintRC-style extends
  ...compat.extends("eslint-config-my-config"),
];
```

`FlatCompat` について詳細は、eslintrc - README ご参照のこと。

https://github.com/eslint/eslintrc#usage

# 最後に

ESLint Flat Config 移行にあたって押さえておくべき内容をまとめてみました。

今回に限らず、フロントエンド界隈はフレームワークの変遷が特に激しく、今回のような EOL によって構造が変わるケースもままあります。流行り廃りに敏感でいるためにも率先したキャッチアップが求められますね。後手後手に回らず、あるべきを追従したいものです。

ここまでご覧いただきありがとうございました。
クリスマスまでちょうど 1 週間です。素敵なクリスマスをお過ごしください。🎄

# 余談 - JavaScriptカンファレンス@オランダ参加した。

筆者は今年 6 月に開催した JavaScript の祭典、JSNation 2024@オランダ に現地参加してきました。そのとき、Vue.js や Nuxt、Vite の開発コアメンバである [Anthony Fu](https://antfu.me/) 氏の講演 [ESLint One for All Made Easy](https://gitnation.com/contents/eslint-one-for-all-made-easy) を聞き、ESLint の需要を肌で感じました。

現地会場の様子や講演内容が気になる方は、こちらをご覧ください。

https://atlaxblogs.nri.co.jp/entry/20240820

[^1]: https://eslint.org/blog/2023/10/flat-config-rollout-plans/
[^2]: https://eslint.org/version-support/
[^3]:
    後述のプラグインや Config 等、Flat Config に未対応のものは、対応バージョンに上げるか、
    [@eslint/compat パッケージ](https://github.com/eslint/rewrite/tree/main/packages/compat) `fixupXXXRules`を用いて適用させる必要がある。
[^4]: https://eslint.org/docs/latest/use/configure/ignore#ignoring-files