dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

ESLintのflat configについてようやく調べた

背景

ESLint v9からflat configがデフォルトとなり、将来のリリースで古いconfigの書き方は廃止される予定なのは把握していた。その後本格的に調べないままになっていたが、そろそろまずいかもなと思って概要だけでも調査したメモ。

先に簡単なまとめ

flat configとは何か

ESLintの新しい設定システム(config system)のこと。

flat config導入の背景

ESLint's new config system, Part 2: Introduction to flat config - ESLint - Pluggable JavaScript Linter

このあたりに書いてそうだったが、あまり詳しくは理解していない。

ESLintも最初にリリースしてから10年が経過し、複雑化したため、よりシンプルで直感的な設計を目指したぽい。

flat configの書き方

参考: Configuration Files

従来は .eslintrc.* という名前の設定ファイルでJSだけでなくYAMLJSONもサポートしていたが、flat configでは eslint.config.js (または ~.mjs, ~.cjs)というようにJSに統一された。
eslint.config.js については、 package.json"type: "module" と書かれていればESM形式、そうでなければCommonJS形式とみなされる。

flat config の中身は、configuration objectと呼ばれるオブジェクトを配列形式で並べたもの。

// eslint.config.mjs (ESM形式)
import customConfig from "eslint-config-custom";

export default [
  customConfig, // configuration object その1
  
  // configuration object その2
  {
    files: ["**/*.js", "**/*.cjs"],
    rules: {
      "semi": "error",
      "no-unused-vars": "error"
    }  
  },
  // configuration object その3    
  {
    files: ["**/*.js"],
    rules: {
      "no-undef": "error",
      "semi": "warn"
    }  
  }
];

各configuration objectはこれらのプロパティで構成される。

  • name
    • configuration objectの名前。Configuration Naming Conventions によれば “optional, but it is recommended to provide a name for each configuration object, especially when you are creating shared configurations.” とのこと
  • files
    • configuration object を適用したいファイルを、globパターンの配列で指定する。省略した場合、他のconfiguration objectによってマッチしたすべてのファイルが対象になる
  • ignores
    • configuration objectの適用対象外にしたいファイルを、globパターンの配列で指定する
  • languageOptions
    • ecmaVersion, sourceType, globals, parser, parserOptionsを設定する
  • linterOptions: 以下、どちらも元からあったプロパティ
    • noInlineConfig:インラインコメントでのルール適用を禁止する(あればエラーになる)
    • reportUnusedDisableDirectives:コメントでenable/disableしたものの、コードの修正によって意味をなさなくなっているものをどう扱うか(error | warn | off)
  • processor
    • 使ったことないので割愛
  • plugins
  • rules
    • ルール。従来通りの記述
  • settings
    • すべてのルールで共有できるkey-value形式の値

注意点として、あるファイルに対して複数のconfiguration objectがマッチした場合、それらはマージされ、配列の前から順番に適用されるような挙動となる。つまりconfiguration object間でプロパティがバッティングしていた場合は後勝ちとなる。

従来のconfigとの違い

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

上記記事の Key Differences between Configuration Formats に主な変更点が記載されている。が、それでも量が多い。
ここでは特に気になったものだけ書く。

また、日本語記事ではこちらがわかりやすかった。

ESLint を eslintrc から Flat Config に移行する、ハマりポイントを添えて。 #JavaScript - Qiita

プラグインのインポート方法

eslintrc では外部のプラグインは以下のように文字列で指定していた。

// .eslintrc.js(抜粋)
module.exports = {
  // ...other config
  plugins: ["jsdoc"],
  ...
}

flat configでは使用するプラグインはconfigファイル内で明示的にimportし、key-valueのペアで使用するプラグイン名とプラグインオブジェクトを指定する。

// eslint.config.js

import jsdoc from "eslint-plugin-jsdoc";

export default [
  {
    files: ["**/*.js"],
    plugins: {
      jsdoc: jsdoc,
    },
    rules: {
      "jsdoc/require-description": "error",
      "jsdoc/check-values": "error",
    },
  },
];

これにより、従来はプラグイン名を eslint-plugin- で始めないといけなかったが、その制約がなくなった。

.eslintignore は廃止

同様に eslintrc の ignorePatterns も廃止。configuration object の ignores を使う。

env プロパティは廃止

特定のランタイム向けのグローバル変数globals というnpmパッケージをインストールし、 globals プロパティに指定して使う

before

// before (.eslintrc.js)
module.exports = {
  env: {
    browser: true,
  },
  globals: {
    myCustomGlobal: "readonly",
  },
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: "module"
  }
  // ...other config
}

after

// eslint.config.js
import globals from "globals";

export default [
  {
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: "module",
      globals: {
        ...globals.browser,
        myCustomGlobal: "readonly"
      }
    }
    // ...other config
  }
];

従来のconfigからの移行方法

ESLint Configuration Migrator (@eslint/migrate-config) というCLIが提供されている。

https://eslint.org/docs/latest/use/configure/migration-guide#migrate-your-config-file

npx @eslint/migrate-config .eslintrc.json

実行すると eslint.config.js が生成される(未確認)。

注意点として、公式ドキュメントには

not guaranteed to work immediately without further modification

とあるのでこれだけではうまくいかないケースもあるらしい。
また、 .eslintrc.js に対してはうまく動かないらしい。

手作業での移行は、先ほども記載した
https://eslint.org/docs/latest/use/configure/migration-guide
を読んで差分を理解し、移行するしかない。

flat config未対応のconfig・pluginを使っている場合

参考:Configuration Migration Guide > Using eslintrc Configs in Flat Config

依存している shareable config がまだ flat config に対応していない場合、FlatCompat というユーティリティを使うことで eslintrc フォーマットから flat config フォーマットに変換できる。

FlatCompat@eslint/eslintrc というパッケージのインストールが必要。

このように、FlatCompat インスタンスを作成した後に適宜メソッドを呼び出して flatconfig 未対応の config 名を渡す。

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"),
];

.extends() 以外にもいくつかメソッドがあるみたい。詳しくは

https://github.com/eslint/eslintrc

のREADME参照。

いつまでに移行が必要?

参考:Flat config rollout plans - ESLint - Pluggable JavaScript Linter

すでに、2024年4月にリリースされた ESLint v9.0.0 からはflat configがデフォルトになっており、従来の config は非推奨(deprecated)という扱い。

v9.0.0 以降で従来の config を使用する場合、ESLINT_USE_FLAT_CONFIG という環境変数false に設定する必要がある。

$ ESLINT_USE_FLAT_CONFIG=false npx eslint src

さらに、v10.0.0 では従来の config は完全に削除される。

v10.0.0 のリリースは参考先のブログ記事では2024年末か2025年初頭と書かれている。

flat config のTypeScript型定義

参考:https://eslint.org/docs/latest/use/configure/migration-guide#typescript-types-for-flat-config-files

DefinitelyTyped にある。

$ npm i -D @types/eslint

eslint.config.js

/** @type import("eslint").Linter.FlatConfig[] */
export default [
  ...
]

と書くと補完が効くようになる。

参考リンク

公式ドキュメント

ブログ記事