TopicsBabel.jsBabel Templates

Babel Templates

@babel/types is a package that contains a lot of utility functions for AST nodes.

@babel/template is a tiny but incredibly useful module. It allows you to write strings of code with placeholders that you can use instead of manually building up a massive AST.

Templates and placeholders

We can see in the example src/template/hello-babel-template.mjs that the template variables IMPORT_NAME and SOURCE in the template object buildRequire are filled with the ASTs built using babel-types and stored in the variables identifier and stringLiteral. The resulting AST ast is used to generate the code using @babel/generator.

src/template/hello-babel-template.mjs
import template from "babel-template";
import _generate from "@babel/generator";
const generate = _generate.default;
 
import * as _t from "babel-types";
const t = _t.default;
 
import compast from "compact-js-ast"
 
const buildRequire = template(`
  var IMPORT_NAME = require(SOURCE);
`);
 
const identifier = t.identifier("myModule"); // See https://babeljs.io/docs/babel-types#identifier
const stringLiteral = t.stringLiteral("my-module");
 
//console.log(compast(identifier, { parse: false, }));
//console.log(compast(stringLiteral, { parse: false, }));
 
const ast = buildRequire({
  IMPORT_NAME: identifier,
  SOURCE: stringLiteral
});
 
console.log(generate(ast).code); // var myModule = require("my-module");

You can use two different kinds of placeholders:

  • syntactic placeholders (e.g. %%name%%) or
  • identifier placeholders (e.g. NAME).

@babel/template supports both those approaches by default, but they can’t be mixed. If you need to be explicit about what syntax you are using, you can use the syntacticPlaceholders option.

The execution produces:

➜  babel-learning git:(main) node src/template/hello-babel-template.mjs
var myModule = require("my-module");

See also

Replacing a node with multiple nodes

The following plugin replaces the return statement with two statements: a let statement and a return statement.

src/manipulation/replacewithmultiple-plugin.cjs
module.exports = function (babel) {
  return {
    name: "ast-transform2", // not required
    visitor: {
      ReturnStatement(path) {
        if (path.node.argument.type == "BinaryExpression" && path.node.argument.left.name  == "n") {
          path.replaceWithMultiple([
            babel.template(`let a = N`)({N: path.node.argument.left }),
            babel.template(`return 4*a`)()
          ])
        }
      }
    }
  };
}

When executed with input:

➜  babel-learning git:(main) cat src/manipulation/square2.js
let square = n => { return n * n; }

We get:

➜ babel-learning git:(main) npx babel src/manipulation/square2.js --plugins=./src/manipulation/replacewithmultiple-plugin.cjs

"use strict";
 
let square = n => {
  let a = n;
  return 4 * a;
};

Note: When replacing an expression with multiple nodes, they must be statements. This is because Babel uses heuristics extensively when replacing nodes which means that you can do some pretty crazy transformations that would be extremely verbose otherwise.

See section replacewithmultipleat file /src/manipulating-ast-with-js/README.md