Skip to content

了解 Babel #13

@sundway

Description

@sundway

最近看了一些 babel 以及 JS 编译器相关的东西,后面分系列对每部分做深入了解。作为第一篇文章,这篇文章总结一些我对 Babel 的整体了解。

一个简单的 JS 编译器

首先可以先看一下一个最简单的 JS 编译器:the-super-tiny-compiler。它分为一下几个步骤:

  • 解析:解析这一步主要是把 JS 代码转换成 AST(抽象语法树),很多语言的编译过程也都会有这样一个概念。而这里面主要做了两步操作:词法分析(Tokenizer or Lexer)语法分析(Parse)。词法分析作用相当于自然语言的分词,比如 a + b,a 和 b 此时都会输出类似 Identifier 这样词法元素,并且一些无效的元素(取决于语言设计)会被剔除。它输出的是扁平化结构, 一般是一个 token 队列结构, 或是一个 token Stream 供 Parser 使用。语法解析通常是根据生成的 token 队列输出是一个树形结构,实现上会涉及大量的递归操作。通常这些解析过程我们可以利用一些工具帮助分析如[AST Explorer],还有其他很多类似的工具。(https://astexplorer.net/) 如图:

  • 转化:转化主要是对 AST 的节点进行 adding/removing/replacing 操作。而在 babel 中,很多插件就是在这一步根据做各种操作,最终静态编译出我们想要的代码。

  • 代码生成:大多数编译代码生成这一步主要是将 AST 和字符串化的代码返回出去。看 babel-core 中的回调也是一样,最终返回了三个参数 code、 map、ast。其中 map 主要是为了方便调试引入的 source map。

以上是这个超级简单的编译器编译的几个主要步骤,但是仔细看,其中还有几个关键的概念:

  • Visitors: 在转化 AST 需要对它进行递归的树形遍历。在遍历的过程中,实际就是通过 visitors(我的理解是主要用来操作节点一种模式)这种模式对节点进行访问,使用这个名字也主要是因为它采用的访问者模式,我们后面写 babel 插件时实际就是写的访问者。比如以下这个
function square(n) {
  return n * n;
}


比如我们需要对 Identifier 进行操作。

const MyVisitor = {
  Identifier() {
    console.log("Called!");
  }
};

// 你也可以先创建一个访问者对象,并在稍后给它添加方法。
let visitor = {};
visitor.MemberExpression = function() {};
visitor.FunctionDeclaration = function() {}

以上就是一个访问者,当进入到 identifier(关于 identifier 可以看看 babylon 语法规范) 节点时,就会自动调用 Identifier() 方法,这里每次进入以及出节点和退出节点都会调用一次方法。

Babel 的编译过程

babel 的编译过程主要是通过 babel-core 来实现的,所以只需要看看 babel-core 是如何做上面的编译过程。

以上是 babel-core 中的依赖,重点几个东西:

  • babylon 是比较核心的一部分,主要就是相当于上面的解析操作,它严重依赖 acorn 和 acorn-jsx 这两个库。
  • babel-traverse 是最为复杂的一部分,主要进行了上面的转化操作,对 AST 节点进行增加、删除、替换操作。
  • babel-generator 是较为简单的一部分,主要进行了上面的代码生成操作,将 AST 转换成代码。

其他的一些库则是一些辅助库,涉及到对节点进行复杂的操作时,可以方便的通过它们简化我们的操作。如:当我们写 babel 插件时,我们可以方便的利用 babel-types 中提供的方法进行操作,当然 babel-types 以及 node 节点都会作为入参传入到我们的 babel 插件的上下文中。

编写第一个 babel 插件

上图是在 astexplorer 的一个插件 demo。

export default function ({Plugin, types: t}) {
  return new Plugin('ast-transform', {
    visitor: {
      Identifier(node) {
        return t.identifier(node.name.split('').reverse().join(''));
      }
    }
  });
}

Identifier 方法中定义的一些操作就会在进入以及出 Identifier 节点时执行, 借助 babel-types 帮助函数返回一个新的 Identifier 节点。

以上是对 babel 整个工作流程的的了解,后面会对每一个部分做更进一步的了解。

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions