
当 ESLint v9.0.0 于 2024 年 4 月发布 时,我们默认启用了新的配置系统。我们知道随着生态系统的转换,会有一段时期的挑战。在随后的几个月里,我们审查了反馈并发布了 兼容性实用程序 和 配置迁移器,以帮助缓解一些过渡期的痛苦。 慢慢地,我们开始看到人们迁移到新的配置系统,因此我们等待更多的反馈。在当时,我们不确定抱怨更多地与过渡有关,还是与配置系统格式本身有关。
到 2024 年底,我们持续听到相同的三个方面的反馈
- 与 TypeScript 一起使用时很笨拙
- 扩展其他配置既困难又令人沮丧
- 全局忽略令人困惑
考虑到这三方面的反馈,我们回到了绘图板,看看如何改进新的配置系统。
为 ESLint 引入 defineConfig()
我们最近的项目之一是将类型定义与 eslint
包捆绑在一起。我们从 @types/eslint
中的类型开始,以获得最大的兼容性,然后从那里改进类型。我们还默认启用了 eslint.config.ts
文件,以允许类型安全的配置文件。问题仍然存在:我们如何轻松地允许用户将正确的类型应用于他们的配置? 答案是像 Rollup、Astro、Vite 和 Nuxt 等其他工具所做的那样:创建一个 defineConfig()
函数。
defineConfig()
函数从 eslint/config
入口点导出,可以像这样使用
// eslint.config.js
import { defineConfig } from "eslint/config";
export default defineConfig([
{
files: ["src/**/*.js"],
rules: {
semi: "error"
}
}
]);
您可以通过 defineConfig()
的类型定义获得类型安全,从而更容易使用 TypeScript 确保配置的正确性。
defineConfig()
函数还会自动扁平化其所有参数,这意味着您可以嵌套对象和数组
// eslint.config.js
import { defineConfig } from "eslint/config";
export default defineConfig(
{
files: ["src/**/*.js"],
rules: {
semi: "error"
}
},
[
{
files: ["tests/**/*.js"],
languageOptions: {
globals: {
it: true,
describe: true
}
}
},
{
files: ["bin/*.js"],
rules: {
"no-console": "off"
}
}
]
);
这种扁平化行为旨在消除我们听到的关于在新配置系统中使用展开运算符 (...
) 的一些困惑。 使用 defineConfig()
,您永远不需要使用展开运算符(除非您真的想使用!)。
带回 extends
扁平配置的最初理论是 extends
只是配置对象的一维数组的抽象,因此如果我们让人访问该一维数组,则不需要 extends
。 虽然许多人喜欢使用 JavaScript 混合和匹配配置的自由,但事实证明,许多用户也发现扩展其他配置令人沮丧。 一个尖锐的批评是,他们从不知道如何扩展另一个配置,因为有些是对象,有些是数组,而且并非所有插件都以相同的方式公开其扁平配置。 这是一个例子
import js from "@eslint/js";
import tailwind from "eslint-plugin-tailwindcss";
import reactPlugin from "eslint-plugin-react";
import eslintPluginImportX from "eslint-plugin-import-x";
export default [
js.configs.recommended,
...tailwind.configs["flat/recommended"],
...reactPlugin.configs.flat.recommended,
eslintPluginImportX.flatConfigs.recommended,
];
在这个例子中,我们有四种不同的方法来访问和合并来自其他插件的配置。 虽然我们给了 ESLint 用户很大的权力,但这也让生态系统中出现了一个狂野西部,插件都在做不同的事情。
当尝试将配置应用于文件的子集时,这个问题被放大了。 这样做需要很多额外的语法,这些语法并不总是很清楚
// eslint.config.js
import exampleConfigs from "eslint-config-example";
export default [
// apply an array config to a subset of files
...exampleConfigs.map(config => ({
...config,
files: ["**/src/safe/*.js"]
})),
// your modifications
{
rules: {
"no-unused-vars": "warn"
}
}
];
这种方法对于 JavaScript 初学者来说难以理解,对于必须弄清楚如何将此技术应用于大型配置文件的经验丰富的开发人员来说也令人沮丧。
最终,我们意识到解决这组问题的最佳方法是重新引入 extends
。 defineConfig()
函数允许您在任何对象中指定一个 extends
数组,并且该数组可以包含对象、数组或字符串(对于遵循 推荐方法 的插件配置)。 这允许您以更一致的方式重写配置文件
import { defineConfig } from "eslint/config";
import js from "@eslint/js";
import tailwind from "eslint-plugin-tailwindcss";
import reactPlugin from "eslint-plugin-react";
import eslintPluginImportX from "eslint-plugin-import-x";
import exampleConfigs from "eslint-config-example";
export default defineConfig(
{
files: ["**/*.js"],
plugins: {
js,
tailwind
},
extends: [
"js/recommended", // load from js.configs.recommended
"tailwind/flat/recommended", // load from tailwind.configs['flat/recommended']
reactPlugin.configs.flat.recommended,
eslintPluginImportX.flatConfigs.recommended,
]
},
// apply an array config to a subset of files
{
files: ["**/src/safe/*.js"],
extends: [exampleConfigs]
},
// your modifications
{
rules: {
"no-unused-vars": "warn"
}
}
);
这种方法使您可以减少担心要扩展的配置是对象还是数组,并且还可以更清楚地了解哪些配置应用于哪些文件。
您可以在 配置文件文档 中阅读更多关于 defineConfig()
函数和 extends
的信息。
引入 globalIgnores()
助手函数
我们收到的另一个反馈是 ignores
键的行为令人困惑。 在某些情况下,它充当全局忽略(就像忽略文件一样 - 完全忽略它匹配的所有内容),而在其他情况下,它充当“排除”。 以下是一些示例
export default [
// global ignores
{
ignores: ["dist", "build"]
},
// local ignores - match everything BUT tests/*.js
{
ignores: ["tests/*.js"],
rules: {
"no-console": "error"
}
}
];
当 ignores
单独在一个对象中时,它充当全局忽略;当对象中有其他内容时,它充当局部忽略。 事实证明,在不破坏大量现有配置的情况下更改此行为是困难的,因此我们选择添加一个新的 globalIgnores()
助手函数来使行为明确
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
// global ignores
globalIgnores(["dist", "build"]),
// local ignores - match everything BUT tests/*.js
{
ignores: ["tests/*.js"],
rules: {
"no-console": "error"
}
}
]);
您可以在 忽略文件文档 中阅读更多关于 globalIgnores()
函数的信息
支持旧版本的 ESLint
我们意识到有很多用户使用旧版本的 ESLint,他们可能无法立即升级以获得 defineConfig()
和 globalIgnores()
的好处,这就是为什么我们还在一个单独的 @eslint/config-helpers
包中发布了这些助手函数。 此包可以与任何支持扁平配置的 ESLint 版本一起使用。 只需确保从 @eslint/config-helpers
而不是 eslint/config
导入 defineConfig()
和 globalIgnores()
,您就可以享受相同的功能。
结论
ESLint 扁平配置系统的演变代表了我们致力于根据真实世界的反馈不断改进开发者体验的承诺。 通过引入 defineConfig()
,我们使编写类型安全的配置变得更容易,同时也简化了嵌套配置的处理方式。 extends
的重新引入带回了一种熟悉且强大的配置组合方式,解决了用户报告的最常见的痛点之一。 随着 globalIgnores()
助手函数的添加,我们通过使全局忽略模式更加明确,澄清了配置系统最令人困惑的方面之一。 总之,这些更改创建了更直观和用户友好的配置体验,同时保持了扁平配置系统的强大功能和灵活性。 对于尚未准备好升级到最新版本 ESLint 的团队,我们已确保通过单独的 @eslint/config-helpers
包提供这些改进。
当我们继续改进 ESLint 时,我们仍然致力于平衡创新与实用性,始终将用户的需求放在我们开发决策的最前沿。 我们鼓励您尝试这些新功能,并通过我们的 GitHub 讨论 或 Discord 服务器 与我们分享您的反馈。