Babel 是一个 JavaScript 编译器,它是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中,官网的链接是babel。也可以使用Babel插件以及工具来对js代码编译的过程进行修改,实现自定义js代码的生成。
Babel 工作流程
Babel处理js代码的主要的流程如下图所示,主要包含三个部分:解析(Parse)、转换(Transform)以及生成(Generate)。

三部分的主要功能是:
Parse:将js代码字符串解析成AST(抽象语法树,Abstract Syntax Tree),Babel中使用babel-parser工具来进行实现;Transform:遍历Parse中生成的AST,并对AST进行相应的转换,从而实现对js代码的修改,Babel中使用babel-traverse来进行实现,Babel Traverse(遍历)模块维护了整棵树的状态,并且负责替换、移除和添加节点;Generate:将上述经过Transform修改的AST代码,生成对应的js代码,最终实现js的转换,Bable中使用babel-generator来进行实现。
抽象语法树(AST)
在具体介绍Babel前,我们需要对抽象语法树有一定的理解,在计算机科学中,抽象语法树(Abstract Syntax Tree,AST)是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。简单来说,它是你所写代码的的树状结构化表现形式。
while(b !== 0){
if(a > b){
a -= b;
}else{
b -= a
}
}
可以通过astexplorer网站来查看js代码对应生成的AST,如上的一段代码生成的AST如下所示生成的AST会比较长,就不直接贴网站上的代码了,可以自己贴上去生成。将对应的AST进行精简,形成如下图所示的树形结构。

可以通过Babel的AST规范,来对Babel定义的AST中的每一个结点的每一个类型进行了解。
实践
通过编程来实现对js代码的修改与编译,因为没有想清楚要实现啥功能,所以此次只记录相应的过程要用到的接口以及经典的代码。
要先安装Bable对应的库:
npm install --save-dev @babel/core @babel/cli @babel/preset-env
第一步先是解析js代码形成AST,通过babel/parser进行实现,变量ast是对应得到的AST对象,如下所示:
const babylon = require('@babel/parser');
const code = require("fs").readFileSync("demo.js", "utf-8");
const ast = babylon.parse(code, {
// parse in strict mode and allow module declarations
sourceType: "script",
filename: "test.js"
});
接下来是对AST树进行遍历,通过babel/traverse来实现:
const babelTraverse = require('@babel/traverse').default;
babelTraverse(ast, {
enter(path) {
...
},
exit(path){
...
}
});
babel/traverse会遍历AST上的所有结点,而访问每一个结点有两个时机:enter 和 exit。enter 是在节点中包含的子结点内容还没被解析时,而 exit 是在包含的子结点被解析完成之后。
可以通过下面的代码来输出ast结点的类型:
const babelTraverse = require('@babel/traverse').default;
const babylon = require('@babel/parser');
const code = require("fs").readFileSync("src.js", "utf-8");
const ast = babylon.parse(code, {
// parse in strict mode and allow module declarations
sourceType: "script",
filename: "test.js"
});
var indent = "";
babelTraverse(ast, {
enter(path) {
console.log(indent + "<" + path.node.type + ">");
indent += " ";
},
exit(path){
indent = indent.slice(0, -2);
console.log(indent + "<" + "/" + path.node.type + ">");
}
});
大多数时候我们是不需要遍历和处理所有的结点,只需要处理某些特定结点(比如if语句)。这种情况下,babel/traverse提供了简单的visitor来过滤结点。可以在visitor中指定要处理的结点类型,从而实现筛选。下面的代码是只处理IfStatement:
const myVisitor = {
IfStatement(path) {
...
}
};
babyTraverse(ast, myVisitor);
visitor也支持 enter 和 exit:
const myVisitor = {
IfStatement: {
enter(path){
...
}
exit(path){
...
}
}
};
接下来就是替换和修改了,在visitor中选中了要处理的结点后就可以对AST进行修改和更新:
babyTraverse(ast, {
enter(path) {
if (
path.node.type === "Identifier" &&
path.node.name === "n"
) {
path.node.name = "x";
}
}
});
在处理完成之后,最终通过babel-generator来生成js代码:
const babelGenerator = require('@babel/generator').default;
generate(ast, {
retainLines: false,
}, code);
总结
很简单的了解了下babel,知道了原来通过ast也可以很简单的对js进行修改,本文只是一个入门的介绍,如果需要详细了解,可以看下面的参考。

