ast 入门(从写babel插件来深入了解ast)
最终效果
先看效果,写了一个小例子,请点击查看 https://astexplorer.net/#/gist/49a1bb9cc81e392220ffd998ff47162c/4728dec0c4645e40c58426886bc2069c959544e4
前言
查找ast相关资料,看到别人引用资源里有一个ppt,《how writing a babel plugin is like... jQuery》看了一遍发现,真香!下面我就按照我的理解总结一下ppt里面的内容。通过了解如何开发一个babel插件,也能让我们更好地理解ast. 后面可以不看,重点推荐下面这两个资料:
- 像写jquery一样写一个babel 插件 https://hzoo.github.io/babel-plugin-slides/assets/player/KeynoteDHTMLPlayer.html
- ASTs for Beginners at Clevertech (February 2017) https://www.youtube.com/watch?v=CFQBHy8RCpg&t=3009s&list=PLq7KCQmgfYTCHovzXIZQxBDL_p9RR2XLF&index=6
ast 基础
在开始往下面看之前,为了对ast节点类型有更全面的认识,推荐阅读下这个同学写的文章:https://github.com/Pines-Cheng/blog/issues/53 Babylon 与 JavaScript 抽象语法树
What is Babel?
Babel is a javascript compiler。
Babel 是一个通用的,多用途的Javascript 编译器
整个ppt最犀利的观点就是:
Babel::javascript jQuery::DOM Babel turns the code you have into the code you need
dom 之于 jQuery 同 javascript 之于 Babel
Compile Overview
babel 中的几个核心模块:
- babylon: 解析器 将code -> ast
- babel-traverse: 转换模块 允许我们遍历,浏览,修改ast
- babel-generator: 生成模块 将ast -> 最终需要的code
Write a Babel Plugin
A Plugin is just a function
export default babel => {
return {
visitor:{...}
}
}
AST Visitor Pattern
export default function(babel) {
const t = babel.types;
return {
visitor: {
BinaryExpression(path) {...},
Identifier(path) {...},
FunctionDeclaration(path) {...},
ClassDeclaration(path) {...},
}
}
}
当Babel遍历ast时,会查看每一个节点,当发现visitor对象上有对应的方法时,会调用这个方法,并且把对应的上下文传递进去。
举个例子
var a = 2**3 // 源代码转换为
var a = Math.pow(2, 3); // 目标代码
上面通过babel 插件如何实现呢?
Step1 查看源代码ast 结构
我们利用https://astexplorer.net 这个网址可以查看到源代码对应的ast结构,让我们知道我们需要的各个节点类型。

Step2 获取源码对应结构
export default function(babel) {
// 插件大体结构是一样的
const t = babel.types;
return {
visitor: {
// 2**3 是一个二元表达式
BinaryExpression(path) {
// console.log(path); 可以把对应路径打印出来看看
if (path.node.operator !== '**') {
return; // 如果节点对应的操作不是** 不做任何处理
}
const {left, right} = path.node; // 获取表达式左右两项
}
}
}
}
Step3 对源码结构进行替换
最终我们希望把2**3 替换为一个函数调用Math.pow(2,3), 所以要看下Math.pow(2,3) 的ast 结构,同样利用上面那个网址

// babel-type 手册https://babeljs.io/docs/en/next/babel-types.html
t.callExpression(
t.memberExpression( // 创建函数
t.identifier('Math'),
t.identifier('pow'),
),
[left, right] // 入参
);
这样就生成了一个Math.pow(2, 3)的函数调用节点
step4 节点替换
export default function(label) {
const t = babel.types;
return {
visitor: {
BinaryExpression(path) {
if (path.node.operator !== '**') {
return;
}
path.replaceWith(t.callExpression(
t.memberExpression(
t.identifier('Math'),
t.identifier('pow')
),
[left, right]
));
}
}
}
}
拓展
其实写babel插件的重点在于了解ast, 有了ast以后我们可以做很多事情,除了写bable插件,我们也可以去写eslint插件,本质是一样的,只是语法有些略微的区别。
module.exports = {
meta: {
docs: {
description: "Disallow if statement without block",
category: "Best Practices",
recommended: true
}
},
create(context) {
return {
IfStatement(node) {
if(isBlock(node.consequent) && isBlock(node.alternate)) {
return;
}
context.report({
node,
message: 'yo, y u non block?'
fix: function(fixer) {
// fix the code
}
})
function isBlock(n) {
return !n || n.type === 'BlockStatement';
}
}
};
// 更复杂的babel插件参考
// https://juejin.im/post/5a17d51851882531b15b2dfc
}
};
参考资料: 1.how writing a babel plugin is like...(重要 非常棒) https://hzoo.github.io/babel-plugin-slides/assets/player/KeynoteDHTMLPlayer.html#0 2.Writing custom Babel and ESLint plugins http://slides.com/kentcdodds/a-beginners-guide-to-asts#/6 3.@babel/types babel的参考手册 4.通过开发 Babel 插件理解抽象语法树(AST) https://www.zcfy.cc/article/understanding-asts-by-building-your-own-babel-plugin 5.如何写一个eslint 插件(拓展) https://www.zcfy.cc/article/creating-an-eslint-plugin