使用 extends 的演变平面配置

你的 eslint.config.js 文件现在可以使用 extends 来简化你的配置。

ESLint v9.0.0 发布 于 2024 年 4 月时,我们默认启用了新的配置系统。我们知道在生态系统切换过程中会有一段挑战期。在接下来的几个月里,我们审查了反馈,并发布了 兼容性工具配置迁移工具 来帮助缓解一些过渡期的不便。慢慢地,我们开始看到人们迁移到新的配置系统,因此我们等待更多反馈。那时我们还不确定这些抱怨是更多地与过渡有关,还是与配置系统格式本身有关。

🌐 When ESLint v9.0.0 was released in April 2024, we enabled the new configuration system by default. We knew that there would be a period of challenges as the ecosystem switched over. In the ensuing months, we reviewed feedback and released compatibility utilities and the configuration migrator to help ease some of the transitional pain. Slowly, we started to see people migrating to the new configuration system and so we waited for more feedback. We weren’t sure at that point whether the complaints were more related to the transition or to the configuration system format itself.

到2024年底,我们经常听到同样的三条反馈意见:

🌐 Towards the end of 2024, we were consistently hearing the same three pieces of feedback:

  1. 在 TypeScript 中使用起来很笨拙
  2. 扩展其他配置是困难且令人沮丧的
  3. 全球忽略令人困惑

考虑到这三条反馈意见,我们回到起点,看看如何改进新的配置系统。

🌐 With these three pieces of feedback in mind, we went back to the drawing board to see how we could evolve the new configuration system.

为 ESLint 推出 defineConfig()

🌐 Introducing defineConfig() for ESLint

我们最近的一个项目是将类型定义与 eslint 包打包在一起。我们首先使用 @types/eslint 的类型以获得最大的兼容性,然后在此基础上发展类型。我们还默认启用了 eslint.config.ts 文件,以支持类型安全的配置文件。问题仍然存在:我们如何轻松地让用户将正确的类型应用到他们的配置中?答案是采用其他工具如 RollupAstroViteNuxt 的做法:创建一个 defineConfig() 函数。

🌐 One of our recent projects was to bundle type definitions with the eslint package. We started with the types from @types/eslint for maximum compatibility and then evolved the types from there. We also enabled eslint.config.ts files by default to allow type-safe configuration files. The question still remained: how can we easily allow users to apply the correct types to their configurations? The answer was to do what other tools like Rollup, Astro, Vite, and Nuxt did: create a defineConfig() function.

defineConfig() 函数是从 eslint/config 入口点导出的,可以这样使用:

🌐 The defineConfig() function is exported from the eslint/config entrypoint and can be used like this:

// eslint.config.js
import { defineConfig } from "eslint/config";

export default defineConfig([
    {
        files: ["src/**/*.js"],
        rules: {
            semi: "error"
        }
    }
]);

通过 defineConfig() 的类型定义,你可以获得类型安全,从而更容易使用 TypeScript 确保配置的正确性。

🌐 You get type safety through the type definitions for defineConfig(), making it easier to ensure the correctness of your configuration using TypeScript.

defineConfig() 函数还会自动将其所有参数展平,这意味着你可以嵌套对象和数组:

🌐 The defineConfig() function also automatically flattens all of its arguments, meaning you can nest objects and arrays:

// 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(),你根本不需要使用扩展运算符(除非你真的想用!)。

🌐 This flattening behavior is designed to eliminate some of the confusion we heard around the use of the spread operator (...) with the new configuration system. With defineConfig(), you never need to use the spread operator (unless you really want to!).

带回 extends

🌐 Bringing back extends

平铺配置的最初理论是,extends 只是配置对象一维数组的一个抽象,因此如果我们让人们访问那个一维数组,它就不再需要。虽然许多人喜欢使用 JavaScript 自由地混合和匹配配置,但事实证明,许多用户也发现扩展其他配置令人沮丧。一个直接的批评是,他们从来不知道如何扩展另一个配置,因为有些是对象,有些是数组,而且并非所有插件以相同的方式暴露它们的平铺配置。这里有一个例子:

🌐 The original theory of flat config was that extends was just an abstraction over a one-dimensional array of configuration objects, and was therefore not needed if we gave people access to that one-dimensional array. While many enjoyed the freedom to mix and match configurations using JavaScript, it turned out that a lot of users also found extending other configurations frustrating. One pointed criticism is that they never knew how to extend another configuration because some were objects, some were arrays, and not all plugins exposed their flat configs the same way. Here’s an example:

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 用户很大的权力,但这也在生态系统中留下了一片混乱状态,插件各自为政,做着不同的事情。

🌐 In this example, we have four different ways to access and merge configs from other plugins. While we had given a lot of power to ESLint users, it also left a wild west in the ecosystem where plugins were all doing different things.

当尝试将配置仅应用于文件的子集时,这个问题被放大了。这样做需要很多额外的语法,而这些语法并不总是清楚的:

🌐 This problem was amplified when trying to apply a configuration to just a subset of files. Doing so required a lot of extra syntax that wasn’t always clear:

// 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 初学者来说很难理解,对于需要弄清如何将这种技术应用到大型配置文件的有经验的开发者来说也很令人沮丧。

🌐 This approach was difficult for JavaScript beginners to understand and frustrating for experienced developers who had to figure out how to apply this technique to large configuration files.

最终,我们意识到解决这一系列问题的最佳方法是重新引入 extendsdefineConfig() 函数允许你在任何对象中指定一个 extends 数组,该数组可以包含对象、数组或字符串(用于遵循 推荐方法 的插件配置)。这允许你以更一致的方式重写你的配置文件:

🌐 Ultimately, we realized that the best way to solve this set of problems was to reintroduce extends. The defineConfig() function allows you to specify an extends array in any object, and that array can contain objects, arrays, or strings (for plugin configs that follow the recommended approach). This allows you to rewrite your configuration file in a more consistent way:

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"
        }
    }
);

这种方法让你不必过多担心你想扩展的配置是对象还是数组,同时也更清楚哪些配置适用于哪些文件。

🌐 This approach allows you to worry less about whether a configuration you want to extend is an object or an array, and also makes it clearer which configurations apply to which files.

你可以在配置文件文档中阅读更多关于defineConfig()函数和extends的信息。

🌐 You can read more about the defineConfig() function and extends in the configuration files documentation.

介绍 globalIgnores() 助手

🌐 Introducing the globalIgnores() helper

我们收到的另一条反馈是,ignores 键的行为令人困惑。在某些情况下,它表现得像全局忽略(就像一个忽略文件——完全忽略它匹配的所有内容),而在其他情况下,它又表现得像“排除”。以下是一些例子:

🌐 Another piece of feedback we received is that the behavior of the ignores key is confusing. In some cases it acts as a global ignores (like an ignore file – completely ignoring everything it matches) while other times it acts like “excludes”. Here are some examples:

export default [

    // global ignores
    {
        ignores: ["dist", "build"]
    },

    // local ignores - match everything BUT tests/*.js
    {
        ignores: ["tests/*.js"],
        rules: {
            "no-console": "error"
        }
    }
];

ignores 独自存在于一个对象中时,它会作为全局忽略;当对象中还有其他内容时,它会作为局部忽略。事实证明,要在不破坏大量现有配置的情况下修改这种行为很困难,因此我们选择添加一个新的 globalIgnores() 辅助函数来使该行为明确化:

🌐 When ignores is in an object by itself, then it acts as global ignores; when there is something else in the object, then it acts as local ignores. It proved to be difficult to make changes to this behavior without breaking a lot of existing configurations, so we opted to add a new globalIgnores() helper function to make the behavior explicit:

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()函数的内容

🌐 You can read more about the globalIgnores() function in the ignoring files documentation

支持较旧的 ESLint 版本

🌐 Support for older ESLint versions

我们意识到有很多使用旧版本 ESLint 的用户可能无法立即升级以获得 defineConfig()globalIgnores() 的好处,这就是为什么我们也将这些辅助函数发布在一个单独的 @eslint/config-helpers 包中。只要 ESLint 版本支持平面配置,就可以使用这个包。只需确保从 @eslint/config-helpers 而不是 eslint/config 导入 defineConfig()globalIgnores(),你就可以享受相同的功能。

🌐 We realize that there are a lot of users on older versions of ESLint who may not be able to upgrade immediately to get the benefits of defineConfig() and globalIgnores(), and that’s why we’ve also published these helper functions in a separate @eslint/config-helpers package. This package can be used with any ESLint version that supports flat config. Just make sure to import defineConfig() and globalIgnores() from @eslint/config-helpers instead of eslint/config and you can enjoy the same functionality.

结论

🌐 Conclusion

ESLint 扁平配置系统的演进体现了我们基于实际反馈持续改善开发者体验的承诺。通过引入 defineConfig(),我们使编写类型安全的配置更加容易,同时也简化了嵌套配置的处理方式。extends 的重新引入带回了一种熟悉且强大的配置组合方式,解决了用户报告的最常见痛点之一。通过增加 globalIgnores() 辅助工具,我们澄清了配置系统中最令人困惑的一个方面,使全局忽略模式更加明确。这些变化共同创造了一个更直观、更用户友好的配置体验,同时保持了扁平配置系统的强大性和灵活性。对于尚未准备升级到最新版本 ESLint 的团队,我们确保通过单独的 @eslint/config-helpers 软件包也能获得这些改进。

🌐 The evolution of ESLint’s flat config system represents our commitment to continuously improving the developer experience based on real-world feedback. By introducing defineConfig(), we’ve made it easier to write type-safe configurations while also simplifying the way nested configurations are handled. The reintroduction of extends brings back a familiar and powerful way to compose configurations, addressing one of the most common pain points reported by our users. With the addition of the globalIgnores() helper, we’ve clarified one of the most confusing aspects of the configuration system by making global ignore patterns more explicit. Together, these changes create a more intuitive and user-friendly configuration experience that maintains the power and flexibility of the flat config system. For teams not yet ready to upgrade to the latest version of ESLint, we’ve ensured these improvements are available through the separate @eslint/config-helpers package.

随着我们继续发展 ESLint,我们仍然致力于在创新与实用性之间保持平衡,始终将用户需求放在开发决策的前沿。我们鼓励你尝试这些新功能,并通过我们的 GitHub 讨论Discord 服务器 与我们分享你的反馈。

🌐 As we continue to evolve ESLint, we remain committed to balancing innovation with practicality, always keeping our users’ needs at the forefront of our development decisions. We encourage you to try these new features and share your feedback with us through our GitHub discussions or Discord server.

最新的 ESLint 新闻、案例研究、教程和资源。

ESLint v10.3.0 发布
1 min read

ESLint v10.3.0 发布

我们刚刚发布了 ESLint v10.3.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。

ESLint v10.2.1 发布
1 min read

ESLint v10.2.1 发布

我们刚刚发布了 ESLint v10.2.1,这是 ESLint 的一个补丁版本升级。本次发布修复了上一版本中发现的几个错误。

ESLint v10.2.0 发布
2 min read

ESLint v10.2.0 发布

我们刚刚发布了 ESLint v10.2.0,这是 ESLint 的一次小版本升级。此版本添加了一些新功能,并修复了上一版本中发现的几个错误。