前言
直入正题,eslint 目前为止的配置文件格式( 如 .eslintrc.js
) 存在很多无法避免的历史问题:
-
配置格式混乱,层层不明确的继承,不易理解。
-
插件配置时不支持实体运用(不能传
function
/object
),基于字符串的依赖解析一旦错误,插件将失效。 -
依赖关系混乱,继承的配置中,依赖查找经常错位,找不到预期的
eslint-plugin-*
/eslint-config-*
等依赖,需要在当前项目重新安装。
等等问题,对于 2
、3
问题我们早期会使用或 fork @rushstack/eslint-patch
来 hack 插件的导入路径解决依赖找不到的问题,但方式十分不优雅。
无论你是否遇到过以上 eslint 的配置问题,在 eslint v8.23 版本正式发布时推出了全新的配置格式 Eslint Flat Config
,这一切问题都将逐步得到解决。
下面我们就旧配置如何迁移升级到新配置,以及迁移的技术细节进行拆解介绍。
前置知识
官方迁移文档
先进行前置知识的学习,官方为我们准备了 3 part 的升级迁移指南:
-
ESLint’s new config system, Part 1: Background
-
ESLint’s new config system, Part 2: Introduction to flat config
-
ESLint’s new config system, Part 3: Developer preview
Flat Config 配置说明文档
Eslint doc :Configuration Files (New)
在阅读过以上内容后,我们针对旧配置升级到新的 Flat Config 新配置进行一个简单 case 的实战。
迁移实战
新旧对比
在官方 3 part 升级文章中的 Backwards compatibility utility 章节,我们得知使用 FlatCompat
可以将旧配置 compat 自动转换到新配置。
我们以此为抓手,细粒度的打印对比新旧配置,观察他们的区别,以此得知迁移升级的方法。
pnpm add -D @eslint/eslintrc
const { FlatCompat } = require('@eslint/eslintrc')
const compat = new FlatCompat({
baseDirectory: __dirname,
})
const newConfigs = [
// >> [ { plugins: { prettier: [Object] } } ]
compat.config({
plugins: ['prettier'],
}),
// >> []
compat.config({
root: true
}),
// >> [ { languageOptions: { parser: [Object] } } ]
compat.config({
parser: '@typescript-eslint/parser'
}),
// >> [ { ignores: [ [Function (anonymous)] ] } ]
compat.config({
ignorePatterns: ['/dist', '/node_modules']
}),
// >> [ { rules: { ... } }, { rules: { ... }, plugins: { prettier: [Object] } } ]
compat.config({
extends: ['plugin:prettier/recommended']
}),
// [ { languageOptions: { globals: [Object], parserOptions: [Object] } } ]
compat.config({
env: {
node: true
},
}),
]
newConfigs.forEach((config) => {
console.log(config)
})
基于以上对比得到的结果,同时参考官方 Flat Config 新配置的 文档 ,我们得知如下结论:
旧配置项 | 发生的变化 |
---|---|
plugins | 插件现在的配置格式是一个 Record<string, Object> ,即可以传递实体了 |
root | 现在新配置没有了,不需要了 |
parser | 现在挪到了 languageOptions.parser |
ignorePatterns | 忽略文件的 compat 结果默认是一个函数,我们推测他是一个匹配逻辑,但是通过 官方文档 我们得知正确的写法应该是 ignores: ['**/node_modules/**'] |
extends | 我们 compat 得到了一个复杂的对象,由于 Flat Config 新配置抛弃了 extends ,通过阅读 eslint-plugin-prettier 的源码,我们得知转换后的配置即为将 eslint-plugin-prettier 的所有零散配置项组装起来的结果,在下文中我们将继续拆解 |
env | 参阅官方升级文档 Goodbye environments, hello globals 章节,可得知新的配置写法需要基于 globals 包进行引用 |
注:关于更多配置,请自行对比,并参考 Flat Config 新配置的 官方文档 ,最终决定你的迁移结果应该如何编写,切勿直接使用 compat 后的结果!
extends 转换拆解
通过 compat 对比我们可以得到几乎全部旧配置升级新配置的结果,此处比较复杂的是 extends
的转换,我们进一步进行拆解。
如下是 eslint-plugin-prettier
的源码主要部分:
module.exports = {
configs: {
recommended: {
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
},
},
rules: {
prettier: {
// ...
},
},
}
我们使用的是 plugin:prettier/recommended
,就是 configs.recommended
部分,拆解后的结果:
可以看到正如我们上面所说,extends
的内容只是被每一项零散的拿到了新配置里,仅此而已。
实战演练
下面我们进行一个真实的实战 case ,旧配置如下:
module.exports = {
// 🎉 新版配置不再需要了
root: true,
// 🟢 我们得知新的 parser 应该写在 languageOptions.parser
parser: '@typescript-eslint/parser',
// 🟢 官方文档告诉我们需要从 globals 这个包里面引用
env: {
es2021: true,
browser: true,
node: true,
commonjs: true,
},
// 🟡 经过 extends 的拆解,我们得知要手动把相关零散配置项拼凑起来
extends: ['plugin:prettier/recommended'],
// 🟢 我们得知应该写在新配置的 plugins 对象里
plugins: ['prettier'],
// 🎉 该配置在 Flat Config 中为默认值,可以不要了
parserOptions: {
sourceType: 'module',
},
rules: {
// 🟡 一些自己的自定义 rules
'prettier/prettier': 'warn',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
},
// 🟢 我们参考官方文档,得知可以写成 **/node_modules/**
ignorePatterns: ['/dist', '/node_modules'],
}
最终得到 Flat Config 新配置如下,我们关注以 🟡
标识的部分如何进入新配置 :
const globals = require('globals')
const eslintConfigPrettier = require.resolve('eslint-config-prettier')
const configPrettier = require(eslintConfigPrettier)
const eslintPluginPrettier = require.resolve('eslint-plugin-prettier')
const pluginPrettier = require(eslintPluginPrettier)
const parser = require.resolve('@typescript-eslint/parser')
const parserInstance = require(parser)
module.exports = [
{
files: ['**/*.ts?(x)'],
ignores: ['**/dist/**', '**/node_modules/**'],
languageOptions: {
parser: parserInstance,
globals: {
...globals.commonjs,
...globals.browser,
...globals.es2021,
...globals.node,
},
},
// 🟡 recommended.plugins: ['prettier']
plugins: {
prettier: pluginPrettier,
},
rules: {
// 🟡 recommended.extends: ['prettier']
...configPrettier.rules,
// 🟡 recommended.rules: { ... }
...pluginPrettier.configs.recommended.rules,
// 🟡 一些自己的自定义 rules
'prettier/prettier': 'warn',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
},
}
]
到此为止,我们已经顺利完成 Flat Config 新配置的迁移升级。
此处需要注意的是新配置是 数组 的导出形式,不再是对象:
-
设定为数组是为分类配置准备的,比如针对
files: ['**/*.ts?(x)']
TypeScript 文件一个配置对象,针对files: ['**/*.js?(x)']
JavaScript 文件一个配置对象,也可以不包含files
,但越往后的配置对象在合并过程中优先级越高(如上文中 compat 转换extends
的结果就是两个对象)。 -
当
files
匹配冲突时(比如一个文件匹配了多个配置对象),将以最后一个匹配对象为准,其余选项合并,请参见:Cascading configuration objects 。
Vscode 自动格式化
在 vscode 进行 eslint cmd/ctrl + s
保存自动格式化必须依赖 vscode-eslint 插件。
开启 Flat Config 的自动格式化需要 eslint 插件版本 >= 2.3.0
,若无法更新到 2.3.0
,可尝试切换到预览版。同时,由于目前 Flat Config 还处于实验性阶段,需要新增如下配置开启:
// settings.json
{
"eslint.experimental.useFlatConfig": true,
}
满足 eslint 插件版本 >= 2.3.0
,并开启配置后,便可在项目中使用 eslint.config.js
与 Flat Config 新配置保存自动格式化了!
总结
由于新配置支持将 eslint-plugin-*
经过 require
后以实体的方式使用,所以原来的字符串会导致依赖解析错误的问题迎刃而解了。
另外,新的 Flat Config 配置的配置文件名是 eslint.config.js
而不是 .eslintrc.js
,需要多加注意。
关于更多变化和配置内容,请参考 官方文档 。
以上。