
当 ESLint 在 2013 年首次发布时,配置系统相当简单。您可以在 .eslintrc
文件中定义您想要启用或禁用的规则。当一个文件被 lint 时,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
中拥有个人配置文件的能力时,添加了下一个复杂性层级。因此,我们添加了一个额外的检查:如果我们没有在文件位置的祖先目录中找到配置文件,那么我们会自动查找个人配置文件。
多种配置文件格式
作为重构的一部分,我们发现允许不同的配置文件格式将是微不足道的。我们可以将 JSON 格式正式化为 .eslintrc.json
,并添加对 YAML (.eslintrc.yml
或 .eslintrc.yaml
) 和 JavaScript (.eslintrc.js
) 的支持,而不是强迫每个人都使用非标准的 .eslintrc
文件。为了向后兼容,我们继续支持 .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
时,对进一步配置文件的搜索不会继续到祖先目录。这阻止了一些混乱,我们最终在 ESLint 通过旧的 --init
命令生成的配置中自动包含了 root: true
,以帮助用户以最少的困惑开始。
overrides
键
ESLint 继续收到关于更强大的项目配置方式的请求。更具体地说,有人请求从现有配置文件中提供 基于 glob 的配置。这促成了 overrides
键的创建,该键允许您进一步修改 ESLint 正在 lint 的特定文件子集的配置。这是一个示例
{
"rules": {
"quotes": ["error", "double"]
},
"overrides": [
{
"files": ["bin/*.js", "lib/*.js"],
"excludedFiles": "*.test.js",
"rules": {
"quotes": ["error", "single"]
}
}
]
}
在这种情况下,bin
和 lib
中的 JavaScript 文件更喜欢单引号而不是其他地方首选的双引号。
事实证明,带有基于 glob 的配置的 overrides
键是实现配置级联尝试做的事情的更好方法。再次,事后看来,这将是尝试删除级联的完美时机……但我们没有。而且复杂性并没有止步于此。
向 overrides
添加 extends
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 用户感到熟悉,同时大大简化配置文件设置过程。扁平配置目前还不能通过 CLI 使用,因为我们仍在继续处理错误和收集反馈,但它可供直接使用 API 的开发人员使用。我将在本系列的 第 2 部分 中讨论扁平配置的设计。