当 ESLint 在 2013 年首次发布时,配置系统相当简单。你可以在 .eslintrc
文件中定义要启用或禁用的规则。当一个文件被 linted 时,ESLint 首先会在该文件所在的目录中查找 .eslintrc
文件,然后向上遍历目录层次结构,直到到达根目录,并将沿途找到的所有 .eslintrc
文件中的配置合并。我们称这个系统为配置级联,它允许你轻松地覆盖特定目录的规则,而 JSHint 就无法做到这一点。你也可以在 package.json
中的 eslintConfig
键中添加更多配置。
然而,多年来,配置系统发展成一个笨拙的混乱。这就是为什么我在 2019 年提议创建一个新的配置系统,以简化在 JavaScript 项目日益复杂的世界中配置 ESLint 的方式。新配置系统的一大部分已经合并到主分支中,因此现在是时候开始学习如何配置未来的 ESLint 了。但为了做到这一点,回顾一下我们是如何走到现在的状态是有帮助的。
导致最大复杂性的增量变化
回顾当前配置系统(称为 eslintrc 系统)是如何演变的,我们发现每一步在当时都很有逻辑。ESLint 一直采用增量开发方法,我们寻求改进现有的方法,而不是抛弃重来。eslintrc 系统也不例外。
extends
键
对 eslintrc 的第一个重大改变是引入了 extends
键。extends
键(借鉴了 JSHint 的想法)允许用户导入另一个配置,然后对其进行扩充,例如
{
"extends": ["./other-config.json"],
"rules": {
"semi": "warn"
}
}
假设 ./other-config.json
包含一些配置数据,你可以导入它,然后在其基础上添加自己的 rules
设置。这对 ESLint 来说是一个巨大的进步,原因有很多。
首先,extends
实际上先于可通过 npm 分发的可共享配置的概念。正是通过实现 extends
,我们才意识到可共享配置是可能的。extends
中指定的文件通过 Node.js 的 require()
函数加载,因此 Node.js 可以通过该函数加载的任何内容都可以用作要从中扩展的配置。
其次,extends
允许我们实现 eslint:recommended
,这是一组我们认为每个人都应该启用的规则。最初,ESLint 默认启用了几个规则,但这对用户来说成了负担。因此,我们切换到默认情况下所有规则都关闭,这对没有看到任何规则的新用户来说也很令人困惑。添加 eslint:recommended
让我们明确表示,你包含了一些我们推荐的规则,但如果你不想使用,可以删除它们。
事后看来,如果我们当时多考虑一下,我们就会在此时移除配置级联。引入 extends
使得级联的许多用例都得以实现,而同时保留两者却会导致混乱,我们将在未来几年内努力解决这个问题。
个人配置
当人们要求我们添加在 ~/.eslintrc
中拥有个人配置文件的功能时,增加了下一层复杂性。因此,我们添加了一个额外的检查:如果我们在文件位置的祖先中没有找到配置文件,那么我们就会自动查找个人配置文件。
多个配置文件格式
作为重构的一部分,我们发现允许不同的配置文件格式将是微不足道的。与其强迫每个人都使用非标准的 .eslintrc
文件,我们可以将 JSON 格式正式化为 .eslintrc.json
,并添加对 YAML(.eslintrc.yml
或 .eslintrc.yaml
)和 JavaScript(.eslintrc.js
)的支持。为了向后兼容,我们继续支持 .eslintrc
,因为它只需要很少的代码来维护。
事后看来,这也不是一个好主意。添加 JavaScript 配置文件格式在它和非 JS 格式之间造成了不兼容:任何 JavaScript 对象都可以传递到配置中并在规则中使用。由于我们没有正确地验证配置以完全匹配非 JS 格式,最终导致一些规则需要传递正则表达式对象才能正确配置。虽然这可以在 JS 配置文件格式中工作,但规则无法在非 JS 配置文件中正确配置。不幸的是,由于插件规则依赖于此功能,我们无法回过头去修复它,而不会造成破坏。
可共享配置和依赖项
也许我们早期面临的最大问题是 npm 决定 在 v3 中停止安装对等依赖。在此之前,我们建议可共享配置将其依赖的任何插件作为对等依赖,而不是常规依赖项。这是 extends
实现方式的一个怪癖:使用 require()
。
由于可共享配置是数据唯独的,不能直接引用 Node.js 依赖项,因此 require()
不会自动将直接依赖项加载到 ESLint 能够解析它们的路径中。另一方面,对等依赖项通过使用 require()
完美地工作,因为它们安装在正常包查找有效的路径中。
当 npm v3 默认停止安装对等依赖项时,所有依赖于此行为的共享配置都停止正常工作。有一个 长期存在的问题要求允许可共享配置直接使用依赖项,但 eslintrc 的架构不允许这样做。我们基本上需要在 ESLint 内部重新创建整个 require()
功能,以解决可共享配置的设计方式。我们建议可共享配置创建一个 post-install 脚本,用于安装其对等依赖项。这绝对不是理想的方案。
我们添加了 --resolve-plugins-relative-to
命令行选项来尝试解决这个问题,但这还不够。我们 Discord #help 频道 中最常见的求助请求与从配置文件中不正确解析插件有关。
npm 最终在 v7 中恢复了默认安装对等依赖项的行为,但此时 ESLint 生态系统已经受到损害。
root
键
随着时间的推移,配置级联继续给用户带来问题。最常见的情况是,人们没有意识到自己在项目工作的祖先目录中有一个配置文件。这会导致混淆,因为他们会获得看似没有配置的 ESLint 设置。
为了解决这个问题,我们为配置文件引入了 root
属性。当在配置中指定 root: true
时,对更多配置文件的搜索不会继续进行到祖先目录。这在一定程度上减少了混乱,我们最终在通过旧的 --init
命令生成的配置中自动包含 root: true
,以帮助用户从最少的混乱开始。
overrides
键
ESLint 继续收到更多强大方法来配置其项目的请求。更具体地说,有请求提供 基于 globs 的配置,从现有的配置文件中进行。这导致了 overrides
键的创建,它允许你进一步修改 ESLint 正在 lint 的特定文件子集的配置。这是一个例子
{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["bin/*.js", "lib/*.js"],
"excludedFiles": "*.test.js",
"rules": {
"quotes": ["error", "single"]
}
}
]
}
在这种情况下,bin
和 lib
中的 JavaScript 文件更喜欢单引号,而不是其他地方更喜欢的双引号。
具有基于 globs 的配置的 overrides
键最终成为完成配置级联尝试做的事情的更好的方法。再次回顾,这本应是尝试移除级联的最佳时机……但我们没有这样做。复杂性并没有就此停止。
在 overrides
中添加 extends
eslintrc 开发的最后一步是将 extends
键添加到 overrides
配置中,允许用户将额外的配置数据注入到基于 globs 的配置对象中,如下所示
{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["bin/*.js", "lib/*.js"],
"excludedFiles": "*.test.js",
"extends": ["eslint:recommended"],
"rules": {
"quotes": ["error", "single"]
}
}
]
}
此添加也带来了很多额外的复杂性,因为我们必须弄清楚如何合并两个不同配置之间的 glob 模式。最终结果是,overrides
配置中的 extends
将使用 AND 运算符来合并 files
和 excludedFiles
。如果你不确定这到底意味着什么,你并不孤单。即使对我们来说,这也很混乱。
简化的需求
大约在 2019 年新年左右,我越来越担心 eslintrc 系统的复杂性。我们收到了越来越多的关于与加载配置文件有关的奇怪错误消息的问题,这些配置文件找不到其他配置文件或插件。此外,团队成员集体开始害怕触碰与配置系统相关的任何内容。没有人真正理解为任何给定文件计算最终配置的所有不同排列。我们陷入了许多软件项目都会遇到的陷阱:我们不断添加新功能,却没有退一步从整体上审视问题(和解决方案)。这导致了我们代码库中几乎无法维护的部分。
正是在这个时候,我做了一个思想实验:如果我从头开始,今天,了解我现在对 ESLint 的所有知识,配置系统会是什么样子?随之而来的是 ESLint 历史上最具争议的 RFC 提案。当时,团队几乎一半的人希望抛弃 eslintrc 从头开始,而另一半的人则认为 eslintrc 可以通过更多迭代来保存。最终,经过 18 个月的修订和辩论,我们决定是时候开始一个全新的配置系统了,这个系统是根据今天的现实构建的。
前进的道路
现在是 2022 年,我们终于发布了新配置系统的第一个实现版本,它在 v8.21.0 中发布。这个新系统,我们给它起了个绰号“扁平配置”,旨在让现有的 ESLint 用户感到熟悉,同时大幅简化设置配置文件的过程。扁平配置尚未通过 CLI 提供,因为我们仍在继续处理错误并收集反馈,但它可供直接使用 API 的开发人员使用。我将在本系列的 第二部分 中讨论扁平配置的设计。