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
进行修改,本文只是一个入门的介绍,如果需要详细了解,可以看下面的参考。