Enhance Console Method

babel 最开始叫 6to5,顾名思义是 es6 转 es5,后面改名为 babel。

babel 的用途

  1. 转义 es、ts 等到目标环境的 js。babel7 支持 preset-env,可以指定 targets,转换更加的精准,产物更小。
  2. 一些特定用途的代码转换。利用 babel 暴露的 api parse 出 AST,taro 框架。
  3. 代码的静态分析。如 linter、type checker 等。

babel 的编译流程

babel 整体编译流程分为三步:

  1. Parse 阶段:通过 parser 把源码转成 AST(抽象语法树),使用了 @babel/parser 工具。
  2. Transform 阶段:遍历 AST,调用各种 transform 插件对 AST 进行增删改,使用了 @babel/traverse、@babel/types、@babel/template 工具等。
  3. Generate 阶段:把转换后的 AST 打印成目标代码,并生成 sourcemap,使用了 @babel/generate 工具。
Babel 编译流程

Parse 阶段将源码字符串转换成 AST,这个过程分为词法分析、语法分析。比如 let name = 'guang'; 这样一段源码,我们要先把它细分为 token(词法分析),也就是 letname='guang'。之后把 token 进行递归组装,生成 AST(语法分析)。

Babel 解析过程

Transform 阶段是对 AST 的处理,对 AST 遍历的过程中遇到不同的节点会调用相应的 visitor 函数,visitor 函数里可以对节点进行增删改,返回新的 AST。这样遍历完 AST 之后就完成了对代码的修改。

Babel 转换过程

Generate 阶段将 AST 打印成目标代码字符串,并且会生成 sourcemap。不同的 AST 对应的不同结构的字符串。比如 IfStatement 节点可以生成 if(){} 格式的代码。

babel 的 AST

JS parser 的 AST 大多遵循了 estree 标准。常见的 AST 节点有:

@babel/parser

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"
  ]
});

@babel/traverse

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) {}
    }
}
Babel visitor

Path 是遍历过程中的路径,会保留上下文信息,有很多属性和方法,如下图所示。

Babel path

@babel/types

遍历 AST 的过程中需要创建一些 AST 和判断 AST 的类型。

// create IfStatement node
t.ifStatement(test, consequent, alternate);
// assert IfStatement node
t.isIfStatement(node, opts);
t.assertIfStatement(node, opts);

@babel/template

使用 @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 所在的行信息。

Github demo