preface

Babel can convert ES6 specifications that are not yet implemented by the browser to run ES5 specifications, or JSX to HTML structures that are recognized by the browser. How does Babel do this? I will explain the whole process by developing a simple Babel plugin. I hope you have a new understanding of how Babel plugins work and AST.

Babel run phase

From the above analysis, we can probably guess that Babel runs as: raw code -> Modify code, so there are three important steps we need to know in this transformation process.

parsing

We first need to lexical, parse, and convert JavaScript strings into a more computer-readable representation called an “Abstract syntax tree (AST),” and we use the Babylon parser to do this.

conversion

When JavaScript is converted from a string to an AST, it is easier to browse, analyze, and regularly modify it to convert to a new AST according to our needs. Babel-traverse is a great conversion tool that allows us to manipulate the AST very easily.

generate

Finally, we reverse process the modified AST to generate JavaScript strings, and the conversion process is complete. In this step, we use the Babel-Generator module.

What is the AST

I’ve heard it before: “If you get good at an AST, you can really do whatever you want.” I didn’t understand its meaning until I really understood AST, and I realized that AST is incredibly important to programming languages.

In computer science, abstract syntax tree (OR AST), or syntax tree, is a tree-like representation of the abstract syntax structure of source code, especially the source code of a programming language. Each node in the tree represents a structure in the source code.

The grammar is “abstract” because it does not represent every detail that occurs in real grammar.

JavaScript programs are usually made up of a series of characters. We can use matching characters ([], {}, ()), paired characters (”, “”), and indentation to make parsing easier, but for a computer, these characters are just numbers in memory and can’t handle these high-level problems. So we need to find a way to translate that into a structure that computers can understand.

Let’s look briefly at the following code:

let a = 2;
a * 8
Copy the code

To convert this to an AST, we use the astExplorer online AST conversion tool to get the following tree structure:

For a more vivid representation, we transform it into a more intuitive structure graph:

The root node of the AST is Program. This example contains two parts:

  1. A VariableDeclarator that assigns the Identifier A to a NumericLiteral 3.

  2. A BinaryExpression statement, described as the Identifier a, operator +, and NumericLiteral 5.

This is just a simple example. In practice, the AST will be a giant tree of nodes. Converting the source code in the form of a string into a tree structure will make it easier for computers to process. Then convert the AST into string source code. This is how the Babel plug-in works. The reason why it can “do whatever you want” is that it can convert the original code into whatever code you want according to the specified logic.

Develop the Babel plugin Demo

Basic concept

A typical Babel plug-in structure looks like this:

export default function(babel) {
  var t = babel.types;
  return {
    visitor: {
      ArrayExpression(path, state) {
          path.replaceWith(
            t.callExpression(
              t.memberExpression(t.identifier('mori'), t.identifier('vector')),
              path.node.elements
            )
          );
      },
      ASTNodeTypeHere(path, state) {}
    }
  };
};
Copy the code

We should pay attention to the following points:

  • babel.types: Operates AST nodes, such as creating, transforming, and verifying them.
  • vistorBabel recursively visits each node of the AST, called visitor only because there is a similar design pattern called visitor pattern, as in the code aboveArrayExpression, when traversing toArrayExpressionNode, which triggers the corresponding function.
  • path: Path is an OBJECT of the AST node. It can be used to obtain the attributes of the node and the association between nodes.
  • state: indicates the plug-in state. You can use state to obtain configuration items in the plug-in.
  • ArrayExpression, ASTNodeTypeHere: indicates the node type in the AST.

Demand analysis

Since it is a Demo, our requirements are very simple. The name of the Bable plug-in we developed is vincePlugin. When using it, we can configure the parameters of the plug-in, so that the plug-in can transform according to the parameters we configured.

Plugins: [[vincePlugin, {name:'vince'}]]Copy the code

Conversion effect:

Var fool = [1, 2, 3]; // translate to => var fool = vince.init(1,2,3)Copy the code

Initialize the project

For easy reading, the source code has been uploaded to GitHub: Babel-plugin-demo

With these concepts and requirements in mind, we are ready to start Babel plug-in development by creating a project directory, initializing NPM, and installing Babel-core:

mkdir babel-plugin-demo && cd babel-plugin-demo
npm init -y
npm install --save-dev babel-core
Copy the code

Create the plugin.js Babel plug-in file, where we will write the transformation logic:

// plugin.js
module.exports = function(babel) {
    var t = babel.types;
    return{ visitor: { // ... }}; };Copy the code

Create the original code index.js

Var fool = [1, 2, 3];Copy the code

Create the test.js test function, where we test the plug-in:

// test.js
var fs = require('fs');
var babel = require('babel-core');
var vincePlugin = require('./plugin');

// read the code from this file
fs.readFile('index.js'.function(err, data) {
  if(err) throw err;

  // convert from a buffer to a string
  var src = data.toString();

  // use our plugin to transform the source
  var out = babel.transform(src, {
    plugins: [
        [vincePlugin, {
            name: 'vince'}}]]); //print the generated code to screen
  console.log(out.code);
});
Copy the code

Let’s test the Babel plug-in’s transformation output with node test.js.

Node contrast

  • The original codeVar fool = [1, 2, 3];The nodes analyzed by AST are shown as follows:

  • Converted codevar bar = vince.init(1, 2, 3);, the nodes analyzed through AST are shown as follows:

We marked the original AST structure diagram with red to distinguish the transformed AST structure diagram. Now we can clearly see the nodes we need to replace. Replace ArrayExpression with CallExpression. Add a MemberExpression to the CallExpression node and retain the original three NumericLiterals.

The plugin to write

First, we need to replace ArrayExpression, so add an ArrayExpression method to Vistor.

// plugin.js
module.exports = function(babel) {
    var t = babel.types;
    return {
      visitor: {
        ArrayExpression: function(path, state) { // ... }}}; };Copy the code

When Babel traverses the AST and finds a node method on a visitor, it triggers this method and passes the context (path, state) to the function where we analyze and replace the node:

// plugin.js
module.exports = function(babel) {
    var t = babel.types;
    return {
      visitor: {
        ArrayExpression: function(path, State) {// replace the node path.replacewith (// create callExpression t.acclexpression (); t.memberExpression(t.identifier(state.opts.name), t.identifier('init')), path.node.elements ) ); }}}; };Copy the code

We need to replace ArrayExpression with CallExpression, which can be generated by t.callistxpression (callee, arguments), The first parameter is MemberExpression, which is generated by T.emberexpression (object, property), and then the original three NumericLiteral are set as the second parameter, thus accomplishing our requirements.

Note that state.opts.name refers to the config parameter that was set when the plugin was configured.

See the documentation for babel-types for more information on transformations and node properties

Test the plugin

If we go back to test.js and run node test.js, we get:

node test.js

=> var bar = vince.init(1, 2, 3);

Copy the code

At this point, our simple Babel plugin is complete. The actual development requirements are much more complex, but the main logic is still based on the concepts above.

conclusion

It’s back to the beginning: “If you get good at the AST, you can really do whatever you want.” We can use the AST to convert the original code into whatever code we need. You can even create a private ESXXX and add new specifications that you create. The AST is not a very complicated technical task, and can be considered a “labor task” in large part, because it may require writing a lot of logic code to meet complex transformation requirements.

In addition to understanding how the Babel Plugin works and putting a Plugin into practice, we also understood the concept of an AST and realized its power.

Reference:

Babel User manual

Babel plugin manual