前提了解:
babel是干啥用的?
babel就是个js编译器,可以用来实现代码检查、代码生成、自定义语法等功能。
AST又是什么,长什么样子?
ast是用来表示代码结构的一种树结构,可以借助网页AST Explorer输入代码查看对应的ATS树。
正片开始:
步骤一:源代码解析成AST抽象语法树
使用@babel/parser将源代码解析成AST树
const parser = require('@babel/parser')
const originCode = fs.readFileSync(path, 'utf-8')
const ast = parser.parse(oldCode, {
sourceType: 'unambiguous',
plugins: ['jsx', 'decorators-legacy', 'classProperties', 'typescript']
})
参数详细说明:官网文档babel-parser
步骤二:对AST树进行遍历
使用@babel/traverse对AST树进行遍历
const traverse = require('@babel/traverse')
traverse.default(ast, visitor) // visitor为对ast节点处理的babel插件
遍历的每个节点都会丢给visitor去处理
步骤三:编写babel插件visitor
代码检查例如
const visitor_check = {
// 标签里属性的类型为JSXAttribute会触发执行
JSXAttribute(path) {
if (_targetEvent.includes(path.node.name.name)) {
const expressionBody = safeProp(path, 'node', 'value', 'expression', 'body', 'body') || []
const flag = expressionBody.some(
element => safeProp(element, 'expression', 'callee', 'name') === 'autoTrack'
)
if (!flag) {
console.log('缺少埋点->', _curPath)
process.exit(1)
}
}
}
}
找到目标所在位置,然后判断。
代码添加例如
const types = require('@babel/types')
const visitor_add = {
JSXAttribute(path) {
if (_targetEvent.includes(path.node.name.name)) {
const expressionBody = safeProp(path, 'node', 'value', 'expression', 'body', 'body') || []
const flag = expressionBody.some(
element => safeProp(element, 'expression', 'callee', 'name') === 'autoTrack'
)
if (!flag) {
const expressionStatement = types.expressionStatement(
types.callExpression(types.identifier('autoTrack'), [
types.stringLiteral('autoTrack_event'),
types.stringLiteral('autoTrack_label')
])
)
path.node.value?.expression?.body?.body?.unshift(expressionStatement) &&
_addImportPathList.push(_curPath)
}
}
}
}
找到要添加的位置,通过@babel/types调用节点类型同名的方法创建该类型的节点,鼠标放到名字上会有提示传入什么样的参数。创建后加进去即可。
最后一步:将此时的AST树生成目标代码
const generator = require('@babel/generator')
const newCode = generator.default(ast)
fs.writeFileSync(path, newCode.code)
childProcess.execSync(`npx prettier --write ${path}`) // 把格式还原
通过@babel/generator将ast转变成代码,ok,万事大吉。