Appearance
带你玩转 babel 工具链(三)@babel/generator
一、前言
在前面两章, 我们一起学习了parser
和traverse
, 还差最后一步generator
就可以把代码编译的整个流程串联起来。下面我们了解写@babel/generator
的用法和原理吧
往期回顾:
二、基础案例
js
const parser = require("@babel/parser");
const generator = require("@babel/generator");
const ast = parser.parse(`
const a: number = 1
`);
const output = generator.default(ast, {});
const parser = require("@babel/parser");
const generator = require("@babel/generator");
const ast = parser.parse(`
const a: number = 1
`);
const output = generator.default(ast, {});
最后输出的内容如下, 产生了最终的code
和sourcemap
选项
并且generator
还支持一些参数
js
const output = generator.default(ast, {
sourceMaps: true,
// ...
});
const output = generator.default(ast, {
sourceMaps: true,
// ...
});
选项 | 说明 |
---|---|
auxiliaryCommentAfter | 添加注释到生成代码的最后 |
auxiliaryCommentBefore | 添加注释到生成代码的最前 |
comments | 生成的代码是否包含注释 |
compact | 生成的代码是否包含多于的空格 |
concise | 设置为 true 可减少空白(但不如 opts.compact) |
decoratorsBeforeExport | decoratorsBeforeExport 为 false, export @decorator class Bar {} , decoratorsBeforeExport 为 true,@decorator export class Foo {} |
filename | 用于警告消息中 |
minified | 是否压缩 |
retainFunctionParens | 保留函数表达式周围的参数(可用于更改引擎解析行为) |
retainLines | 尝试在输出代码中使用与源代码中相同的行号(有助于保留堆栈跟踪) |
shouldPrintComment | 是一个函数,参数可以接收注释内容,如果函数返回 true,就会保留注释,否则删除,类似 comments 选项 |
sourceMaps | 是否生成 sourceMap |
sourceRoot | 设置 sourcemap url 相对根路径 |
三、实现@babel/generator
下面的代码中,定义了各种 AST 的生成函数,其实就是使用策略模式
这种设计模式,对于不同类型的节点使用不同的方法处理。
其中还实现了sourcemap
的生成,借助了source-map
这个库,分别在不同节点遍历时,创建一个 map
js
const { SourceMapGenerator } = require("source-map");
class Printer {
constructor(source, fileName) {
// 记录生成的代码,不断拼接
this.buf = "";
this.sourceMapGenerator = new SourceMapGenerator({
file: fileName + ".map.json",
});
this.fileName = fileName;
// 设置源码内容
this.sourceMapGenerator.setSourceContent(fileName, source);
this.printLine = 1;
this.printColumn = 0;
}
addMapping(node) {
if (node.loc) {
// 添加一个映射关系
this.sourceMapGenerator.addMapping({
generated: {
line: this.printLine, // 这里是转换后的行数
column: this.printColumn, // 这里是转换后的列数
},
source: this.fileName, // 文件名
original: node.loc && node.loc.start, // 转换前的起始位置
});
}
}
// 增加一个空格
space() {
this.buf += " ";
this.printColumn++;
}
// 换行
nextLine() {
this.buf += "\n";
this.printLine++;
this.printColumn = 0;
}
// 遍历 Program下的节点,并调用相应的方法拼接
Program(node) {
this.addMapping(node);
node.body.forEach((item) => {
// 执行对应节点的拼接方法
this[item.type](item) + ";";
// 行数+1
this.printColumn++;
// 换行
this.nextLine();
});
}
// 遍历声明语句
VariableDeclaration(node) {
if (!node.declarations.length) {
return;
}
this.addMapping(node);
// let var const
this.buf += node.kind;
// 空格
this.space();
// 遍历变量名
node.declarations.forEach((declaration, index) => {
if (index != 0) {
this.buf += ",";
this.printColumn++;
}
// 执行到 VariableDeclarator 拼接方法
this[declaration.type](declaration);
});
// 添加分号
this.buf += ";";
// 列数+1
this.printColumn++;
}
VariableDeclarator(node) {
this.addMapping(node);
// 执行到 Identifier 拼接
this[node.id.type](node.id);
// 添加赋值
this.buf += "=";
// 列数+1
this.printColumn++;
// 执行到 Identifier 或 NumericLiteral 等
this[node.init.type](node.init);
}
Identifier(node) {
this.addMapping(node);
this.buf += node.name;
}
FunctionDeclaration(node) {
this.addMapping(node);
// 拼接函数
this.buf += "function ";
this.buf += node.id.name;
this.buf += "(";
this.buf += node.params.map((item) => item.name).join(",");
this.buf += "){";
this.nextLine();
this[node.body.type](node.body);
this.buf += "}";
this.nextLine();
}
CallExpression(node) {
this.addMapping(node);
this[node.callee.type](node.callee);
this.buf += "(";
node.arguments.forEach((item, index) => {
if (index > 0) this.buf += ", ";
this[item.type](item);
});
this.buf += ")";
}
ExpressionStatement(node) {
this.addMapping(node);
this[node.expression.type](node.expression);
}
ReturnStatement(node) {
this.addMapping(node);
this.buf += "return ";
this[node.argument.type](node.argument);
}
BinaryExpression(node) {
this.addMapping(node);
this[node.left.type](node.left);
this.buf += node.operator;
this[node.right.type](node.right);
}
BlockStatement(node) {
this.addMapping(node);
node.body.forEach((item) => {
this.buf += " ";
this.printColumn += 4;
this[item.type](item);
this.nextLine();
});
}
NumericLiteral(node) {
this.addMapping(node);
this.buf += node.value;
}
}
const { SourceMapGenerator } = require("source-map");
class Printer {
constructor(source, fileName) {
// 记录生成的代码,不断拼接
this.buf = "";
this.sourceMapGenerator = new SourceMapGenerator({
file: fileName + ".map.json",
});
this.fileName = fileName;
// 设置源码内容
this.sourceMapGenerator.setSourceContent(fileName, source);
this.printLine = 1;
this.printColumn = 0;
}
addMapping(node) {
if (node.loc) {
// 添加一个映射关系
this.sourceMapGenerator.addMapping({
generated: {
line: this.printLine, // 这里是转换后的行数
column: this.printColumn, // 这里是转换后的列数
},
source: this.fileName, // 文件名
original: node.loc && node.loc.start, // 转换前的起始位置
});
}
}
// 增加一个空格
space() {
this.buf += " ";
this.printColumn++;
}
// 换行
nextLine() {
this.buf += "\n";
this.printLine++;
this.printColumn = 0;
}
// 遍历 Program下的节点,并调用相应的方法拼接
Program(node) {
this.addMapping(node);
node.body.forEach((item) => {
// 执行对应节点的拼接方法
this[item.type](item) + ";";
// 行数+1
this.printColumn++;
// 换行
this.nextLine();
});
}
// 遍历声明语句
VariableDeclaration(node) {
if (!node.declarations.length) {
return;
}
this.addMapping(node);
// let var const
this.buf += node.kind;
// 空格
this.space();
// 遍历变量名
node.declarations.forEach((declaration, index) => {
if (index != 0) {
this.buf += ",";
this.printColumn++;
}
// 执行到 VariableDeclarator 拼接方法
this[declaration.type](declaration);
});
// 添加分号
this.buf += ";";
// 列数+1
this.printColumn++;
}
VariableDeclarator(node) {
this.addMapping(node);
// 执行到 Identifier 拼接
this[node.id.type](node.id);
// 添加赋值
this.buf += "=";
// 列数+1
this.printColumn++;
// 执行到 Identifier 或 NumericLiteral 等
this[node.init.type](node.init);
}
Identifier(node) {
this.addMapping(node);
this.buf += node.name;
}
FunctionDeclaration(node) {
this.addMapping(node);
// 拼接函数
this.buf += "function ";
this.buf += node.id.name;
this.buf += "(";
this.buf += node.params.map((item) => item.name).join(",");
this.buf += "){";
this.nextLine();
this[node.body.type](node.body);
this.buf += "}";
this.nextLine();
}
CallExpression(node) {
this.addMapping(node);
this[node.callee.type](node.callee);
this.buf += "(";
node.arguments.forEach((item, index) => {
if (index > 0) this.buf += ", ";
this[item.type](item);
});
this.buf += ")";
}
ExpressionStatement(node) {
this.addMapping(node);
this[node.expression.type](node.expression);
}
ReturnStatement(node) {
this.addMapping(node);
this.buf += "return ";
this[node.argument.type](node.argument);
}
BinaryExpression(node) {
this.addMapping(node);
this[node.left.type](node.left);
this.buf += node.operator;
this[node.right.type](node.right);
}
BlockStatement(node) {
this.addMapping(node);
node.body.forEach((item) => {
this.buf += " ";
this.printColumn += 4;
this[item.type](item);
this.nextLine();
});
}
NumericLiteral(node) {
this.addMapping(node);
this.buf += node.value;
}
}
总结
@babel/generator
实现起来较为简单,核心在于借助了策略模式
,各个节点都有其对应的代码生成方法。
另外还使用了source-map
这个库,生成代码映射。