preface

There are many articles written about Babel in the community, some of them very good, and I have been inspired by them myself. But one problem I found was that this kind of article introduced a lot of basic concepts of Babel. To be honest, it was difficult for some children who didn’t know much about Babel to understand after reading it. The most important thing was that AFTER reading it, I didn’t know how to write a Babel plug-in. So this is how to write a Babel plug-in from 0 to 1. By the end of this article, hopefully you will have a general understanding of Babel. What is Babel? How does Babel work? And you can implement a simple Babel plug-in yourself.

What is the Babel

Babel is a JavaScript compiler, which means you give Babel some code, Babel does some transformation, and it returns you some new code. For example, we commonly convert ES5+ code to some code before ES5+.

Processing steps for Babel

As shown in the figure, Babel goes through three processing steps: parse, transform and generate.

parsing

Parsing through two steps of lexical analysis and syntax analysis, the input code generates abstract syntax number (AST). AST can be understood as a node tree describing a code, as shown in the following example:

We enter

const a = 1
Copy the code

After being parsed, a node tree with the following structure is generated (some attributes indicating the location of the node are removed for easy viewing). Details can be viewed using this tool

{
	"type": "VariableDeclaration"."declarations": [{"type": "VariableDeclarator"."id": {
				"type": "Identifier"."name": "a"
			},
			"init": {
				"type": "Literal"."value": 1."rawValue": 1."raw": "1"}}]."kind": "const"
}

Copy the code

The contents of each {“type”:””} package can be treated as a Node.

conversion

You get the AST abstract syntax tree, which is essentially a Node tree that describes your code, and you can traverse it through the tree for code transformation (adding, updating, removing nodes, etc.), which is where the Babel plug-in really takes care of

generate

The transformed AST is still an AST, so we also need to generate the AST as string code

In actual combat

There’s a lot more to Babel basics, and I think that’s enough to start with, so let’s start developing a simple Babel transformation.

As mentioned above, Babel provides corresponding methods for the three steps of Babel: parse, transform, and generate.

  • @babel/parser provides parsingparse
  • @babel/traverse offers conversionstraverse
  • @babel/generator provides generationgenerate

We’re going to implement a plug-in that introduces the entire component code

import { Select as MySelect, Pagination } from 'UI';
// import UI2 from 'xxx-ui';
import * as UI from 'xxx-ui';
Copy the code

The processing is in the form of on-demand processing as follows

import MySelect from "/MySelect/MySelect.js";
import Pagination from "/Pagination/Pagination.js"; // import UI2 from 'xxx-ui';
import * as UI from 'xxx-ui';
Copy the code

First: build a development environment

Here I use codesandBox to write online, visit here and import the required dependency packages.

const parse = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');
Copy the code

Among them, @babel/types is a Lodash library for AST nodes, which contains methods for constructing, validating, and transforming AST nodes. This tool library contains thoughtful tool methods that are useful for writing logic that processes AST.

Second: parse the code

const code = `import { Select as MySelect, Pagination } from '';
// import UI2 from 'xxx-ui';
import * as UI from 'xxx-ui';
`;
const ast = parse(code);
Copy the code

Third: Transform the code

This is the key step, this is where all of our transformation is

traverse(ast, {
  ImportDeclaration(path) {
    // Get the original component name
    const source = path.node.source.value;
    // get Select as MySelect, Pagination
    const specifiers = path.node.specifiers;
    / / import specifiers there are three kinds of forms, ImportSpecifier, ImportNamespaceSpecifier, ImportDefaultSpecifier
    // Get whether the specifiers are namespace types, like import * as UI from 'xxx-ui'
    const isImportNamespaceSpecifier = t.isImportNamespaceSpecifier(
      specifiers[0]);// Get speciFIERS are default export types, like import UI2 from 'xxx-ui'
    const isImportDefaultSpecifier = t.isImportDefaultSpecifier(specifiers[0]);
    if(! isImportNamespaceSpecifier && ! isImportDefaultSpecifier) {const declarations = specifiers.map(specifier= > {
        // Cache individual component names
        let localName = specifier.local.name;
        // Splice the import path
        let newSource = `${source}/${localName}/${localName}.js`;
        // Construct a new ImportDeclaration node
        return t.importDeclaration(
          [t.importDefaultSpecifier(specifier.local)],
          t.stringLiteral(newSource)
        );
      });
      // Replace the old AST with the new ASTpath.replaceWithMultiple(declarations); }}});Copy the code

The second argument to the traverse method is what we’re going to do with a specific node. The idea here is that when we traverse a node as a visitor, we’re actually visiting a path, not a specific node. So in our example we have two ImportDeclaration nodes, but we only write one handler, because here the handler will be executed twice.

Fourth: generation

Finally, we need to regenerate the code from the transformed AST

let newCode = generate(ast).code;
console.log(newCode);
Copy the code

Fifth: Running

The input terminal

node ./src/index.js
Copy the code

You can see the resulting code

import MySelect from "/MySelect/MySelect.js";
import Pagination from "/Pagination/Pagination.js"; // import UI2 from 'xxx-ui';

import * as UI from 'xxx-ui';
Copy the code

Actual project reference

We’ve completed a Babel plug-in, so how do we introduce it into the project? In fact, the steps described above are just an inside look at how the Babel plug-in works. In the real project, we only need to expose a method that returns an object containing the visitor property.

Visitor A visitor is an object that defines a series of methods for accessing nodes in a tree structure

// myPlugin.js const babel = require(@babel/core'); const t = require('@babel/types'); Export default function() {return {visitor: {ImportDeclaration(path, state) {},}}; };Copy the code

Then the plugin in babel-loader is introduced

options:{
    plugins:[
        ["myPlugin"]]}Copy the code

How does it work? The core module of Babel provides a transform method. The APi can be seen here. Just pass in the visitor object, which will perform parsing, conversion, and generation by default.

const visitor = require('visitor.js');
const babel = require('@babel/core');
const result = babel.transform(code, {
	plugins: [visitor],
});
Copy the code

Advanced example

React address vue react address vue react address vue react address

Finally, if you are interested in multi-terminal development, we have a team in our wechat store, which is engaged in multi-terminal unified development research. If you are interested, you can also join us and have a look. There are many examples and articles related to Babel