ESLint 在 2013 年首次发布时,其配置系统相当简单。您可以在 .eslintrc
文件中定义要启用或禁用的规则。当对文件进行代码检查时,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 默认停止安装对等依赖项时,所有依赖此行为的可共享配置都停止了正常工作。有一个 长期存在的 issue 请求允许可共享配置直接使用依赖项,但 eslintrc 的架构不允许这样做。我们基本上必须在 ESLint 内部重新创建整个 require()
功能才能解决可共享配置的设计方式。我们建议可共享配置创建一个安装后脚本以安装其对等依赖项。这绝不是理想的解决方案。
我们添加了 --resolve-plugins-relative-to
命令行选项以尝试解决此问题,但这还不够。在我们 Discord #help 频道 中,最常见的求助请求与配置文件中插件的错误解析有关。
npm 最终在 v7 中恢复了默认安装对等依赖项,但此时 ESLint 生态系统已经遭受了损害。
root
键
随着时间的推移,配置级联继续给用户带来问题。最常见的是,人们没有意识到他们在他们正在使用的项目的祖先目录中有一个配置文件。这会导致混淆,因为他们会收到他们似乎没有配置的 ESLint 设置。
为了帮助解决此问题,我们为配置文件引入了 root
属性。当在配置中指定 root: true
时,对更多配置文件的搜索不会继续到祖先目录。这阻止了一些混淆,并且我们最终自动在 ESLint 通过旧的 --init
命令生成的配置中包含 root: true
,以帮助用户从最少的混淆开始。
overrides
键
ESLint 继续收到更多关于如何更强大地配置项目的请求。更具体地说,有请求在现有配置文件中提供 基于 glob 的配置。这导致创建了一个 overrides
键,它允许您进一步修改 ESLint 正在检查的特定文件子集的配置。这是一个示例
{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["bin/*.js", "lib/*.js"],
"excludedFiles": "*.test.js",
"rules": {
"quotes": ["error", "single"]
}
}
]
}
在这种情况下,bin
和 lib
中的 JavaScript 文件更喜欢单引号而不是其他地方更喜欢的双引号。
具有基于 glob 的配置的 overrides
键结果证明是完成配置级联试图做的事情的更好方法。再次强调,事后看来,这本来是尝试删除级联的绝佳时机……但我们没有这样做。而且复杂性并没有就此止步。
将 extends
添加到 overrides
中
eslintrc 开发的最后一步是将 extends
键添加到 overrides
配置中,允许用户将其他配置数据注入到基于 glob 的配置对象中,如下所示
{
"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 用户感到熟悉,同时极大地简化配置文件的设置过程。由于我们仍在修复 bug 并收集反馈,因此扁平化配置尚未通过 CLI 提供,但直接使用 API 的开发者可以使用它。我将在本系列的 第二部分 中讨论扁平化配置的设计。