ESLint 配置组合实战:用extends合并配置对象与配置数组
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
在实际项目中,eslint.config.js很少完全从零手写:你通常会将社区预定义配置、可共享配置(shareable config)与自己的覆写规则组合在一起,形成项目专属的 ESLint 配置。本文围绕 docs/src/use/configure/combine-configs.md 讲解的两种核心组合模式——应用配置对象与应用配置数组——展开,并结合当前仓库源码,说明extends键在 flat config 体系下的求值顺序、合并语义与底层实现,让你能正确、灵活地组装自己的配置文件。
前置概念:配置对象与配置数组
在理解组合之前,需要先明确两个基础概念(详见 glossary 术语表):
- 配置对象(Config Object):一个包含
files、plugins、rules、languageOptions等键的普通对象,描述了 ESLint 在满足某些条件时如何解析与检查文件。 - 配置数组(Config Array):配置对象的数组。
eslint.config.js导出的就是一个配置数组。数组内的对象按顺序求值,后面的对象可以覆盖前面对象中同名规则的设置——这正是 flat config 区别于旧 eslintrcextends深层合并的关键语义。
每个配置文件都导出配置数组,例如下面这份只含一个配置对象的配置(来自 configuration-files.md 的示例):
// eslint.config.js import { defineConfig } from "eslint/config"; export default defineConfig([ { rules: { semi: "error", "prefer-const": "error", }, }, ]);这里的defineConfig()是eslint/config提供的辅助函数,它的作用仅仅是让编辑器获得更好的类型提示与自动补全,其返回值仍是一个配置数组,与直接写数组字面量等价。
defineConfig与eslint/config入口
在 lib/config-api.js 中可以看到eslint/config模块的真实导出:
const { defineConfig, globalIgnores, includeIgnoreFile, } = require("@eslint/config-helpers"); module.exports = { defineConfig, globalIgnores, includeIgnoreFile, };也就是说,eslint/config对外提供了三个辅助函数:defineConfig(定义配置数组)、globalIgnores(全局忽略)、includeIgnoreFile(引入.gitignore等忽略文件)。它们均来自@eslint/config-helpers包,ESLint 只是将其重新导出,方便统一从eslint/config引入。组合配置时我们主要使用defineConfig包裹整个配置数组。
模式一:应用一个配置对象(files+extends)
当从其他模块导入一个配置对象时,可以创建一个带files键的新对象,并通过extends键把导入对象的其余属性合并进来,从而让该配置只作用于文件子集。
// eslint.config.js import js from "@eslint/js"; import { defineConfig } from "eslint/config"; export default defineConfig([ { files: ["**/*.js"], plugins: { js, }, extends: ["js/recommended"], rules: { "no-unused-vars": "warn", }, }, ]);这段配置的含义是:
files: ["**/*.js"]限定本配置对象只作用于所有.js文件;extends: ["js/recommended"]将@eslint/js包内置的recommended预定义配置(一个配置对象)合并进来。当前仓库中该预定义配置定义在 packages/js/src/index.js:
configs: { all: require("./configs/eslint-all"), recommended: require("./configs/eslint-recommended"), },rules: { "no-unused-vars": "warn" }在合并后的基础上追加规则覆写。
由于files与extends位于同一个对象内,合并后的完整配置(预定义配置的规则 + 你自己的覆写)只会应用到"**/*.js"匹配的文件上。这是"先限定文件范围、再注入预定义配置"的标准写法。
为什么要显式声明plugins
注意上面的示例在extends之外还显式声明了plugins: { js }。原因是:在 flat config 中,规则所属的插件必须在每个使用它的配置对象里可见。extends合并进来的是@eslint/js内部的规则定义,而js/recommended这样的引用字符串最终要解析为某个已注册插件下的规则集。保持插件对象与配置对象同处一个作用域,可以让合并与后续校验(见下文Config类)顺利进行。这是从当前仓库 Config 类的规则解析逻辑 可以观察到的约束:规则 ID 中的插件名会通过config.plugins查找对应插件。
模式二:应用一个配置数组(extends数组)
当从其他模块导入的是一个配置数组时,同样可以通过extends键把它应用到文件子集。例如从eslint-config-example包导入配置:
// eslint.config.js import exampleConfigs from "eslint-config-example"; import { defineConfig } from "eslint/config"; export default defineConfig([ { files: ["**/*.js"], extends: [exampleConfigs], rules: { "no-unused-vars": "warn", }, }, ]);这里extends: [exampleConfigs]传入的是一个配置数组(数组中每个元素是配置对象)。行为与模式一完全一致:exampleConfigs中的全部配置对象先被展开合并,然后rules中的no-unused-vars: "warn"作为后续配置对象追加覆盖。
两种模式的本质区别
| 对比维度 | 模式一:应用配置对象 | 模式二:应用配置数组 |
|---|---|---|
extends中的元素类型 | 配置对象(或引用该对象的字符串,如"js/recommended") | 配置对象、配置数组(或引用它们的字符串) |
| 典型来源 | @eslint/js、eslint-plugin-*导出的单个对象 | 可共享配置包导出的数组 |
| 应用范围 | 受外层files约束 | 受外层files约束,行为一致 |
事实上,两种模式在底层走的是同一条路径:extends接受"字符串、配置对象或配置数组"构成的列表,这些元素会被依次展开后与当前对象的files合并。这与 configuration-files.md 中对extends键的官方定义一致:
extends- An array of strings, configuration objects, or configuration arrays that contain additional configuration to apply.
合并语义:数组按序求值,后者覆盖前者
理解组合配置最关键的一点是合并顺序。在 flat config 中:
- 配置数组内的对象按顺序求值;
- 对于规则配置,后出现的对象会覆盖(override)先前对象中同名规则的设置;
- 对于数组型配置(如
languageOptions的某些字段),则按deepMergeArrays的策略合并——该实现位于 lib/shared/deep-merge-arrays.js(Config类构造时引入)。
以一个三段式配置为例:
export default defineConfig([ { files: ["**/*.js"], extends: ["js/recommended"], }, { files: ["**/*.js"], rules: { "no-unused-vars": "warn", // 覆盖 recommended 中该规则的默认级别 }, }, { files: ["**/*.test.js"], rules: { "no-unused-vars": "off", // 测试文件里彻底关闭 }, }, ]);第一段注入js/recommended,第二段把no-unused-vars调整为warn,第三段只对测试文件关闭该规则。这正是"预定义配置 + 项目覆写 + 文件子集特例"的典型三段式结构,也是extends组合模式最常见的实际用法。
底层机制:FlatConfigArray与Config类的处理流程
组合配置在运行时经历两个关键阶段(对应仓库源码):
展开与合并:配置文件导出的数组被包装成 FlatConfigArray(继承自
@eslint/config-array的ConfigArray)。其中的preprocessConfig钩子(lib/config/flat-config-array.js#L182-L202)负责把extends中的字符串与数组元素替换为真实对象后再进入规范化流程。校验与规范化:每个合并完成的配置对象交由 Config 类 处理:用
ObjectSchema(flatConfigSchema)校验键的合法性、解析language与processor、合并languageOptions,最后通过#normalizeRulesConfig与validateRulesConfig(lib/config/config.js#L608-L633)统一规则配置格式——例如把"warn"规范化为数字1,并依据规则的meta.schema校验选项。这也解释了为什么extends合并进来的规则在运行前就会被严格校验,拼写错误的规则名会直接抛出带插件提示的TypeError。
与 eslintrcextends的差异
如果你熟悉旧的 eslintrc 配置体系,需要特别注意:flat config 中extends的行为与 eslintrc 时代的"继承链 + 深层对象合并"完全不同——这里不存在递归继承的层级,只有同一数组内对象的顺序覆盖。旧式 eslintrc 的env、globals、ignorePatterns、overrides等键在 flat config 中均为非法键,见 flat-config-schema.js 中的 eslintrcKeys 清单(extends本身也在其中被列为 eslintrc 风格、应替换为 flat config 写法的键)。
组合配置的进阶实战模式
基于上述两条核心模式,可以组合出更复杂的实战写法:
(1)多个预定义配置叠加,并分别限定文件范围:
import js from "@eslint/js"; import ts from "typescript-eslint"; import { defineConfig } from "eslint/config"; export default defineConfig([ { files: ["**/*.js"], extends: ["js/recommended"], }, { files: ["**/*.ts"], extends: [...ts.configs.recommended], }, ]);(2)命名配置对象,便于排查与调试:
export default defineConfig([ { name: "project/javascript", files: ["**/*.js"], extends: ["js/recommended"], rules: { "no-unused-vars": "warn" }, }, ]);name是配置对象的可选元信息,运行eslint --print-config或使用调试工具时可以据此快速定位是哪一段配置影响了文件。
(3)配合globalIgnores组织全局忽略:
import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig([ { files: ["**/*.js"], extends: ["js/recommended"], }, globalIgnores(["dist/**", "node_modules/**"]), ]);globalIgnores()生成一个只含ignores的配置对象,其模式会对所有其他配置对象生效——这正是在 lib/config-api.js 中重新导出的辅助函数之一,可作为组合配置的补充模块。
常见陷阱与排查建议
extends元素类型错误:extends只接受字符串、配置对象或配置数组。直接传入函数、Promise等会在校验阶段被ObjectSchema拒绝,报错信息会指明非法键。- 漏声明插件:当
extends引用的字符串(如"js/recommended")所依赖的插件对象没有在当前配置对象的plugins中注册时,运行时会在 Config 类的规则查找逻辑 处抛出Key "rules": Key "xxx": Could not find plugin类错误,并提示可能的拼写或插件名。 - 覆盖顺序反了:想覆写预定义配置的规则,覆写对象必须排在
extends所在对象之后(或与extends同处一个对象、位于其后),否则会被预定义配置"反覆盖"。 - 调试手段:使用
npx eslint --print-config <file>查看某个文件最终生效的合并结果,或借助--debug输出配置解析过程,都可以快速确认组合是否正确。
小结
组合配置是 flat config 体系下日常开发最常用的能力:通过files限定作用范围、用extends注入预定义或可共享的配置对象/数组、再叠加自己的规则覆写,即可拼装出结构清晰、按文件类型差异化的完整配置。其底层由 FlatConfigArray 负责展开合并、Config 负责规范化与校验,理解这两层的求值顺序与校验时机,你就能避免绝大多数配置组合过程中的"灵异问题"。
【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考