sunabox

ESLintのFlat Config対応 (2024)

以前、ESLintをFlat Configに置き換えるという記事を書きました。

eslintをflat configで書き換える | sunaboxeslintの設定をflat configで置き換えてみました。
faviconsuna.dev

この記事を書いたのは2年前で、当時はまだ各種pluginやconfigのライブラリがFlat Configに対応していなかったため、各所でFlatCompatを使う必要がありました。

あれから2年の時が経ち、各種ライブラリがFlat Configをサポートしています。
また、最近Next.jsのv15によってeslintのv9対応がなされたこともあり、より最適な形でFlat Configとして移行できるようになったと思います。

こうした流れを受けて、最近プロダクトでESLintのFlat Config移行をしたのでその時に色々調べたことを備忘録として残しておきます。
基本的な設定の仕方などは以前の記事に記載したのとMigration Guideが用意されているので省略。

Configuration Migration Guide - ESLint - Pluggable JavaScript LinterA pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease.
faviconeslint.org

今回使用したpackageのバージョンは以下。
主な移行対象は@typescript-eslinteslint-config-nextです。

.eslintignoreの廃止

これまでは.eslintignoreにglobalにignoreしたいファイルを記載していました。
Flat Configではこれもオブジェクトとして設定します。

JavaScript Icon
eslint.config.mjs
export default [
  {
    ignores: ["**/node_modules/**", "**/dist/**", "**/public/**"],
  },
  ...
];

注意点として、従来の.eslintignoreのようにglobalにignoreしたい意図で設定する場合は、そのオブジェクトの中ではignores以外は設定してはいけません。

If ignores is used without any other keys in the configuration object, then the patterns act as global ignores.
https://eslint.org/docs/latest/use/configure/configuration-files#configuration-objects

ドキュメントにもしっかり書いてました。最初filesを併記していたせいでignoresが効かなくて若干ハマりました。

@typescript-eslintのリプレイス

これまでは@typescript-eslint/parser@typescript-eslint/eslint-pluginをそれぞれインストールし、extendsに以下のような設定を書くことでplugin, parser, rulesを一括で設定していました。

.eslintrc.json
{
  "extends": [
    "plugin:@typescript-eslint/recommended",
  ],
}

Flat Configでは新しくtypescript-eslintを使用でき、config関数を使用することが推奨されています。

JavaScript Icon
eslint.config.mjs
import tseslint from "typescript-eslint"
 
export default tseslint.config(
  tseslint.configs.recommended,
  {
    ...
  }
)

これで同様にpluginとparser, rulesを一括で設定できます。

実際のプロジェクトでは全体の中の1つのオブジェクトとして設定されてるようにしたかったため、以下のように書き換えましたがこの辺は好みかと思います。

JavaScript Icon
eslint.config.mjs
import tseslint from "typescript-eslint"
 
export default [
  ...tseslint.config({
    extends: [tseslint.configs.recommended],
  }),
  {
    ...
  }
]

ちなみにparserとpluginをそれぞれ個別に設定することもtypescript-eslintだけで可能です。
そのため、@typescript-eslint/parser@typescript-eslint/eslint-pluginはもはや不要になります。

JavaScript Icon
eslint.config.mjs
import tseslint from "typescript-eslint"
 
export default [
  plugins: {
    '@typescript-eslint': tseslint.plugin,
  },
  languageOptions: {
    parser: tseslint.parser,
  },
  ...
]

ちなみにpluginを設定する場合、namespaceは以前と同じ@typescript-eslintとすることが強く推奨されています。

We strongly recommend declaring our plugin with the namespace @typescript-eslint as shown above. If you use our shared configs this is the namespace that they use. This has been the standard namespace for our plugin for many years and is what users are most familiar with.
https://typescript-eslint.io/packages/typescript-eslint#advanced-usage

eslint-config-nextのリプレイス

以前まではeslint-config-nextを使っていました。

.eslintrc.json
{
  "extends": [
    "next/core-web-vitals",
  ],
}

しかし、こちらは現時点ではまだFlat Config対応していません。
こちらのissueによるとFlatCompatを使う方法で解決できると記載されています。

New ESLint "flat" configuration file does not work with `next/core-web-vitals` · Issue #64114 · vercel/next.jsLink to the code that reproduces this issue https://codesandbox.io/p/devbox/pensive-star-go8s7s To Reproduce Run npm run dev and observe the error showing that ...compat.extends("next/core-web-vita...
favicongithub.com

試してみたところ、確かに動いたのですがいくつか点で微妙だなと思いました。

微妙な点その1: 内部で@typescript-eslintをpluginとして定義している

Flat Configでは同じplugin定義を登録するとエラーになります。
next/core-web-vitalsの中で@typescript-eslintをpluginとして定義しているため、その状態でtseslintの設定を行うとエラーになります。

JavaScript Icon
eslint.config.mjs
export default [
  ...compat.extends('next/core-web-vitals', 'next/typescript'),
  ...tseslint.config({
    extends: [tseslint.configs.recommended],
  }),
]
TypeError: Key "plugins": Cannot redefine plugin "@typescript-eslint".

そのため、回避しようとすると若干煩雑な記述をせざるを得ませんでした。

JavaScript Icon
eslint.config.mjs
export default [
  ...compat.extends('next/core-web-vitals', 'next/typescript'),
  ...tseslint.configs.recommended.map((conf) => {
    // next/core-web-vitalsの中でも@typescript-eslintをpluginに設定していて、二重定義でエラーになるためplugins以外を設定する
    const { plugins: _, ...rest } = conf
    return { ...rest }
  }),
]

微妙な点その2: 内部でeslint-plugin-importをpluginとして定義している

その1と類似しますが、eslint-plugin-importも内部でpluginとして定義しているため、定義しようとすると同様にエラーになります。
これは単純に定義しないようにすればすれば回避できますが、pluginが暗黙的に設定されているという点でFlat Configの利点を活かしきれてないように思います。

微妙な点その3: Flat Configでしか使用できない機能が使用できない

後述するESLint Config InspectorはFlat Configだけで使用できる機能です。
eslint-config-nextを使用している場合は、Flat Config対応していないためこの機能が使用できませんでした。

以上の点から、FlatCompatを使う方針は断念。
別のDiscussionで使用するpluginをそれぞれ個別に設定する方法が記載されていたのでこちらの方法を採用しました。

How to use new "flat config" approach in Eslint? · vercel/next.js · Discussion #49337Summary I have Eslint working in a TypeScript Next.js project, but I want to switch to the new "flat config" approach that Eslint offers. (Why is that not Next.js's default?) I created /eslint.conf...
favicongithub.com
JavaScript Icon
eslint.config.mjs
import nextPlugin from "@next/eslint-plugin-next"
import reactPlugin from "eslint-plugin-react"
import reactHooksPlugin from "eslint-plugin-react-hooks"
 
export default [
  ...
  reactPlugin.configs.flat.recommended,
  reactPlugin.configs.flat["jsx-runtime"],
  {
    plugins: {
      "react-hooks": reactHooksPlugin,
      "@next": nextPlugin,
    },
    ...reactHooksPlugin.configs.recommended.rules,
    ...nextPlugin.configs.recommended.rules,
    ...nextPlugin.configs["core-web-vitals"].rules,
    "@next/next/no-img-element": "error",
    "react/prop-types": "off",
  }
]

eslint-config-prettierの設定

eslint-config-prettierの中身はrulesの中にあらゆるruleがoffで設定されているだけなので、ruleの最後に適用するだけでokです。

JavaScript Icon
eslint.config.mjs
import prettierConfig from "eslint-config-prettier"
 
export default [
  ...
  {
    rules: {
      ...
      "react/prop-types": "off",
      ...prettierConfig.rules,
    }
  }
]

ESLint Config Inspectorの使用

Flat Configで使用できるツールとしてESLint Config Inspectorがあります。

Introducing ESLint Config Inspector - ESLint - Pluggable JavaScript LinterA pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease.
faviconeslint.org

eslint --inspect-configを実行すると、設定されている内容を可視化したサーバーが立ち上がります。

わりと使い心地良いです。

ちなみに、linterOptionsとsしてdefaultでreportUnusedDisableDirectives: 1が入っていて、未使用のdisableコメントをwarnにしてくれるようになってました。
このオプション自体は以前からあったらしいんですが、初めて知りました。
v8.56.0からerrorにもできるようになったらしいです。

こういうdefaultの設定も視覚的に見れるようになったのは便利だなと思います。

適用前後のチェック

上記のConfig Inspectorはかなり便利ですが、Flat Configでしか使えません。
Flat Configに移行した前後で適用されるルールが変わっていないか、適用するファイルが変わっていないかをきちんとチェックしたいです。

以下の記事がとても参考になりました。

新卒エンジニアがESLintのFlat Config移行と格闘した話 - ドワンゴ教育サービス開発者ブログESLintのFlat Configへの移行は進んでますでしょうか?試してみたでしょうか? 今回はドワンゴの新卒エンジニアが初仕事として取り組んだ、ESLintのFlat Configへの移行に関して「その方法と嵌ったところの乗り越え方」をお伝えします。 この記事で言及すること Flat Configに書き変えるときに見る資料 ESLintのconfigをFlat Configに移行するとき、configs.recommendedなどのプリセットを用いる場合はFlatCompatを使う eslint-plugin-importを使用してると嵌る どうやって新旧configが同じになっていること…
faviconblog.nnn.dev

この記事を参考に、対象ファイルが変わってないかは以下のコマンドで確認しました。

pnpm eslint --debug "src/**/*.ts" |& grep "eslint:languages:js Parsing successful:" | sed 's/.*eslint:languages:js Parsing successful://'

適用されるルールが変わっていないかは以下のコマンドでファイル出力し、以前のファイルと比較して確認しました。

pnpm eslint --print-config src/foo.tsx > eslint-after.log.json

この辺をいい感じにチェックしてくれるツールもありますが、今回は使用しませんでした。

GitHub - sajikix/check-eslint-config-compatContribute to sajikix/check-eslint-config-compat development by creating an account on GitHub.
favicongithub.com

まとめ

  • globalなignoreをしたいならignoresのみを設定したオブジェクトを定義する
  • @typescript-eslintのリプレイスはtypescript-eslintを使う
  • eslint-config-nextはFlat Config対応していないので、pluginを個別に設定する

以前も移行作業自体はしたことがありますが、個人的には適用内容をとても追いやすくなったと思います。
可視化するESLint Config Inspectorも便利です。

以前よりもライブラリのサポートが進んでFlatCompatを一切使わずにFlat Config移行することができました。

参考

Configuration Migration Guide - ESLint - Pluggable JavaScript LinterA pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease.
faviconeslint.org
How to use new "flat config" approach in Eslint? · vercel/next.js · Discussion #49337Summary I have Eslint working in a TypeScript Next.js project, but I want to switch to the new "flat config" approach that Eslint offers. (Why is that not Next.js's default?) I created /eslint.conf...
favicongithub.com
新卒エンジニアがESLintのFlat Config移行と格闘した話 - ドワンゴ教育サービス開発者ブログESLintのFlat Configへの移行は進んでますでしょうか?試してみたでしょうか? 今回はドワンゴの新卒エンジニアが初仕事として取り組んだ、ESLintのFlat Configへの移行に関して「その方法と嵌ったところの乗り越え方」をお伝えします。 この記事で言及すること Flat Configに書き変えるときに見る資料 ESLintのconfigをFlat Configに移行するとき、configs.recommendedなどのプリセットを用いる場合はFlatCompatを使う eslint-plugin-importを使用してると嵌る どうやって新旧configが同じになっていること…
faviconblog.nnn.dev
Buy Me A Coffeeのbutton

目次