推荐一个可视化的AST工具:https://astexplorer.net
推荐一篇文章:AST for JavaScript Developers (文章所用解析器Babylon已合并到@babel/parse,非Babylon.js,另一个同名的3D开源库)
Javascript
代码中的语法单元主要包括以下这么几种:
- 关键字:例如
var
、let
、const
等 - 标识符:没有被引号括起来的连续字符,可能是一个变量,也可能是
if
、else
这些关键字,又或者是true
、false
这些内置常量 - 运算符:
+
、-
、*
、/
等 - 数字:像十六进制,十进制,八进制以及科学表达式等语法
- 字符串:因为对计算机而言,字符串的内容会参与计算或显示
- 空格:连续的空格,换行,缩进等
- 注释:行注释或块注释都是一个不可拆分的最小语法单元
- 其他:大括号、小括号、分号、冒号等
写个小功能:箭头函数转成ES5中的普通函数,代码详见Github
const add =(a, b) => a + b;
先看一眼通过工具解析出来的AST结构:
Babel已经提供了丰富的处理AST的方法,一般仅需通过parse解析源代码为AST,然后进行transform转为需要的代码格式。
// AST遍历器,针对指定类型节点处理
const visitor = {
ArrowFunctionExpression(path) {
console.log(path);
const params = path.node.params;
// 偷懒写法,其实应该是遍历节点内容,然后逐条转换
const blockStatement = types.blockStatement([
types.returnStatement(
types.binaryExpression(
"+",
types.identifier("a"),
types.identifier("b")
)
),
]);
const func = types.functionExpression(
null,
params,
blockStatement,
false,
false
);
path.replaceWith(func);
},
};
const result = babel.transform(arrowCodeString, {
plugins: [
{
visitor,
},
],
});
console.log(result.code);//即为普通函数代码
const sum = function() {
return a + b;
};
转换后的抽象语法树解构如下图:
关键在于定义Visitor进行转换,如在Webpack中使用Babel的预置插件,即是使用官方实现的Visitor。
{
"plugins": ["@babel/plugin-transform-spread"]
}
可参考源码实现:babel-plugin-transform-spread/visitor
{
//核心代码,返回一个对象包含如下主要对象
ArrayExpression, //数组中解构
CallExpression, //函数调用时解构
NewExpression //对象构造时解构
}
以ArrayExpression为例:
ArrayExpression(path) {
const { node, scope } = path;
const elements = node.elements;
if (!hasSpread(elements)) return;
const nodes = build(elements, scope);
let first = nodes[0];
// If there is only one element in the ArrayExpression and
// the element was transformed (Array.prototype.slice.call or toConsumableArray)
// we know that the transformed code already takes care of cloning the array.
// So we can simply return that element.
if (nodes.length === 1 && first !== elements[0].argument) {
path.replaceWith(first);
return;
}
// If the first element is a ArrayExpression we can directly call
// concat on it.
// `[..].concat(..)`
// If not then we have to use `[].concat(arr)` and not `arr.concat`
// because `arr` could be extended/modified (e.g. Immutable) and we do not know exactly
// what concat would produce.
if (!t.isArrayExpression(first)) {
first = t.arrayExpression([]);
} else {
nodes.shift();
}
path.replaceWith(
t.callExpression(
t.memberExpression(first, t.identifier("concat")),
nodes,
),
);
},