· 854 words · 5 min
babel 最开始叫 6to5,顾名思义是 es6 转 es5,后面改名为 babel。
babel 整体编译流程分为三步:
Parse 阶段将源码字符串转换成 AST,这个过程分为词法分析、语法分析。比如 let name = 'guang';
这样一段源码,我们要先把它细分为 token(词法分析),也就是 let
、name
、=
、'guang'
。之后把 token 进行递归组装,生成 AST(语法分析)。
Transform 阶段是对 AST 的处理,对 AST 遍历的过程中遇到不同的节点会调用相应的 visitor 函数,visitor 函数里可以对节点进行增删改,返回新的 AST。这样遍历完 AST 之后就完成了对代码的修改。
Generate 阶段将 AST 打印成目标代码字符串,并且会生成 sourcemap。不同的 AST 对应的不同结构的字符串。比如 IfStatement 节点可以生成 if(){}
格式的代码。
JS parser 的 AST 大多遵循了 estree 标准。常见的 AST 节点有:
Literal 字面量节点。
Identifier 标识符,变量名、属性名、参数名等各种声明和引用的名字。
Statement 语句(可独立执行的单元)。
Declaration 声明变量。
Expression 表达式,执行完以后有返回值。
Babel parser 叫 babylon,基于 acorn 实现的,扩展了很多语法,可以支持 es next、jsx、flow、typescript 等语法的解析,其中 jsx、flow、typescript 这些非标准的语法的解析需要指定语法插件。
function parse(input: string, options?: ParserOptions): File
function parseExpression(input: string, options?: ParserOptions): Expression
require("@babel/parser").parse("code", {
sourceType: "module",
plugins: [
"jsx",
"typescript"
]
});
Parse 出的 AST 由 @babel/traverse 来遍历和修改,babel traverse 包提供了 traverse 方法:
function traverse(parent, opts)
Parent 指定要遍历的 AST 节点,opts 指定 visitor 函数。babel 会在遍历 parent 对应的 AST 时调用相应的 visitor 函数。visitor 函数会接收两个参数 path 和 state。
visitor: {
Identifier(path, state) {},
StringLiteral: {
enter(path, state) {},
exit(path, state) {}
}
}
Path 是遍历过程中的路径,会保留上下文信息,有很多属性和方法,如下图所示。
遍历 AST 的过程中需要创建一些 AST 和判断 AST 的类型。
// create IfStatement node
t.ifStatement(test, consequent, alternate);
// assert IfStatement node
t.isIfStatement(node, opts);
t.assertIfStatement(node, opts);
使用 @babel/template 包来批量创建 AST 节点。
const ast = template(code, [opts])(args);
const ast = template.ast(code, [opts]);
const ast = template.program(code, [opts]);
如果模版中有占位符,那么就用 template 的 api,在模版中写一些占位的参数,调用时传入这些占位符参数对应的 AST 节点。
const fn1 = template(`console.log(NAME)`);
const fn2 = template(`console.log(%%NAME%%)`);
const ast = fn1({
NAME: t.stringLiteral("CaptainOfPhB"),
});
const ast = fn2({
NAME: t.stringLiteral("CaptainOfPhB"),
});
接下来进入实战,使用上面的一些知识,让我们在写 console 的时候自动打印出 console 所在的行信息。