# Babel 基础
# Babel 是什么
Babel is a JavaScript compiler. Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments
Babel就是js的编译器
Babel是一个工具集,可以用来将ES2015+
的代码转换为向后兼容的浏览器可运行的代码
# Babel 能做些什么
- 语法转换
- 提供当前环境可运行的代码垫片
- 源代码的转换
# Babel 的工作原理
# 抽象语法树(AST)
首先我们编写的代码在编译阶段
解析成抽象语法树(AST),然后经过一系列的遍历和转换,然后再将转换后的抽象语法树生成
为常规的js代码,具体的过程如下图
代码解析成AST的目的就是方便计算机更好地理解我们的代码,比如一个add
函数
function add(x, y) {
return x + y;
}
add(1, 2);
将代码解析成抽象语法树(在线工具 (opens new window)),表示成JSON的形式
{
"type": "Program",
"start": 0,
"end": 53,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 40,
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"name": "add"
},
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 13,
"end": 14,
"name": "x"
},
{
"type": "Identifier",
"start": 16,
"end": 17,
"name": "y"
}
],
"body": {
"type": "BlockStatement",
"start": 19,
"end": 40,
"body": [
{
"type": "ReturnStatement",
"start": 25,
"end": 38,
"argument": {
"type": "BinaryExpression",
"start": 32,
"end": 37,
"left": {
"type": "Identifier",
"start": 32,
"end": 33,
"name": "x"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 36,
"end": 37,
"name": "y"
}
}
}
]
}
},
{
"type": "ExpressionStatement",
"start": 42,
"end": 52,
"expression": {
"type": "CallExpression",
"start": 42,
"end": 51,
"callee": {
"type": "Identifier",
"start": 42,
"end": 45,
"name": "add"
},
"arguments": [
{
"type": "Literal",
"start": 46,
"end": 47,
"value": 1,
"raw": "1"
},
{
"type": "Literal",
"start": 49,
"end": 50,
"value": 2,
"raw": "2"
}
],
"optional": false
}
}
],
"sourceType": "module"
}
这里你会发现抽象语法树中不同层级有着相似的结构,比如:
{
"type": "Program",
"start": 0,
"end": 52,
"body": [...]
}
{
"type": "FunctionDeclaration",
"start": 0,
"end": 40,
"id": {...},
"body": {...}
}
{
"type": "BlockStatement",
"start": 19,
"end": 40,
"body": [...]
}
像这样的结构叫做节点(Node)。一个AST是由多个或单个这样的节点组成,节点内部可以有多个这样的子节点,构成一颗语法树,这样就可以描述用于静态分析的程序语法。
节点中的type字段表示节点的类型,比如上述AST中的 "Program"、"FunctionDeclaration"、 "ExpressionStatement"等等, 当然每种节点类型会有一些附加的属性用于进一步描述该节点类型
再举一个例子,如下的代码:
function abs(number) {
if (number >= 0) { // test
return number; // consequent
} else {
return -number; // alternate
}
}
被解析成 AST 后对应的结构图
# 工作原理
上图已经描述了Babel的工作流程,有三个主要处理步骤分别是: 解析(parse)
,转换(transform)
,生成(generate)
解析 将代码解析成抽象语法树(AST),每个js引擎(比如Chrome浏览器中的V8引擎)都有自己的AST解析器,而Babel是通过
Babylon
(现改为@babel/parser (opens new window) Github (opens new window))实现的。在解析过程中有两个阶段:词法分析
和语法分析
,词法分析阶段把字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中节点;而语法分析阶段则会把一个令牌流转换成 AST的形式,同时这个阶段会把令牌中的信息转换成AST的表述结构转换 在这个阶段,Babel接受得到AST并通过
babel-traverse
对其进行深度优先遍历
,在此过程中对节点进行添加
、更新
及移除
操作。这部分也是Babel插件介入工作的部分
生成 将经过转换的AST通过
babel-generator
再转换成js代码,过程就是深度优先遍历
整个AST,然后构建可以表示转换后代码的字符串,同时这个阶段还会生成Source Map
这部分的详细过程可以参考:babel手册 (opens new window)
Code -> Tokens -> AST -> transform -> Code(sourcemap)
# Babel 工具集
# babylon (opens new window)
“Babylon 是 Babel的解析器。最初是从Acorn项目fork出来的。Acorn非常快,易于使用,并且针对非标准特性(以及那些未来的标准特性) 设计了一个基于插件的架构。”。这里直接引用了手册里的说明,可以说Babylon定义了把代码解析成AST的一套规范。
eg:
import * as babylon from "babylon";
const code = `function square(n) {
return n * n;
}`;
babylon.parse(code);
// Node {
// type: "File",
// start: 0,
// end: 38,
// loc: SourceLocation {...},
// program: Node {...},
// comments: [],
// tokens: [...]
// }
实际上该项目已经被移动至@babel/parser (opens new window)
@babel/parser的介绍:传送门 (opens new window)
# babel-traverse (opens new window)
babel-traverse用于维护操作AST的状态,定义了更新、添加和移除节点的操作方法。之前也说到,path参数里面的属性和方法都是在babel-traverse里面定义的。这里还是引用一个例子,将babel-traverse和Babylon一起使用来遍历和更新节点
import * as babylon from "babylon";
import traverse from "babel-traverse";
const code = `function square(n) {
return n * n;
}`;
const ast = babylon.parse(code);
traverse(ast, {
enter(path) {
if (
path.node.type === "Identifier" &&
path.node.name === "n"
) {
path.node.name = "x";
}
}
});
# babel-types
babel-types是一个强大的用于处理AST节点的工具库,它包含了构造、验证以及变换AST节点的方法。该工具库包含考虑周到的工具方法,对编写处理AST逻辑非常有用。 这个工具库的具体的API可以参考Babel官网 (opens new window)
示例:
这里我们还是用import命令来演示一个例子,比如我们要判断import导入是什么类型的导入,这里先写出三种形式的导入
import { Ajax } from '../lib/utils';
import utils from '../lib/utils';
import * as utils from '../lib/utils';
在AST中用于表示上面导入的三个变量的节点是不同的,分别叫做ImportSpecifier、ImportDefaultSpecifier和ImportNamespaceSpecifier。具体可以参考这里 (opens new window)。 如果我们只对导入指定变量的import命令语句做处理,那么我们的babel插件就可以这样写
function plugin() {
return ({ types }) => ({
visitor: {
ImportDeclaration(path, state) {
const specifiers = path.node.specifiers;
specifiers.forEach((specifier) => {
if (!types.isImportDefaultSpecifier(specifier) && !types.isImportNamespaceSpecifier(specifier)) {
// do something
}
})
}
}
}
}