Detailed examples of introductory development of Babel plug-ins

Introduction to Babel

Babel is a Javascript compiler, a code-to-code compiler, commonly known as a "conversion compiler".

Babel's working process

Babel has three main processes: parse, transform and generate.

  • Code parsing
    AST is constructed by lexical analysis and grammatical analysis.
  • Code Conversion
    Processing AST, processing tools, plug-ins and so on is in this stage of code conversion, return to the new AST.
  • code generation
    Traverse the AST and output the code string.

    So we need some knowledge of AST to develop Babel plug-ins.

AST

In this whole process, it is around the abstract grammar tree (AST). In Javascritp, AST, in short, is an Object that records the syntactic structure of the code. Interested students are available https://astexplorer.net/ To experience deeply
For example, the following code:

import {Button} from 'antd';

import Card from 'antd/button/lib/index.js';

After converting to AST, the following is done.

{
  "type": "Program",
  "start": 0,
  "end": 253,
  "body": [
    {
      "type": "ImportDeclaration",
      "start": 179,
      "end": 207,
      "specifiers": [
        {
          "type": "ImportSpecifier",
          "start": 187,
          "end": 193,
          "imported": {
            "type": "Identifier",
            "start": 187,
            "end": 193,
            "name": "Button"
          },
          "local": {
            "type": "Identifier",
            "start": 187,
            "end": 193,
            "name": "Button"
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 200,
        "end": 206,
        "value": "antd",
        "raw": "'antd'"
      }
    },
    {
      "type": "ImportDeclaration",
      "start": 209,
      "end": 253,
      "specifiers": [
        {
          "type": "ImportDefaultSpecifier",
          "start": 216,
          "end": 220,
          "local": {
            "type": "Identifier",
            "start": 216,
            "end": 220,
            "name": "Card"
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 226,
        "end": 252,
        "value": "antd/button/lib/index.js",
        "raw": "'antd/button/lib/index.js'"
      }
    }
  ],
  "sourceType": "module"
}

Plug-in Development Ideas

  • Determine the type of nodes we need to process
  • Processing Node
  • Returns a new node

Simple plug-in structure

The plug-in must be a function, as required by the official documentation, in the following form:

module.exports = function ({ types: t }) {
    return {
        visitor: {
            ImportDeclaration(path, source){
                //todo
            },
            FunctionDeclaration(path, source){
                //todo
            },
        }    
    }
}

Types comes from the @babel/types toolkit class and is mainly used to determine the types of grammars in the process of creating AST.

Implementation examples

Many students have used babel-plugin-import, which helps us to use some JS class libraries to achieve on-demand loading. In fact, the plug-in helps us to do the following code conversion:

//from
import {Button } from 'antd/es/button';

//to
import Button from 'antd/es/button';
import 'antd/es/button/style'; 

Let's first look at the AST differences between the two to help us have a clear understanding of the transformation:

Before conversion:

 
[{
      "type": "ImportDeclaration",
      "start": 6,
      "end": 45,
      "specifiers": [
        {
          "type": "ImportSpecifier",
          "start": 14,
          "end": 20,
          "imported": {
            "type": "Identifier",
            "start": 14,
            "end": 20,
            "name": "Button"
          },
          "local": {
            "type": "Identifier",
            "start": 14,
            "end": 20,
            "name": "Button"
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 28,
        "end": 44,
        "value": "antd/es/button",
        "raw": "'antd/es/button'"
      }
    }]
    

After conversion:

 [{
  "type": "ImportDeclaration",
  "start": 5,
  "end": 41,
  "specifiers": [
    {
      "type": "ImportDefaultSpecifier",
      "start": 12,
      "end": 18,
      "local": {
        "type": "Identifier",
        "start": 12,
        "end": 18,
        "name": "Button"
      }
    }
  ],
  "source": {
    "type": "Literal",
    "start": 24,
    "end": 40,
    "value": "antd/es/button",
    "raw": "'antd/es/button'"
  }
},
{
  "type": "ImportDeclaration",
  "start": 46,
  "end": 76,
  "specifiers": [],
  "source": {
    "type": "Literal",
    "start": 53,
    "end": 75,
    "value": "antd/es/button/style",
    "raw": "'antd/es/button/style'"
  }
}]

Comparing the two trees, we should have a general idea. In the conversion process, we also need some parameters, which provide some custom configurations in the configuration file (package.json or. babelrc), such as on-demand loading of antd:

["import",{libraryName:"antd",libraryDireactory:"es","style":"css"}]

Now let's try to implement this plug-in:

module.exports = function ({ types: t }) {
    return {
        visitor: {
            ImportDeclaration(path, source) {
                
                //Take out the parameters
                const { opts: { libraryName, libraryDirectory, style="css" } } = source;
                //Get the old AST node
                let node = path.node

                if(node.source.value !== libraryName){
                    return;
                }
                //Create an array to store in the newly generated AST
                let newImports = [];
                
                path.node.specifiers.forEach(item => {
                    newImports.push(t.importDeclaration([t.importDefaultSpecifier(item.local)], t.stringLiteral(`${libraryName}/${libraryDirectory}/${item.local.name}`)));
                    newImports.push(t.importDeclaration([t.importDefaultSpecifier(item.local)], t.stringLiteral(`${libraryName}/${libraryDirectory}/${style}/index.css`)))
                });
                path.replaceWithMultiple(newImports);
                return newImports;
            }
        }
    }
}

Now, we have experienced the simple implementation of babel production inspection. If you are interested in learning more, the official website of babel provides a lot of details.

Tags: Javascript JSON

Posted on Tue, 06 Aug 2019 20:19:11 -0700 by hi2you