
当我们在四月发布 ESLint v9.0.0时,这是 30 个月来的第一次重大版本发布,并正式引入了新的 配置 系统。ESLint v9.0.0 还对一些规则 API进行了更改,为核心准备下一步的更新。在发布之后,我们花了大量时间创建兼容性工具、配置迁移工具以及规则 API 转换工具,以帮助生态系统迁移到 ESLint v9.0.0。所有这些工作都是我们推进 ESLint 即将到来的重大变化所必需的。
🌐 When we released ESLint v9.0.0 in April, it was the first major release in 30 months and formally introduced the new configuration system. ESLint v9.0.0 also made some rule API changes to prepare the core for what’s coming next. After the release, we spent a lot of time creating compatibility utilities, a configuration migration tool, and a rule API transform utility to help the ecosystem move to ESLint v9.0.0. All of that work was necessary for us to move on to the next significant changes coming to ESLint.
语言插件
🌐 Language plugins
两年前,TSC 决定是时候开放 ESLint,使其能够对 JavaScript 以外的语言进行 lint。实际上,ESLint 核心处理的很多事情并不是 JavaScript 特有的:查找和读取文件、加载特定文件的配置、收集规则违规、将结果输出到控制台,等等。此外,我们不断发现一些插件正在从 ESLint 中 lint 其他语言(例如 GraphQL 和 HTML),通过相同的 JavaScript 逻辑传递不同的 AST。这样既低效又容易出错,必须有更好的方法。
在2023年,语言插件RFC正式获得批准。然而,为了实现这个RFC,我们需要正式发布新的配置并进行一些规则API的更改。特别是规则API的更改,是为了区分ESLint核心所做的工作和语言插件为自定义规则提供的功能。一旦这两项更改到位,我们就开始了艰巨的任务,即重构ESLint核心,将特定于JavaScript的部分从与语言无关的部分中提取出来。
🌐 In 2023, the language plugins RFC was formally approved. In order to implement this RFC, however, we needed to officially ship the new configuration and make some rule API changes. The rule API changes, in particular, were necessary to separate what the ESLint core was doing and what a language plugin would provide to custom rules. Once those two changes were available, we started the laborious task of refactoring the ESLint core to extract the JavaScript-specific parts from the language-agnostic parts.
以下是未来的预期(没有具体时间表,因为一切都取决于贡献者的可用性):
🌐 Here’s what to expect going forward (with no specific timetable as everything is dependent upon contributor availability):
@eslint/js- 我们将慢慢把所有与 JavaScript 相关的功能移到@eslint/js包中,包括规则和文档。我们将重用现有的 Espree 仓库,并将其转换为一个包含 Espree、eslint-scope、eslint-visitor-keys以及所有核心 JavaScript 规则的 monorepo。这将使我们能够在单一仓库中处理所有与 JavaScript 相关的内容。@eslint/json- 我们的第一个语言插件将允许 ESLint 原生地检查 JSON 文件。该插件将包含解析逻辑以及所有相关的规则和文档。与@eslint/js类似,这将允许我们有一个单独的仓库专注于 JSON 检查,进一步将任何特定语言的功能从核心中移出。@eslint/markdown-eslint-plugin-markdown将被重命名为@eslint/markdown,以更好地与其他软件包保持一致。我们将增加对 Markdown 及 Markdown 特定规则的 lint 功能。
当前的计划是拥有这三个官方 ESLint 语言插件,希望它们能作为示例,促使生态系统开发更多语言插件。
🌐 The current plan is to have these three official ESLint language plugins with the hopes that they serve as examples for the ecosystem to develop more language plugins.
随着特定语言功能被提取到单独的仓库中,我们面临的任务就是完全重写核心部分。
🌐 With language-specific functionality extracted into separate repositories, that leaves us with the task of rewriting the core completely.
核心重写
🌐 Core rewrite
ESLint 仓库中的代码架构自从 11 年前 ESLint 首次创建以来没有发生显著变化。因此,积累了大量技术债务,阻碍了我们进行一些希望的更改。我们目前面临的一些问题:
🌐 The architecture of the code in the ESLint repository hasn’t changed considerably since ESLint was first created 11 years ago. As a result, there is a lot of technical debt that has accumulated and prevents us from making some of the changes we’d like to make. Some of the problems we currently face:
- 同步核心逻辑。
Linter类是完全同步的,这意味着我们无法支持异步规则或异步解析,这是我们经常收到的两个功能请求。我们需要么添加一个可以异步工作的第二个类,要么为Linter添加可以异步工作的备用方法。这两种方案看起来都需要大量工作,实际上是重写核心的很大一部分内容。 - 有限的 API。 公共 API 是有限的,因为 ESLint 在一开始并不是打算作为一个 API 来使用的。最初的
linter对象只是为了让浏览器演示可用。后来它被Linter类取代,再后来,又出现了一个ESLint类,以完全模仿命令行接口。当我们想要公开新的功能时,它必须存在于这两个听起来相似的 API 之一,不幸的是,主要的决策点是该 API 是否需要在浏览器中可用。如果需要,它就必须在Linter中,否则就必须在ESLint中。这既令人困惑,也难以长期维持。 - 缺乏类型检查。 我们一直希望在仓库中加入类型检查以帮助捕捉潜在问题,但过去尝试这样做需要大量的工作和协调。最终,我们得出结论,认为这不值得付出这样的努力。
- 困在 CommonJS 中。 类似于类型检查的情况,虽然有人希望将代码库转换为 ESM,但所需的工作量是非常大的。我们可能会花大量时间进行转换,最终却只得到一个等效的功能集。
在考虑了是持续逐步修改现有核心还是从头开始的选项后,我们决定是时候从头开始了。虽然我们坚信完全重写存在重大风险,但经过11年,我们可以坦诚地说,我们已经充分利用了最初的架构。
🌐 After thinking through the options of continually to incrementally change the existing core or starting from scratch, we decided that it was time to start from scratch. While we firmly believe that a complete rewrite represents a significant risk, after 11 years, we can honestly say that we got the most out of the initial architecture.
展望未来,你可以期待以下内容:
🌐 Going forward, here’s what you can expect:
- 一个新仓库。
eslint/rewrite仓库是我们与语言无关的工作的全新归宿。它是一个单一仓库,我们将在这里管理所有以某种方式直接与核心相关的包。 - 现代包。
eslint/rewrite中的每个包都将符合现代标准,发布 ESM 入口点以及类型定义。在可能的情况下,我们也会发布 CommonJS 入口点。所有包都会在发布时提供到 npm 和 JSR(如适用)的来源信息。 - 一个与运行时无关的核心。
@eslint/core包将包含用于 ESLint 的与运行时无关的 API 以及核心的类型定义。随着我们设计新的 API,我们将逐步构建此包,从eslint包中提取功能部分。随着每年涌现出更多的 JavaScript 运行时,我们认为一个与运行时无关的核心变得越来越重要。 - 可组合的 API。 新的 API 不会将所有功能打包到一两个类中,而是可组合的,拥有许多为特定用途设计、可以一起使用的类。类将遵循单一职责原则,使在集成中修改和自定义 ESLint 更加容易。
- 一个新的命令行工具(CLI)。 我们将从头开始重写
@eslint/node包中的 CLI。我们将从最常见的使用场景开始,并重新评估每个标志,以确保它对于语言无关的核心仍然有意义。使用不同的包名允许我们在不干扰eslint包使用的情况下实验新的 CLI。该包还将导出额外的 Node.js 特定 API。 - 并行开发。 我们将继续并行维护
eslint和重写版本,这样我们就不会为了全新的东西而牺牲现有的包。目标是让eslint包逐渐变小,并利用@eslint/core中包含的 API,从而尽可能减少重复。
结论
🌐 Conclusion
我们对于 ESLint 的这一未来方向感到非常兴奋,因为我们认为这是 ESLint 演化的下一个逻辑步骤。将 ESLint 转变为一个语言无关的 linter,任何人都可以为其编写插件,这将通过减少单个项目所需的 lint 工具和编辑器扩展的数量来简化开发。此外,将我们的核心重写为更具可组合性的 API 可以实现更容易和更可定制的集成,并且拥有我们自己的类型定义以确保与 API 的兼容性。
🌐 We are very excited for this future direction of ESLint as we think this represents the next logical step in ESLint’s evolution. Turning ESLint into a language-agnostic linter that anyone can write plugins for will simplify development by reducing the number of linting tools and editor extensions any one project needs. Further, rewriting our core into a more composable API allows easier and more customizable integrations, along with owning our own type definitions to ensure compatibility with the APIs.
ESLint 通过其现有架构已经走过了 11 年,我们希望这些变化能让我们继续走过接下来的 11 年。
🌐 ESLint has made it through 11 years with its current architecture, and we hope that these changes will get us through the next 11 years.
