# 一、开始

前面分析了@babel/core (opens new window)@babel/parser (opens new window)@babel/traverse (opens new window),下面看一下@babel/generator

这个库主要是根据AST生成代码,相对比较简单,本文会介绍其核心逻辑,再举个简单的例子。

# 二、API说明

看下@babel/generator官方文档 (opens new window),其API使用方法如下:

import { parse } from "@babel/parser";
import generate from "@babel/generator";

const code = "class Example {}";
const ast = parse(code);

const output = generate(
  ast,
  {
    /* options */
  },
  code
);

# 三、源码解析

本次分析的@babel/generator版本是v7.16.0 (opens new window)

# 1. 结构

@babel/generator的目录结构如下:

- generators // 对不同种类的node有不同的生成模式
  - base.js
  - class.js
  - expression.js
  ...
- node // 主要是对空格和小括号的判断
  - index.js
  - parentheses.js
  - whitespace.js
- buffer.js  // Buffer类,存储最后的返回信息,包含操作_buf的方法
- printer.js // Printer类,中间层,包含print方法
- index.js // Generator类,继承自Printer,导出generate方法

# 2. 核心思想

@babel/generator内部维护了一个_buf,就是code字符串。遍历AST,根据Node节点的类型不同去增加或改变这个_buf,最后将其返回。

# 3. 运行机制

下面是一个简单的例子:

const { parse } = require("@babel/parser");
const generate = require("@babel/generator").default;

const a = "const a = 1;";
const ast = parse(a);
const code = generate(ast)

console.log('code', code)

通过@babel/parser拿到AST,然后调用@babel/generatorgenerate方法。

这个AST在上篇关于@babel/parser (opens new window)文章中介绍过,主要由FileProgramVariableDeclaratorVariableDeclaratorNumericLiteral几种Node构成。

{
  "type": "File",
  "start": 0,
  "end": 11,
  "program": {
    "type": "Program",
    "start": 0,
    "end": 11,
    "sourceType": "module",
    "interpreter": null,
    "body": [
      {
        "type": "VariableDeclaration",
        "start": 0,
        "end": 11,
        "declarations": [
          {
            "type": "VariableDeclarator",
            "start": 6,
            "end": 11,
            "id": {
              "type": "Identifier",
              "start": 6,
              "end": 7,
              "name": "a"
            },
            "init": {
              "type": "NumericLiteral",
              "start": 10,
              "end": 11,
              "extra": {
                "rawValue": 1,
                "raw": "1"
              },
              "value": 1
            }
          }
        ],
        "kind": "const"
      }
    ],
  },
}

调用generate后,先实例化一个Generator,然后调用其上的generate方法。它又调用了super.generate方法,也就是Printer类中定义的generate

export default function generate(
  ast: t.Node,
  opts?: GeneratorOptions,
  code?: string | { [filename: string]: string },
): any {
  const gen = new Generator(ast, opts, code);
  return gen.generate();
}   


class Generator extends Printer {
  generate() {
    return super.generate(this.ast);
  }
}

class Printer {
  generate(ast) {
    this.print(ast);
    this._maybeAddAuxComment();

    return this._buf.get();
  }
}

之后进入print (opens new window)方法,主要是根据node.type获取printMethod,这里的node.typeFile,即这里的printMethod是一个名为File的函数。

class Printer {
  print(node, parent?) {
    if (!node) return;

    const oldConcise = this.format.concise;
    if (node._compact) {
      this.format.concise = true;
    }

    const printMethod = this[node.type];
    // ...

    const loc = isProgram(node) || isFile(node) ? null : node.loc;
    this.withSource("start", loc, () => {
      printMethod.call(this, node, parent);
    });
    // ...
  }
}

这个File (opens new window)函数来自于generators目录,就是针对node的不同类型做的不同操作。

import * as generatorFunctions from "./generators";

Object.assign(Printer.prototype, generatorFunctions);

接着调用this.withSource,在它的回调中调用了printMethodwithSource方法主要是对sourcemap生成的处理,如果不需生成sourcemap,直接调用cb,这里是File函数。

class Printer {
    withSource(prop: string, loc: any, cb: () => void): void {
    this._catchUp(prop, loc);

    this._buf.withSource(prop, loc, cb);
  }
}


class Buffer {
  withSource(prop: string, loc: t.SourceLocation, cb: () => void): void {
  if (!this._map) return cb();
  // ...
  }
}

File函数判断有无node.program,有的话,执行this.print(node.program.interpreter, node),也就又回到print方法中。我们的例子中有node.program,但是node.program.interpreternull,所以再次进入print方法时什么也没做,直接返回。

接着执行this.print(node.program, node),这次进入print方法主要是执行Program (opens new window)方法。

export function File(this: Printer, node: t.File) {
  if (node.program) {
    this.print(node.program.interpreter, node);
  }

  this.print(node.program, node);
}

export function Program(this: Printer, node: t.Program) {
  this.printInnerComments(node, false);

  this.printSequence(node.directives, node);
  if (node.directives && node.directives.length) this.newline();

  this.printSequence(node.body, node);
}

这次node上没有directives属性,所以会调用this.printSequence(node.body, node)。之所以不用print方法,是因为node.body是数组类型。

printSequence中调用了printJoin方法,printJoin中依次遍历传入的nodes,调用_printNewlinethis.print方法。

再次进入print中,会调用VariableDeclaration (opens new window)方法。

class Printer {
  printSequence(
    nodes,
    parent,
    opts: {
      statement?: boolean;
      indent?: boolean;
      addNewlines?: Function;
    } = {},
  ) {
    opts.statement = true;
    return this.printJoin(nodes, parent, opts);
  }

  printJoin(nodes: Array<any> | undefined | null, parent: any, opts: any = {}) {
    if (!nodes?.length) return;

    if (opts.indent) this.indent();

    const newlineOpts = {
      addNewlines: opts.addNewlines,
    };

    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (!node) continue;

      if (opts.statement) this._printNewline(true, node, parent, newlineOpts);

      this.print(node, parent);

      if (opts.iterator) {
        opts.iterator(node, i);
      }

      if (opts.separator && i < nodes.length - 1) {
        opts.separator.call(this);
      }

      if (opts.statement) this._printNewline(false, node, parent, newlineOpts);
    }

    if (opts.indent) this.dedent();
  }
}

VariableDeclaration (opens new window)方法在generators/statement.js中,会先调用this.word(node.kind)word方法会调用Buffer中的_append方法。也就是在_buf中添加一个字符——node.kind,这里就是const

然后调用this.space()添加一个空格。

接着调用this.printList(node.declarations, node, { separator }

export function VariableDeclaration(
  this: Printer,
  node: t.VariableDeclaration,
  parent: t.Node,
) {
  if (node.declare) {
    this.word("declare");
    this.space();
  }

  this.word(node.kind);
  this.space();

  let hasInits = false;
  if (!isFor(parent)) {
    for (const declar of node.declarations as Array<any>) {
      if (declar.init) {
        hasInits = true;
      }
    }
  }
  let separator;
  if (hasInits) {
    separator =
      node.kind === "const"
        ? constDeclarationIndent
        : variableDeclarationIndent;
  }

  this.printList(node.declarations, node, { separator });

  if (isFor(parent)) {
    if (isForStatement(parent)) {
      if (parent.init === node) return;
    } else {
      if (parent.left === node) return;
    }
  }

  this.semicolon();
}

class Printer {
  word(str: string): void {
    if (
      this._endsWithWord ||
      (this.endsWith(charCodes.slash) && str.charCodeAt(0) === charCodes.slash)
    ) {
      this._space();
    }

    this._maybeAddAuxComment();
    this._append(str);

    this._endsWithWord = true;
  }

  _append(str: string, queue: boolean = false) {
    this._maybeAddParen(str);
    this._maybeIndent(str);

    if (queue) this._buf.queue(str);
    else this._buf.append(str);

    this._endsWithWord = false;
    this._endsWithInteger = false;
  }
}

class Buffer {
  append(str: string): void {
    this._flush();
    const { line, column, filename, identifierName, force } =
      this._sourcePosition;
    this._append(str, line, column, identifierName, filename, force);
  }

  _append(
    str: string,
    line: number,
    column: number,
    identifierName?: string | null,
    filename?: string | null,
    force?: boolean,
  ): void {
    this._buf += str;
    this._last = str.charCodeAt(str.length - 1);

    let i = str.indexOf("\n");
    let last = 0;

    if (i !== 0) {
      this._mark(line, column, identifierName, filename, force);
    }

    while (i !== -1) {
      this._position.line++;
      this._position.column = 0;
      last = i + 1;

      if (last < str.length) {
        this._mark(++line, 0, identifierName, filename, force);
      }
      i = str.indexOf("\n", last);
    }
    this._position.column += str.length - last;
  }
}

printListprintSequence的区别是,printList会提供separator,也就是在拼接的时候会提供连接符,比如,;

这里会遍历node.declarations,调用print方法。例子中node.declarations中只有一个元素,其typeVariableDeclarator,也就是会调用VariableDeclarator (opens new window)方法。

class Printer {
  printList(
    items,
    parent,
    opts: { separator?: Function; indent?: boolean; statement?: boolean } = {},
  ) {
    if (opts.separator == null) {
      opts.separator = commaSeparator;
    }

    return this.printJoin(items, parent, opts);
  }
}

VariableDeclarator方法主要逻辑有:

  • 调用this.print(node.id, node),这里node.idtypeIdentifierNode
  • node.init存在的话,调用spacetoken方法等,这里node.inittypeNumericLiteralNode
export function VariableDeclarator(this: Printer, node: t.VariableDeclarator) {
  this.print(node.id, node);
  if (node.definite) this.token("!"); // TS
  this.print(node.id.typeAnnotation, node);
  if (node.init) {
    this.space();
    this.token("=");
    this.space();
    this.print(node.init, node);
  }
}

我们已经知道print方法主要是调用与node.type同名的函数,这里会调用Identifier (opens new window)函数和NumericLiteral (opens new window)函数,最终为_buf增加a = 1

export function Identifier(this: Printer, node: t.Identifier) {
  this.exactSource(node.loc, () => {
    this.word(node.name);
  });
}

export function NumericLiteral(this: Printer, node: t.NumericLiteral) {
  const raw = this.getPossibleRaw(node);
  const opts = this.format.jsescOption;
  const value = node.value + "";
  if (opts.numbers) {
    this.number(jsesc(node.value, opts));
  } else if (raw == null) {
    this.number(value); // normalize
  } else if (this.format.minified) {
    this.number(raw.length < value.length ? raw : value);
  } else {
    this.number(raw);
  }
}

最后调用this._buf.get()codemap等信息返回。

class Buffer {
  get(): any {
    this._flush();

    const map = this._map;
    const result = {
      code: this._buf.trimRight(),
      map: null,
      rawMappings: map?.getRawMappings(),
    };

    if (map) {
      Object.defineProperty(result, "map", {
        configurable: true,
        enumerable: true,
        get() {
          return (this.map = map.get());
        },
        set(value) {
          Object.defineProperty(this, "map", { value, writable: true });
        },
      });
    }

    return result;
  }
}

# 4. 流程图

# 四、总结

本文简单介绍了@babel/generator主要逻辑,其可视为分层架构:

  • Buffer层维护核心的_buf,提供操作_buf_queueappend等方法;
  • Printer为中间的抽象层,调用底层Buffer中提供的append等方法,并提供printprintList等方法;
  • Generator为顶层,提供对外API。

生成code的思想就是从顶层FileProgram开始,分析Node.type,递归调用对应的生成函数,不断增加或修改_buf,最后返回。

# 五、系列文章

  1. Babel基础 (opens new window)
  2. Babel源码解析之@babel/core (opens new window)
  3. Babel源码解析之@babel/parser (opens new window)
  4. Babel源码解析之@babel/traverse (opens new window)
  5. Babel源码解析之@babel/generator