ESLintのconfigがどのように変わり得るか(flat configとは何か)
はじめに
v8.21.0のリリースでこれまでとは異なるconfigシステム(flat config)が持ち込まれた。
ESLint以下の通り、新しいconfigシステムへの一歩というように言及があり、ESLintを利用する上で無視できない影響を受ける変更となりそうなので、どのようなものか確認しておきたい。
We took a big step toward ESLint’s new config system! The new FlatESLint class is now merged. Its API is not yet stable, and not all features are implemented yet, but it is accessible via the Node.js API for early testing. See RFC9 for the original design.
flat configとは何かを大雑把にまとめてしまうと
シンプルさを重視した新しいconfigのこと。
これまでのconfigがさまざまな経路で拡張や上書き可能なことで設定コストが高くなっていたのを、1つのファイルの配列に集約することで設定が容易になることを意図していると思う。
また、デフォルト値を今のJavaScriptの状況に合わせたり、これまで整理されていなかったconfigの構成を妥当性のあるように変更したりもしている。
eslintrc
)とflat configを比較する
これまでのconfig(表にして比較してみると主な相違点は以下のようものがあると思う。なお便宜上これまでの設定をeslintrc
とする。
項目 | eslintrc |
flat config |
---|---|---|
configファイル |
.eslintrc.js 、.eslintrc.yml 、.eslintrc.json 、package.json (eslintConfig フィールド)などさまざま |
eslint.config.js のみ |
configの探索 |
config cascadingによってroot: true もしくはルートディレクトリまでconfigファイルを探し続ける |
カレントディレクトリか最も近い上位のディレクトリのeslint.config.js
|
configのカスケーディング | ファイルシステムベースやoverrides など |
eslint.config.js でのconfigオブジェクトの配列 |
ecmaVersion のデフォルト |
5 |
latest |
sourceType のデフォルト |
script |
module (ESM)かcommonjs (CommonJS) |
対象ファイルのデフォルト | *.js |
*.js 、*.mjs 、.cjs
|
JavaScriptの解釈に関するオプション |
globals , ecmaVersion , sourceType , env など |
languageOptions に集約 |
プラグインの利用 | 文字列での指定 | モジュールとして明示的にimport
|
eslint.config.js
flat configではカレントディレクトリかより上位のディレクトリからeslint.config.js
をconfigとして探す。見つかった時点でそれ以上探すことはなく、そのファイルだけを利用する。
eslint.config.js
がこれまでとどのように異なるか実際にイメージするため、適当な内容ではあるけどもeslint.config.js
を以下に記述してみる。
import graphqlEslint from "@graphql-eslint/eslint-plugin" // プラグインは明示的にimportするように
import eslintImport from "eslint-plugin-import"
// 配列にconfigオブジェクトを並べる。後方のconfigオブジェクトが優位となって上書き・マージされていく。
export default [
// ESLintのあらかじめ用意された eslint:recommended、eslint:all のconfigは文字列で配列に含めることができる。
"eslint:recommended",
{
// pluginの指定がfiles指定なしで行われた場合、全てのファイルを対象としてpluginが有効になる
plugins: {
eslintImport,
},
rules: {
"eslintImport/default": "error",
},
},
{
// ignoresのみを指定したconfigオブジェクトで.eslintignoreのようにグローバルに無視するファイルを指定できる(.eslintignoreのパターンの後に追加となる)。
ignores: ["./lib/**/*"],
},
{
rules: {
// 後方のconfigオブジェクトが前方のconfigオブジェクトをマージ(競合したら上書き)するので、semiのoffは後方のerrorによって上書きされる
semi: "off",
},
},
{
files: ["./**/*.js"],
ignores: ["./*.config.js"],
// ESLintのJavaScriptの解釈に関わることはlanguageOptionsのオプションで設定する。
languageOptions: {
ecmaVersion: 2020,
sourceType: "commonjs",
globals: {
FOO: "readonly",
},
},
rules: {
semi: "error",
},
},
{
files: ["./src/bar/*.js"],
languageOptions: {
// 後方のconfigオブジェクトが前方のconfigオブジェクトをマージ(競合したら上書き)するので、globalsにはFOOとBARが含まれた状態になり、FOOは書き込み可能として扱う。
globals: {
FOO: "writable",
BAR: "readonly",
},
},
// Linter特有のオプションをlinterOptionsに。
linterOptions: {
// /* eslint semi: error */のようなインラインコメントを無効にする。
noInlineConfig: true,
// /*eslint-disable-next-line*/ のようなLint無効化のコメントが不要になっている場合にリポートする。
reportUnusedDisableDirectives: true,
},
},
{
files: ["./src/**/*.graphql"],
languageOptions: {
// parserに関するconfigもlanguageOptionsで設定する。
parser: graphqlEslint,
parserOptions: {
schema: "./schema.graphql",
},
},
plugins: {
// pluginsにはプラグインのキー名とプラグイン自体の組み合わせで指定する。
// このキー名がそのままrulesにおけるルール名やprocessorなどのプレフィックスとなる。
graphql: graphqlEslint,
},
processor: "graphql/processor",
rules: {
"graphql/known-type-names": "error",
},
},
]
個別の内容についてもう少し見ていく。
configオブジェクト
配列に指定するconfigオブジェクトは、全体としては以下のようなプロパティ構成になる。
files: ["./**/*.js"], // Lint対象のファイルのglobパターン
ignores: ["./*.config.js"],// Lint対象外のファイルのglobパターン。filesにマッチしているファイル全てを対象としてパターンマッチする。
languageOptions: {// JavaScriptをどのように解釈するかのオプション群。
ecmaVersion: "latest",// サポートするECMAScriptのバージョン。
sourceType: "module", // JavaScriptのコードの種別。scriptかmoduleかcommonjsか。
globals: {// グローバルに定義しておくべき値のまとまり。
Promise: "off",
},
parser: "pluginName/parserName",// parser名。
parserOptions: {...},// parserのオプション群。
},
linterOptions: {// Linter特有のオプション群。
noInlineConfig: false,// ESLintのインラインコメントを無効にするかどうかの真偽値。
reportUnusedDisableDirectives: false,// Lint無効化のコメントが不要になっている場合にリポートするかどうかの真偽値。
},
processor: "pluginName/processorName", // processor名。
plugins: {// プラグイン群のオブジェクト。プラグイン名のキーとパッケージのオブジェクトの組み合わせ。
pluginName: pluginName,
}
rules: {// ルール群のオブジェクト。
"ruleName": "error",
},
settings: {...}// 全てのルールで共通して参照する情報のオブジェクト。
論理的なデフォルト値
混乱を減らすようにデフォルト値が見直されている。ECMAScriptのバージョンやESModuleに関連して現在のJavaScriptの状況を反映したものになっている。
具体的な内容としては以下のようなもの。
-
ecmaVersion: "latest"
- 常に最新のバージョンを選択するように。手動で
ecmaVersion
を指定することは基本的になくなるはず
- 常に最新のバージョンを選択するように。手動で
-
sourceType
- デフォルトでesmoduleとして扱うように。
-
sourceType: "module"
-
.js
と.mjs
-
-
sourceType: "commonjs"
が追加に-
.cjs
向け
-
-
.js
,.mjs
,.cjs
を探すように-
--ext
で指定しなければ.js
だけを探していたけどもこれらの拡張子もみるようになった
-
globベースでの設定をあらゆる場所で可能に
これまでoverrides
では可能だったglobベースでの対象ファイルの設定が、flat configでは全てのconfigオブジェクトで利用可能となる。これによってconfiguration cascadingによる設定の上書きを代替可能になる。
{
files: ["./src/**/*.js"],
ignores: ["./*.config.js"],
...
extends
プロパティは無くなる
eslint.config.js
での配列において、先頭のconfigオブジェクトから末尾のconfigオブジェクトに至るまでカスケーディングする。以下のようなルールのコンフリクトがあれば常に後方のconfigオブジェクトが優先される。
Shareable Configsとしてeslint-config-*
のようなconfigのパッケージを全体のベースとして使いたいケースがあると思うけど、配列の先頭に含めることでベースとして扱うことになる。
import fooConfig from "eslint-config-foo";
export default {
fooConfig,
{
...,
},
}
languageOptions
の追加
globals
, ecmaVersion
, sourceType
, env
などJavaScriptをどう解釈するかの設定が混乱しやすい構成になっているので、それらの設定はlanguageOptions
へ集約するように。
env
はもはや必要性がないということで廃止となっていて、環境に関する情報の設定はlanguageOptions.globals
が代替となり得る。
globals
パッケージがESLintでの環境に関する情報を全て持っている。
plugin
既存のプラグインは変更せずともこれまで通り動作するはず。flat configでは文字列での指定ではなく明示的にimport
したそのものを利用する。
プラグイン名はユーザの自由となり、そのネームスペースもユーザの自由となる。
プロジェクトローカルでのカスタムルールの追加には--rulesdir
オプションが必要だったけども、それもただカスタムルールのモジュールをimport
すれば良いだけに。
import customRule from "./custom-rule.js";
export default [
{
files: ["./src/**/*.js"],
plugins: {
custom: {
rules: {
customRule,
},
},
},
rules: {
"customRule": "error",
},
},
];
後方互換
@eslint/eslintrc
のFlatCompat
によってflat configでこれまでの設定の互換を保つことが可能になる。
flat configに至った背景
flat config導入に至った背景については以下のブログ記事にまとめられていて、現在のconfigがどのように複雑になっていったか過去の経緯を振り返っている。
主に以下のような機能追加や修正を加えていった結果、複雑なconfigになってしまったので、その反省からflat configへと至ったということのよう。
-
extends
- npmパッケージに設定を切り出して利用することが可能になった
-
eslint:recommended
によって推奨されるルールを明示することができた
- ユーザ固有のconfigファイル
- 設定ファイルが見つからなければ個人の設定ファイルとして
~/.eslintrc
を探すように
- 設定ファイルが見つからなければ個人の設定ファイルとして
- configファイルのフォーマット
-
.eslintrc
,.eslintrc.json
,.eslintrc.yml
,.eslintrc.yaml
,.eslintrc.js
。フォーマットの多様化によってJSとそれ以外での互換性を保てないところが出てきているらしい。
-
-
root
-
configuration cascading
がユーザの混乱を生んでいて、configファイルがどの親ディレクトリに存在するか把握しきれない - 辿る親ディレクトリを制限できるように
root
キーを追加した(--init
でデフォルト有効にして混乱を減らすように)
-
-
overrides
- globパターンベースで特定のサブセットに対するルールを上書き可能にした
-
overrides
がconfiguration cascading
を排除する代替として適切なアプローチであるように考えられたけど、当時はconfiguration cascading
の排除に至らなかったという振り返り
-
overrides
に対するextends
の追加- 上書きした設定に拡張した設定を加えることを可能に(かなり複雑な印象を受ける)
まとめ
ESLintのconfigが最近複雑になっているのは実感としてあったので、flat configで1箇所の配列で集約されたシンプルな形になると良さそうな印象がある。
今回特に試してないけども、Linterクラスを利用していたりするとflat configを試すことが可能(new Linter({ configType: "flat" })
)となっている。
恐らく今後ESLintのエコシステムにおけるflat configが問題なく動作するかなどのフィードバックを受け付けて、どこかで正式なリリースタイミングがくると思う(いつかは不明瞭だけども)ので、それを気にかけておきたい。
参考
- Configuration Files (New) - ESLint - Pluggable JavaScript Linter
- ESLint's new config system, Part 1: Background - ESLint - Pluggable JavaScript Linter
- ESLint's new config system, Part 2: Introduction to flat config - ESLint - Pluggable JavaScript Linter
- ESLint's new config system, Part 3: Developer preview - ESLint - Pluggable JavaScript Linter
- rfcs/designs/2019-config-simplification at main · eslint/rfcs
- Implement Flat Config · Issue #13481 · eslint/eslint
- Configuration Files - ESLint - Pluggable JavaScript Linter
Discussion