上一篇: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 逻辑执行完毕。