ESLint 9.0 配置打平(flat)改造
ESlint 升级到 9.0 之后配置文件名由.eslintrc.js 更改为 eslint.config.js ,并且配置项由原本的嵌套对象结构更改为了打平数组结构,废弃了原本的 extends、override 等组合式字段。姑且称这种新模式为 flat mode。
https://eslint.org/docs/latest/use/configure/configuration-files
之前写过一篇文章 ESLint 配置最佳实践,内容包括 eslint 的语言配置、与 prettier 的协同、IDE 的格式化能力等。来到 9.0 之后配置大改,因此单独更新一文专门记录配置的调整。
前置的思考
虽然我是「极左派」(会及时把软件或者系统升级到最新以尝鲜),但对于依赖包而言,在真正动手之前还是需要改动的 ROI 有几分底气。
关于性能
9.0 之后 ESlint 的配置检索只限定于指定名称的文件,不再从 package.json等内部读取。所以配置性能读取可以说有质的提升。包括 extends一些隐晦的别名写法实际上也会降低配置文件路径的精确度,而需要多次遍历。
关于工程集成性
工程集成性其实分为两个方面:多语种和复用性。
在这之前可以说 ESlint 是专为 Javascript 设计的。一方面 Javascript 本身比较能“整活”,ES5、ES6 等语法迭代太快都快成两种语言了,更别说当今又是 Typescript 的天下,混合着 React TSX 等多种框架特有语法简直欲仙欲死; 另一方面对于工程而言 JSON、YAML、SVG、CSS 等多种格式的文件也有了不同程度的 Lint 要求。
在大型的 Monorepo 工程下,不同类型的项目(webApp、node、unit test)等不同性质的项目对 Lint 的要求也有所不同。 extends 和 override配置是“继承覆盖”的思路,相对繁琐的多。
各类插件的兼容性
自己写 rule 是不可能自己写的,用 ESlint 还是以配置市面上已有的插件为主。
那么问题来了,几个主流插件是否支持扁平化配置呢?
✅ eslint-plugin-prettier
Prettier 作为代码风格的基石,充满了老一辈的艺术家从容,从 5.1.0 版本起 eslint-plugin-prettier 包中增加一个 recommended 导出支持扁平配置

别忘记了使用它需要安装 prettier, eslint-config-prettier, eslint-plugin-prettier 三个包
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
export default [
eslintPluginPrettierRecommended,
{
rules: {
'prettier/prettier': [
// custom rules
]
}
}
]❌ @typescript-eslint/eslint-plugin
https://typescript-eslint.io/getting-started/legacy-eslint-setup
使用 @typescript-eslint/parser 直接配置 parser 的时代已经过去了,parser 等语言相关的配置项都被收到了 languageOptions 里面。所以 @typescript-eslint/parser 搭配 @typescript-eslint/eslint-plugin 的配置方式已经不兼容。
✅ typescript-eslint@v8
早在 v7 版本typescript-eslint 就开始有意收紧各类依赖的开放程度来改善各种版本配置的兼容性,在官方博客中的描述为「Into the Future」。当 ESlint 迈向 9.0 时,v8 版本的 typescript-eslint 用一个 breaking change 告别了过去繁琐且充满不确定性的配置。
至此 Typescript 的 ESlint 配置可以简化为:
import eslintJs from '@eslint/js';
import tsEslint from 'typescript-eslint';
export default [
eslintJs.configs.recommended,
...tsEslint.configs.recommended,
]✅ eslint-plugin-import-x 与 eslint-plugin-import
截止到目前两者都已经支持了 ESlint 9。
要吐槽的是 eslint-plugin-import 动作要慢的很多,可以看到相关的 issues 在 23 年就已经提出,而 eslint-plugin-import 在 24 年的 9 月才发布 2.30.0 版本支持,对它的持续维护性和活跃度感到担忧。
我在升级的时候它还没有支持计划,迫不得已我只能换成 eslint-plugin-import-x,作为更轻量级的平替用起来差别不大、体感不明显:
import eslintPluginImportX from 'eslint-plugin-import-x';
export default [
eslintPluginImportX.flatConfigs.recommended,
eslintPluginImportX.flatConfigs.typescript,
]✅ eslint-plugin-jsonc
我把它用于 package.json 中依赖的自动排序,Monorepo 仓库中的永远的神。我们只需要用它 prettier 的插件即可
import eslintPluginJsonc from 'eslint-plugin-jsonc';
export default [
...eslintPluginJsonc.configs['flat/prettier'],
]按照语言/功能性分类
基于扁平化的设计以及官方的 files、languageOptions 等资源,按照语言来分类是一件非常自然而言的事情,除了 prettier 本身是功能性插件以外(JS 和 TS 都会用到)
- prettier # 公共功能性插件
- javascript
- typescript
- package-jsonexport default config[] 约定
可以发现上文在讲插件时,有的插件导出是一个对象需要这样使用 [someConfig];有的插件导出是一个数组这样使用[...someConfig]。
没有统一的导出约定也会极大提高使用成本,所以内部封装的插件统一是导出数组。
configMerge
该函数的主要功能是为所有的数组添加上 files 选项,保证不同规则之间的隔离
const configMerge = (configs, config) => {
return configs.map((conf) => ({ ...conf, ...config }));
};比如最终的 tsLintConfig 如下:
const config = [
eslintJs.configs.recommended,
...tsEslint.configs.recommended,
...tsEslint.configs.stylistic,
...eslintPrettier,
eslintPluginImportX.flatConfigs.recommended,
eslintPluginImportX.flatConfigs.typescript,
]
export default configMerge(config, {
files: ['**/*.{ts,tsx}'],
})后言
从历史包袱和活跃性而言,JS 和 PHP 是何其相似的两兄弟,成也萧何败也萧何。
工具链的统一规范对一门语言重要性不言而喻。抛开其他因素不谈,ESlint 9 有勇气甩开历史包袱重构是值得点赞的,而且社区的插件能这么快响应是我意料之外的(点名批评 import-js),我以为会像 rollup 一样先烂一阵子。