babel源码分析 - generator

5 篇文章 1 订阅
4 篇文章 0 订阅

上一篇:babel源码分析 - traverse

这是 babel 解析的最后一篇,坚持就是胜利✌️。

在这篇中我会梳理下 babel 是如何需要借助 generator 方法将处理好的 AST 重新转化为代码,从而完成整个流程。

首先还是回到 babel-core 的 run 方法中,当执行 generateCode(config.passes, file) 方法时最终执行的下面的代码:

// babel-generator/src/index.ts

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();
}

当调用 generate 时执行的是 Printer 中的 generate。

// Generator
generate() {
  // 🌵🌵🌵 执行这里 🌵🌵🌵
  return super.generate(this.ast);
}

// -------------我是快乐的分割线--------------

// Printer
generate(ast) {
  // 🌵🌵🌵 执行这里 🌵🌵🌵
  this.print(ast);
  this._maybeAddAuxComment();

  return this._buf.get();
}

然后将 ast 传入到实例上 print 方法中开始生成逻辑。

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

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

  const printMethod = this[node.type];
  if (!printMethod) { }

  this._printStack.push(node);

  const oldInAux = this._insideAux;
  this._insideAux = !node.loc;
  this._maybeAddAuxComment(this._insideAux && !oldInAux);

  let needsParens = n.needsParens(node, parent, this._printStack);
  if (
    this.format.retainFunctionParens &&
    node.type === "FunctionExpression" &&
    node.extra &&
    node.extra.parenthesized
  ) {
    needsParens = true;
  }
  if (needsParens) this.token("(");

  this._printLeadingComments(node);

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

  this._printTrailingComments(node);

  if (needsParens) this.token(")");

  // end
  this._printStack.pop();

  this.format.concise = oldConcise;
  this._insideAux = oldInAux;
}

这里的 withSource 会多次执行,分别对应着 File,Program,VariableDeclaration

// babel-generator/src/generators/base.ts

export function File(this: Printer, node: t.File) {
  if (node.program) {
    // Print this here to ensure that Program node 'leadingComments' still
    // get printed after the hashbang.
    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);
}

当执行 printSequence 的时候会跳到 printer 中继续执行

// packages/babel-generator/src/printer.ts

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();
}

// -------------我是快乐的分割线--------------

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

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

  const printMethod = this[node.type];
  if (!printMethod) {}

  this._printStack.push(node);

  const oldInAux = this._insideAux;
  this._insideAux = !node.loc;
  this._maybeAddAuxComment(this._insideAux && !oldInAux);

  let needsParens = n.needsParens(node, parent, this._printStack);
  if (
    this.format.retainFunctionParens &&
    node.type === "FunctionExpression" &&
    node.extra &&
    node.extra.parenthesized
  ) {
    needsParens = true;
  }
  if (needsParens) this.token("(");

  this._printLeadingComments(node);

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

  this._printTrailingComments(node);

  if (needsParens) this.token(")");

  // end
  this._printStack.pop();

  this.format.concise = oldConcise;
  this._insideAux = oldInAux;
}

// -------------我是快乐的分割线--------------

withSource(prop: string, loc: any, cb: () => void): void {
  this._catchUp(prop, loc);
	// 🌵🌵🌵 执行这里 🌵🌵🌵
  this._buf.withSource(prop, loc, cb);
}

之后会跳到 buffer.ts 执行 withSource 方法,然后执行参数中的 cb 方法

// babel-generator/src/buffer.ts

withSource(prop: string, loc: t.SourceLocation, cb: () => void): void {
  // 🌵🌵🌵 执行这里 🌵🌵🌵
  if (!this._map) return cb();

  // Use the call stack to manage a stack of "source location" data because
  // the _sourcePosition object is mutated over the course of code generation,
  // and constantly copying it would be slower.
  const originalLine = this._sourcePosition.line;
  const originalColumn = this._sourcePosition.column;
  const originalFilename = this._sourcePosition.filename;
  const originalIdentifierName = this._sourcePosition.identifierName;

  this.source(prop, loc);

  cb();

  if (
    // If the current active position is forced, we only want to reactivate
    // the old position if it is different from the newest position.
    (!this._sourcePosition.force ||
      this._sourcePosition.line !== originalLine ||
      this._sourcePosition.column !== originalColumn ||
      this._sourcePosition.filename !== originalFilename) &&
    // Verify if reactivating this specific position has been disallowed.
    (!this._disallowedPop ||
      this._disallowedPop.line !== originalLine ||
      this._disallowedPop.column !== originalColumn ||
      this._disallowedPop.filename !== originalFilename)
  ) {
    this._sourcePosition.line = originalLine;
    this._sourcePosition.column = originalColumn;
    this._sourcePosition.filename = originalFilename;
    this._sourcePosition.identifierName = originalIdentifierName;
    this._sourcePosition.force = false;
    this._disallowedPop = null;
  }
}

其实就是上面 print 方法中的 printMethod.call(this, node, parent); 这段代码,其中 printMethod 如下:

const printMethod = this[node.type];

// -------------我是快乐的分割线--------------

// Expose the node type functions and helpers on the prototype for easy usage.
Object.assign(Printer.prototype, generatorFunctions);

此时 node 是一个变量节点,所以此时 type 就是 VariableDeclaration,这里的 this 是一个 Printer 实例,它本身并没有这个方法,而是通过原型获取的 generatorFunctions 中的方法,如下

// babel-generator/src/generators/statements.ts

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;
  // don't add whitespace to loop heads
  if (!t.isFor(parent)) {
    for (const declar of node.declarations as Array<any>) {
      if (declar.init) {
        // has an init so let's split it up over multiple lines
        hasInits = true;
      }
    }
  }

  let separator;
  if (hasInits) {
    separator =
      node.kind === "const"
        ? constDeclarationIndent
        : variableDeclarationIndent;
  }

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

  if (t.isFor(parent)) {
    // don't give semicolons to these nodes since they'll be inserted in the parent generator
    if (t.isForStatement(parent)) {
      if (parent.init === node) return;
    } else {
      if (parent.left === node) return;
    }
  }

  this.semicolon();
}

这个方法会根据节点生成对应的代码然后挂载到 _buf 上,具体如下图:
在这里插入图片描述

至此 generate 逻辑执行完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值