Skip to content

带你玩转 babel 工具链(三)@babel/generator

一、前言

在前面两章, 我们一起学习了parsertraverse, 还差最后一步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, {});

最后输出的内容如下, 产生了最终的codesourcemapimage.png

选项

并且generator还支持一些参数

js
const output = generator.default(ast, {
  sourceMaps: true,
  // ...
});
const output = generator.default(ast, {
  sourceMaps: true,
  // ...
});
选项说明
auxiliaryCommentAfter添加注释到生成代码的最后
auxiliaryCommentBefore添加注释到生成代码的最前
comments生成的代码是否包含注释
compact生成的代码是否包含多于的空格
concise设置为 true 可减少空白(但不如 opts.compact)
decoratorsBeforeExportdecoratorsBeforeExport 为 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这个库,生成代码映射。

参考

Babel 插件通关秘籍