说明
本文适合想要快速、粗略的了解,很多深入、细节的地方都不涉及。
如有需要,建议直接看babel插件开发手册
AST了解
含义
AST全称Abstract Syntax Tree,是指用树状结构化来表示源代码。
前端构建过程,很多工具都有用到,比如TSbabel、rollup、webpack。
如图,右侧则是左边源代码的AST
可以使用astexplorer在线体验
如何生成AST?
我们可以直接使用库,常用的有esprima(eslint的默认解析器)、acorn(rollup使用)。babel是自己开发的,@babel/parser,参考了acron。
从源码到AST,会经过两次处理:
-
词法分析器 Lexical analyzer (别名:scanner)
先将源码分割成一串语法单元(token)
语法单元还有其他属性,但这里为了截图出更多的
type就没显示出来。
在线体验esprima -
语法分析器 Syntax analyzer (别名:parser)
分析语法单元间的关系,将词法单元转为树结构。
babel代码转换流程
我们先使用babel的基础组件来了解转化过程。
demo如下,我们来修改函数名称。
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const code = `function exec(message = 'hello') {
console.log(message);
}`;
// 1. 解析——转为AST
const ast = parser.parse(code);
// console.log(ast);
// 2. 转换——修改AST数据
traverse(ast, {
enter(nodePath){
// console.log(nodePath.node.type);
if(nodePath.node.type === 'FunctionDeclaration') {
nodePath.node.id.name = 'modify_' + nodePath.node.id.name;
}
}
});
// 3. 生成——使用修改后的AST生成新代码
const result = generator(ast);
console.log('\n新代码:');
console.log(result.code);
插件开发示例
我们开发插件时,不用像上面那样需要自己引入parser等,babel已经定义了接口。
功能确认
我想开发一个能给普通的Function增加一个运行时长的日志
// 源代码
function exec(message = 'hello') {
console.log(message);
}
// 目标代码
function exec(message = 'hello') {
console.time("exec");
console.log(message);
console.timeEnd("exec");
}
开发
-
搭结构
const babel = require('@babel/core'); const addConsolePlugin = require('./plugin-add-console'); // 源代码 const code = `function exec(message = 'hello') { console.log(message); }`; // 转换 const target = babel.transform(code, { // 使用插件 plugins: [addConsolePlugin] }); console.log('\n转换后的代码:'); console.log(target.code);插件 plugin-add-console.js
const babelTypes = require('@babel/types'); module.exports = { visitor: { FunctionDeclaration(nodePath) { console.log(nodePath); } } }visitor的属性就是我们想要匹配的节点类型,但如何知道我应该匹配那个type?
AST结构中的type属性值就是 -
AST对比
目标代码的AST对比源代码AST和目标代码的AST,body的头部和尾部各自增加了一个
ExpressionStatement。 -
修改AST树
上一步我们知道了如何改变AST树,现在就来处理
开发需要用用到@babel/types,可以通过babel-types的文档查到它的使用方法visitor: { FunctionDeclaration(nodePath) { const funName = nodePath.node.id.name; const body = nodePath.node.body.body; // 每个babelTypes的函数调用,都是通过AST树的type得到的 const obj = babelTypes.Identifier('console'); const time = babelTypes.Identifier('time'); const timeEnd = babelTypes.Identifier('timeEnd'); const startMember = babelTypes.memberExpression(obj, time); const endMember = babelTypes.memberExpression(obj, timeEnd); const funLit = babelTypes.stringLiteral(funName); const startExpression = babelTypes.callExpression(startMember, [funLit]); const endExpression = babelTypes.callExpression(endMember, [funLit]); const first = babelTypes.ExpressionStatement(startExpression); const last = babelTypes.ExpressionStatement(endExpression); // 修改AST body.unshift(first); body.push(last); } }执行调用,得到转换后的代码