https://astexplorer.net/ 查看AST。

1、抽象语法树 AST

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中,这个处理过程中的每一步都涉及到创建或是操作抽象语法树,亦称 AST。

  1. function square(n) {
  2. return n * n;
  3. }
  4. // 转换成下面的样子
  5. {
  6. type: "FunctionDeclaration",
  7. id: {
  8. type: "Identifier",
  9. name: "square"
  10. },
  11. params: [{
  12. type: "Identifier",
  13. name: "n"
  14. }],
  15. body: {
  16. type: "BlockStatement",
  17. body: [{
  18. type: "ReturnStatement",
  19. argument: {
  20. type: "BinaryExpression",
  21. operator: "*",
  22. left: {
  23. type: "Identifier",
  24. name: "n"
  25. },
  26. right: {
  27. type: "Identifier",
  28. name: "n"
  29. }
  30. }
  31. }]
  32. }
  33. }

每一层都有相同的结构:

  1. {
  2. type: "FunctionDeclaration",
  3. id: {...},
  4. params: [...],
  5. body: {...}
  6. }
  7. {
  8. type: "Identifier",
  9. name: ...
  10. }
  11. {
  12. type: "BinaryExpression",
  13. operator: ...,
  14. left: {...},
  15. right: {...}
  16. }

这样的每一层结构也被叫做 节点(Node)。 一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。

字符串形式的 type 字段表示节点的类型(如: “FunctionDeclaration”,”Identifier”,或 “BinaryExpression”)。 每一种类型的节点定义了一些附加属性用来进一步描述该节点类型。
Babel 还为每个节点额外生成了一些属性,用于描述该节点在原始代码中的位置。比如 start end

2、Babel 的处理步骤

Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。

2.1 解析

解析步骤接收代码并输出 AST。

  • 词法分析:
    • 词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。
    • 你可以把令牌看作是一个扁平的语法片段数组:n * n
    • 转换成tokens是这样的:

[
{ type: { … }, value: “n”, start: 0, end: 1, loc: { … } },
{ type: { … }, value: “*”, start: 2, end: 3, loc: { … } },
{ type: { … }, value: “n”, start: 4, end: 5, loc: { … } },

]

  • 语法分析
    • 语法分析阶段会把一个令牌流转换成 AST 的形式。 这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操作。

2.2 转换


转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程 同时也是插件将要介入工作的部分。

2.3 生成

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。
代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

3、简单写一个babel插件

yarn add @babel/core
yarn add babel-template

  1. const template = require('babel-template');
  2. const temp = template("var b = 1")
  3. module.exports = function ({
  4. types: t
  5. }) {
  6. // 插件内容
  7. return {
  8. visitor: {
  9. // 接收两个参数path, state
  10. VariableDeclaration(path, state) {
  11. // 找到AST节点
  12. const node = path.node;
  13. // 判断节点类型 是否是变量节点, 申明方式是const
  14. if (t.isVariableDeclaration(node, {
  15. kind: "const"
  16. })) {
  17. // 将const 声明编译为let
  18. node.kind = "let";
  19. // var b = 1 的AST节点
  20. const insertNode = temp();
  21. // 插入一行代码var b = 1
  22. path.insertBefore(insertNode);
  23. }
  24. }
  25. }
  26. }
  27. }

使用插件:

  1. const myPlugin = require('./plugin')
  2. const babel = require('@babel/core');
  3. const content = 'const name = xiaoming';
  4. // 通过你编写的插件输出的代码
  5. const {
  6. code
  7. } = babel.transform(content, {
  8. plugins: [
  9. myPlugin
  10. ]
  11. });
  12. console.log(code);
  13. // var b = 1
  14. // let name = 'xiaoming'