编写命令行工具升级eslint配置
2021-06-08
前段时间,我们组对旧的 eslint 规则做了一次改造,推出了新的 eslint 规则。为了让从旧往新的过度更顺畅,同时让使用者的改造意愿更强,我们提供了命令行无缝升级,只需要执行 idux-cli init
即可完成配置升级或初始化。组长把这个叫做 开发者体验
。
配置升级的本质是:将无用的旧配置去掉,有用配置保留,再将旧配置和新规则进行合并生成新配置文件。
首先有一个标准的 eslint 配置作为升级后的目标配置,大概如下(用xxx替换了一些缩写信息):
module.exports = {
root: true,
parserOptions: { // ts的项目 或者 ts+vue的项目启用
parser: '@typescript-eslint/parser'
},
extends: [
'@xxx/base', // 基础规则,必须启用
'@xxx/vue', // 使用 vue 需要启用
'@xxx/vue2', // 使用 vue2 需要启用
'@xxx/vue3', // 使用 vue3 需要启用
'@xxx/typescript', // 使用 typescript 需要启用
'@xxx/i18n', // 老版本国际化需要启用
'@xxx/jsformat', // 格式化相关
'@xxx/vueformat', // 格式化相关
'@xxx/tsformat', // 格式化相关
],
env: {
},
globals: {
},
rules: {
// Customize your rules
},
};
从配置里可以看出来,新配置的生成和以下几个因素有关:
- 是否通过 eslint 来控制格式化
- 使用的技术栈
同时 eslint 配置文件有 .eslintrc.[js,json,yml,yaml]
等多种后缀文件,甚至还可以定义在项目的 package.json
文件中,我们对所有的文件格式都提供支持。
整个过程伪代码如下:
init () {
// 命令行询问格式化控制是否由 eslint 控制
let formatrControlByEslint = await formatControlByEslint();
// step1: 生成eslintrc
genEslintRc(formatrControlByEslint);
// step2: 格式化如果由prettier控制,则生成 prettierrc
if (!formatrControlByEslint) {
prettierrc();
}
// step4: 删除package.json中旧的eslint依赖
deleteOldPkgsInPackageJson();
}
genEslintRc
要做以下几件事:
- 判断技术栈
- 根据技术栈创建新的 eslint 文件的内容
- 根据现在项目的后缀来分析现在的配置,且和新的配置合并生成新的 eslint 配置
- 写入/创建对应的 eslint 配置文件
如何判断技术栈?
- 通过
package.json
中的依赖来判断
如何分析旧 eslint 配置?
.eslintrc.js
文件
最开始,我是通过 require .eslintrc.js
文件来对旧配置进行处理,但是后面发现这种方式是不对的。因为在配置中,可能存在三元表达式,如果 require 此文件,则得到的是表达式执行后的结果,而我的目标是保留原始代码。因此只能通过 操作抽象语法树来处理。
起手就是三板斧: esprima
来解析 AST,estraverse
来操作 AST,escodegen
来生成代码。伪代码如下:
import * as estraverse from 'estraverse';
import * as esprima from 'esprima';
import * as escodegen from 'escodegen';
// 解析ast, fileContent为读取出来的 .eslintrc.js 文件内容
let parseAst = esprima.parseScript(fileContent);
/**
* 操作抽象语法树
* @Params {Object} ast 解析出来的ast
* @Params {Object} 根据技术栈构造的eslint一些配置项
*/
const eslintAst = (ast, newEslintConfig) => {
estraverse.replace(ast, {
enter (astNode) {
// 对节点做增删改查处理,构造出想要的eslint配置
}
});
}
// 生成代码,得到配置
let newAst = eslintAst(parseAst, newEslintConfig);
escodegen.generate(newAst);
- json、yml、yaml文件
这里将这三者放在一起讲,很显然他们的处理是相似的。将 yml 或 yaml 处理为 json 数据来进行操作,最后再把处理好的 JSON 转为相应后缀的文件格式即可。这个工作可以使用 js-yaml
来完成。
后续生成新的配置则是对 JSON 进行操作,问题就变得很简单了。
其他
- 注意新生成的配置,其缩进要和旧的配置缩进一致。可以使用
detect-indent
这个包来获得缩进位数。 - 容易忽略没有配置 eslint 的新项目的场景