🔧

ESLintのeslintrcをFlat Configに移行してみた

2023/11/05に公開

はじめに

みなさん、ESLint の Flat Config 移行は進んでいますか?
先日、ESLint の Flat Config への移行に関するブログが公開されました。
ざっくり下記のような流れで移行していくとのことです。

  • ESLint v9 (今年末 ~ 来年の初めにリリース予定)で flat config がデフォルトになり、eslintrc は非推奨になる。
  • eslint v10 (2024年末 ~ 2025 年初頭にリリース予定)で eslintrc が削除される

徐々に Flat Config に移行していかなければ...ということで、以前、筆者が作成した vite-plugin-sri2 で使用している eslintrc を Flat Config に移行してみました。
本記事では移行する時に対応したことを列挙していきます。

移行前後の比較

先に移行前の eslintrc と移行後の Flat Config の設定ファイルをそれぞれ載せておきます。

Before

.eslintrc.js
module.exports = {
  root: true,
  extends: [
    'eslint:recommended',
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'prettier'
  ],
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly'
  },
  plugins: ['@typescript-eslint', 'html', 'markdown'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 2020
  },
  ignorePatterns: ['**/fixtures/**'],
  rules: {
    'no-console': 'off',
    'no-debugger': 'error',
    'node/no-deprecated-api': 'off',
    'node/no-unpublished-import': 'off',
    'node/no-unpublished-require': 'off',
    'node/no-unsupported-features/es-syntax': 'off',
    'no-process-exit': 'off',
    'node/no-missing-import': 'off'
  }
};

After

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
import html from 'eslint-plugin-html';
import markdown from 'eslint-plugin-markdown';
import js from '@eslint/js';
import typeScriptESLint from '@typescript-eslint/eslint-plugin';
import typeScriptESLintParser from '@typescript-eslint/parser';

const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
  ...compat.extends('plugin:node/recommended', 'plugin:@typescript-eslint/eslint-recommended'),
  {
    plugins: {
      typeScriptESLint,
      html,
      markdown
    },
    languageOptions: {
      globals: {
        Atomics: 'readonly',
        SharedArrayBuffer: 'readonly'
      },
      parser: typeScriptESLintParser,
      parserOptions: {
        sourceType: 'module',
        ecmaVersion: 2020
      }
    },
    rules: {
      'no-console': 'off',
      'no-debugger': 'error',
      'node/no-deprecated-api': 'off',
      'node/no-unpublished-import': 'off',
      'node/no-unpublished-require': 'off',
      'node/no-unsupported-features/es-syntax': 'off',
      'no-process-exit': 'off',
      'node/no-missing-import': 'off'
    }
  }
];

Flat Config に移行する時にやったこと

ここからは実際に Flat Config へ移行する時に対応したことを列挙していきます。
公式でマイグレーションガイドが用意されているので、こちらを参考にしながら対応していきました。

Flat Config を有効にする

プロジェクトのルートに eslint.config.js を配置するか、環境変数の ESLINT_USE_FLAT_CONFIGtrue にすることで Flat Config を有効化できます。
今回は環境変数の設定はせず、eslint.config.js の配置だけします。

eslint.config.js
export default [
]

--ignore-pattern を ignores に置き換える

eslintrc の時は下記のように lint を実行する npm scripts に --ignore-path オプションを指定していました。これにより、lint 実行時も .gitignore で除外しているファイルを除外できます。

package.json
{
  "scripts": {
    "lint": "eslint --ignore-path .gitignore '**/*.{js,ts,html,md}'",
  }
}

Flat Config には --ignore-pattern オプションがないので、代わりに ignores プロパティで除外したいファイルを指定しました。

eslint.config.js
export default [
+ {
+   ignores: ['**/fixtures/**', '**/dist/**']
+ },
]

また、--ignore-path オプションは削除しています。

package.json
{
  "scripts": {
-   "lint": "eslint --ignore-path .gitignore '**/*.{js,ts,html,md}'",
+   "lint": "eslint '**/*.{js,ts,html,md}'",
  }
}

extends を移行する

eslintrc の時は下記のように extends プロパティを指定していました。

.eslintrc.js
module.exports = {
  extends: [
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'prettier'
  ],
}

Flat Config には extends プロパティがないので対応が必要です。

eslint:recommended を移行する

まずは、他の共有設定と比べて指定方法が少し特殊な eslint:recommended から設定していきます。Flat Config では、@eslint/jsという別パッケージに切り出されています。

まずはパッケージをインストールします。

pnpm add -D @eslint/js

設定ファイルに追記します。

eslint.config.js
+ import js from '@eslint/js';

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
+ js.configs.recommended,
];

Flat Config 対応が完了している共有設定を移行する

これまでは、パッケージの解決を ESLint 側が担っていました。
Flat Config では、設定ファイルの形式を JS のみに絞ったことで、JS の機能を活用できるようになり、 import などが使えるようになりました。
その結果、下記のように設定を書く側でパッケージの解決をする形に変わりました。

eslint.config.js
+ import eslintConfigPrettier from 'eslint-config-prettier';
import js from '@eslint/js';

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
+ eslintConfigPrettier,
];

Flat Config 対応が完了していない共有設定を移行する

2023/11/05 時点だと、eslint-plugin-node@typescript-eslint/eslint-pluginはまだ Flat Config へ移行されていませんでした。

ですが、安心してください、このような問題を解決するために公式がツールを用意してくれています。(ありがたい🙏)
具体的には、eslintrc 形式から Flat Config 形式に変換してくれる FlatCompat を使用していきます。

FlatCompat も別パッケージに切り出されているのでまずはインストールから。

pnpm add -D @eslint/eslintrc

次に FlatCompat 経由で共有設定を読み込みます。

eslint.config.js
+ import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
import js from '@eslint/js';

+ const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
+ ...compat.extends(
+   'plugin:node/recommended',
+   'plugin:@typescript-eslint/eslint-recommended',
+   'prettier'
  ),
];

globals を移行する

これまでは下記のように指定していました。

.eslintrc.js
module.exports = {
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly'
  },
}

Flat Config では languageOptions プロパティ内の globals プロパティに指定します。

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
import js from '@eslint/js';

const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
  ...compat.extends(
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
  ),
+ {
+   languageOptions: {
+     globals: {
+       Atomics: 'readonly',
+       SharedArrayBuffer: 'readonly'
+     },
+   },
+ }
];

plugins を移行する

これまでは下記のように指定していました。

.eslintrc.js
module.exports = {
  plugins: ['@typescript-eslint', 'html', 'markdown'],
}

Flat Config では共有設定の時と同様に設定を書く側で import してから plugins プロパティに指定します。

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
+ import html from 'eslint-plugin-html';
+ import markdown from 'eslint-plugin-markdown';
import js from '@eslint/js';
+ import typeScriptESLint from '@typescript-eslint/eslint-plugin';

const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
  ...compat.extends(
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
  ),
  {
+   plugins: {
+     typeScriptESLint,
+     html,
+     markdown
+   },
    languageOptions: {
      globals: {
        Atomics: 'readonly',
        SharedArrayBuffer: 'readonly'
      },
    },
  }
];

parser を移行する

これまでは下記のように指定していました。

.eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
}

Flat Config では languageOptions プロパティ内の parser プロパティに指定します。
また、共有設定の時と同様に設定を書く側で import してから parser プロパティに指定します。

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
import html from 'eslint-plugin-html';
import markdown from 'eslint-plugin-markdown';
import js from '@eslint/js';
import typeScriptESLint from '@typescript-eslint/eslint-plugin';
+ import typeScriptESLintParser from '@typescript-eslint/parser';

const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
  ...compat.extends(
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
  ),
  {
    plugins: {
      typeScriptESLint,
      html,
      markdown
    },
    languageOptions: {
      globals: {
        Atomics: 'readonly',
        SharedArrayBuffer: 'readonly'
      },
+     parser: typeScriptESLintParser,
    },
  }
];

parserOptions を移行する

これまでは下記のように指定していました。

.eslintrc.js
module.exports = {
  parserOptions: {
    sourceType: 'module',
    ecmaVersion: 2020
  },
}

Flat Config では languageOptions プロパティ内に parserOptions オプションを含めます。

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
import html from 'eslint-plugin-html';
import markdown from 'eslint-plugin-markdown';
import js from '@eslint/js';
import typeScriptESLint from '@typescript-eslint/eslint-plugin';
import typeScriptESLintParser from '@typescript-eslint/parser';

const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
  ...compat.extends(
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
  ),
  {
    plugins: {
      typeScriptESLint,
      html,
      markdown
    },
    languageOptions: {
      globals: {
        Atomics: 'readonly',
        SharedArrayBuffer: 'readonly'
      },
      parser: typeScriptESLintParser,
+     parserOptions: {
+       sourceType: 'module',
+       ecmaVersion: 2020
+     }
    },
  }
];

rules を移行する

これまでは下記のように指定していました。

.eslintrc.js
module.exports = {
  rules: {
    'no-console': 'off',
    'no-debugger': 'error',
    'node/no-deprecated-api': 'off',
    'node/no-unpublished-import': 'off',
    'node/no-unpublished-require': 'off',
    'node/no-unsupported-features/es-syntax': 'off',
    'no-process-exit': 'off',
    'node/no-missing-import': 'off'
  },
}

rules に関しては非互換な変更はないのでそのままコピペでOKです。

eslint.config.js
import { FlatCompat } from '@eslint/eslintrc';
import eslintConfigPrettier from 'eslint-config-prettier';
import html from 'eslint-plugin-html';
import markdown from 'eslint-plugin-markdown';
import js from '@eslint/js';
import typeScriptESLint from '@typescript-eslint/eslint-plugin';
import typeScriptESLintParser from '@typescript-eslint/parser';

const compat = new FlatCompat();

export default [
  {
    ignores: ['**/fixtures/**', '**/dist/**']
  },
  js.configs.recommended,
  eslintConfigPrettier,
  ...compat.extends(
    'plugin:node/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
  ),
  {
    plugins: {
      typeScriptESLint,
      html,
      markdown
    },
    languageOptions: {
      globals: {
        Atomics: 'readonly',
        SharedArrayBuffer: 'readonly'
      },
      parser: typeScriptESLintParser,
      parserOptions: {
        sourceType: 'module',
        ecmaVersion: 2020
      }
    },
+   rules: {
+     'no-console': 'off',
+     'no-debugger': 'error',
+     'node/no-deprecated-api': 'off',
+     'node/no-unpublished-import': 'off',
+     'node/no-unpublished-require': 'off',
+     'node/no-unsupported-features/es-syntax': 'off',
+     'no-process-exit': 'off',
+     'node/no-missing-import': 'off'
+   }
  }
];

おわりに

ここまで Flat Config へ移行する時に対応したことを列挙していきました。
マイグレーションガイドがかなり手厚く、特に詰まるところもなかったのでありがたいですね...🙏
残っているプロジェクトも徐々に移行していこうと思います💪

参考

Discussion