preface

This article focuses on how to write a Babel plug-in, without too much description of some intellectual concepts, so let’s get right to the point.

Write one of these plug-ins

For example, we have the following code:

const a = 1;
const b = 10;
const c = (d) = > {
  var e = 20;
  console.log(d);
}
Copy the code

We are going to write a plugin to convert const to var, arrow function to function, and finally to change the parameter in arrow function and d in function to m. That is, the converted code is as follows:

var a = 1;
var b = 10;
var c = function (m) {
  var e = 20;
  console.log(m);
};
Copy the code

Some preparation points

What is the Babel

Babel can be thought of as a converter that converts some code into some other code. The transformation process can be divided into three steps:

  1. Source code parsing generates AST
  2. The AST is converted to the new AST (the Babel plug-in modifies the code along the way)
  3. The new AST generates new code

AST(Abstract Syntax Tree)

An AST is a tree describing code, where each node represents a location in the code, such as const a = 1 via AstExplorer; When transformed into AST, the following node tree can be obtained:

From the figure, it can be seen that each node has a Type field, which represents the type of node each node is. AST types are far more than the three listed in the figure, and more types can be consulted in the manual here.

Some key methods and tools for the Babel plug-in

  1. Visitor

    Visitor objects are like a large collection of listening methods. When Babel processes each of the AST nodes, if there is a method in the Visitor that declares a node type, it executes the method when Babel processes the AST node type and the node type method, for example:

    Visitor: {Identifier(path) {// This method is executed when the node type is Identifier. console.log('type is identifier') } }Copy the code
  2. Path

    The first parameter in the visitor object declaration method is a parameter named PATH. The path parameter contains information about the node and methods for adding, updating, moving, and deleting nodes to the node. More about the path definition can be viewed directlyThe path of source codeTo check.

  3. babel-types

    Babel-types is a library of tools for dealing with AST nodes, including methods for constructing and validating AST nodes. More information can be found on Babel’s website.

Start writing plug-ins

The plug-in capabilities described above can be divided into three points:

  • Convert const to var
  • Convert arrow functions to function functions
  • Change the parameter in the arrow function and the variable d in the function to m

Let’s do it first, by converting const to var:

  1. Add the node listening method of the Visitor object

    We mentioned that we need to add our corresponding type method to the visitor object, but we have a problem. How do we know what type method we need? We can actually put our source code in astExplorer and convert it into an AST, and then find the corresponding Type field, and we can see what methods we need to write in that visitor object. Here we generate the AST from the source code mentioned above:

    var a = 1;
    var b = 10;
    var c = function (m) {
      var e = 20;
      console.log(m);
    };
    Copy the code

    As you can see from the AST, there are three array elements with type VariableDeclaration underneath the body. The VariableDeclaration type is one of the corresponding VariableDeclaration typesbabel-typesManual, take a look at the definition:

    You can seebabel-typesVariable constructors are defined int.variableDeclaration(kind, declarations), the parameter kind can be taken"var" | "let" | "const".

    So here we add a listener type method to the visitor object, called a VariableDeclaration:

    visitor: {
      VariableDeclaration(path) {
        console.log(path)
      }
    }
    Copy the code
  2. Convert the AST

    We have added a listener for variables in the visitor, and we also know that the node has a variable of type VariableDeclaration and a variable of type KIND that identifies how the variable is declared. Const var var var var var var var var var var var var var var var var var var

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            path.node.kind = 'var'}}}Copy the code

    Run the code to see what we want:

    In the picture above we have succeededconstconvertvar, the transformation mode is the nodekindThe field is directly assigned tovarIn addition to this method, we can also achieve the same effect by replacing the current node. Babel-types is a library for handling AST nodes, including methods for constructing and validating AST nodesbabel-typesDefine the constructor method to construct onevariableDeclarationNode, the specific method has been shown in the screenshot above, here directly on the code:

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            // types is the babel-types utility library
            let node = types.VariableDeclaration('var', path.node.declarations)
            // replaceWith is the method to replace the nodepath.replaceWith(node); }}}Copy the code

    The same transformation can be achieved with the above code, where the replaceWith method is a method to replace nodes and can be viewed in the source code.

Next we will implement the arrow function to function function, also follow the above two steps

  1. Add the node listening method of the Visitor object

    In the same way, we write the listener method from the arrow function type in the AST tree

    The arrow function type is ArrowFunctionExpression.

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            path.node.kind = 'var'}},ArrowFunctionExpression(path) {
          console.log(path)
      }
    }
    Copy the code
  2. Convert the AST

    To convert an arrow function to a function function, this is obviously requiredbabel-typesThe function constructor is used to construct a function node, and then replace it with the original node.

    Now that we know the function constructor API, we can convert directly:

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            path.node.kind = 'var'}},ArrowFunctionExpression(path){ path.replaceWith(types.functionExpression(path.node.id, path.node.params, path.node.body)); }}Copy the code

    Look at the effect

Now that the above two functions have been implemented, let’s implement how to change the parameter in the arrow function and the variable in the function d to the variable m. Again, follow the above two steps:

  1. Add the node listening method of the Visitor object

    Again from the AST to find the corresponding arrow function type of listening method writing

    Namely: the Identifier

    So the listening mode is as follows:

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            path.node.kind = 'var'}},ArrowFunctionExpression(path) {
          path.replaceWith(types.functionExpression(path.node.id, path.node.params, path.node.body));
      },
      Identifier(path) {
          console.log(path)
      }
    }
    Copy the code
  2. Convert the AST

    From the AST we know that a node of type Identifier has a name attribute to identify the node name, so we need to change this node name by reassigning the name attribute, i.e. :

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            path.node.kind = 'var'}},ArrowFunctionExpression(path) {
          path.replaceWith(types.functionExpression(path.node.id, path.node.params, path.node.body));
      },
      Identifier(path) {
          if (path.node.name === 'd') { // If the variable name is d, adjust to m
            path.node.name = 'm'; }}}Copy the code

    Take a look at the effect:

    As shown in the figure, variable d is also successfully converted to m, but there is a problem here. If I declare a variable D outside the arrow function in the source code, the variable D will also be changed to m, for example:

    const a = 1;
    const b = 10;
    const c = (d) = > {
      var e = 20;
      console.log(d);
    }
    const d = 30;
    Copy the code

    Execute the code above:

    As you can see, our plugin also changed the d variable to m elsewhere, so we need to change the conversion code here, because we only need to convert the d variable in the arrow function, so we should convert the current scope in the way the arrow function listens:

    visitor: {
      VariableDeclaration(path) {
        if(path.node.kind === 'const') { // If kind is const, then var
            path.node.kind = 'var'}},ArrowFunctionExpression(path) {
          // traverse the current node
          path.traverse({
            Identifier(path) {
                if (path.node.name === 'd') {
                   path.node.name = 'm'; }}}); path.replaceWith(types.functionExpression(path.node.id, path.node.params, path.node.body)); }}Copy the code

    Execute the code

    In this case, the variable d outside the arrow function will not be converted.

conclusion

At this point, we have completed a Babel plug-in, the source can be obtained here: github.com/canfoo/babe…

Some readers may think this is a simple plugin implementation, and it is, but the purpose of this article is to show how the Babel plugin is developed, so hopefully a little experience will be helpful 😊.