preface

In writing Your First Babel Plugin, we learned about the theoretical underpinnings of the Babel Plugin. Next, we’ll get into more practice. This article will implement the following two plug-ins:

  • babel-plugin-remove-console– Remove console from JS code;
  • babal-plugin-import– Use antD and Element to implement a way to automatically convert the writing of import to import on demand during compilation.

The preparatory work

In the previous article, we simply developed a Babel plug-in, and we need to use various helper functions, methods, and so on in our daily development. Here’s a quick review of what you need to prepare.

Again, familiarize yourself with the Babel transformation process

If you are still a little vague about syntax trees and Babel, I recommend you finish the following article

  • Babel series 2 – The use of advanced Babel guide
  • AST in Modern JavaScript
  • JavaScript abstract syntax tree AST – Syntax tree nodes are described in detail and can even be used as documentation
  • Babel plugin -AST (abstract syntax tree – more practical
  • Babel plugin Manual – Participated in Babel open source, documentation is very clear, support a variety of language types
  • Babel part of the concept of Chinese interpretation

@babel/helper-xxxx-xxxx

  • @babel/helper-plugin-utils AIDS plug-in development, but only with a wrapper layer

@babel/plugin-xxxx-xxxx

Why plugin-syntax-xxxx-xxxx and plugin-transform-xxxx-xxxx plugins? — Transform plugin vs Syntax plugin in Babel

Plugin-syntax-xxxx-xxxx is the base class of plugin-transform-xxxx-xxxx, which is used to set the parsing mode and corresponding syntax, so that it can be correctly processed by @babel/parser. We’re going to use

  • @babel/plugin-syntax-jsx

@babel/types

With a lot of functionality, and especially a lot of definitions, you may be a little confused when you first see an AST, so the following concepts must have an impact

  • Definitions — Definitions (including aliases for some node names)
  • Builders — node generation tools
  • Validators – Node judgment tools
  • Implies — node assertion tool, which is the wrapper of validators, and results with results
  • .

The definitions of common nodes are listed below

FunctionDeclaration
function a() {}
Copy the code
FunctionExpression
var a = function() {}
Copy the code
ArrowFunctionExpression (ArrowFunctionExpression)
() = > {}Copy the code
CallExpression (calling expressions)
a()
Copy the code
Identifier (Variable Identifier)
Var a(where a is an Identifier)Copy the code
. I’m not going to give you an example. If you’re interested, you can go to the official website, or go to my websitegithub

What is the difference between Declaration, Statement and Expression?

Most programming languages provide both expressions and statements. Expressions can always return a value; Statements do nothing but work. A statement consists of one or more expressions

  • Declaration: Declaration and definition. The main functions are FunctionDeclaration and VariableDeclaration
  • Expression: Expression. There are FunctionExpression, ObjectExpression, ArrayExpression, ThisExpression and so on
  • Statement: is our code Statement that does the work, does not return, can have multiple expressions

For example, if 1+2 equals 3, then 1+2 expression, 3 is the value of expression

Babel-plugin-remove-console and babel-plugin-import will be introduced in this chapter

Demo1 – babel-plugin-remove-console

Install dependencies first

We will of course start by initializing a project and installing the necessary dependencies, which we need to install because of the plug-in package rollup

npm i --save  @babel/cli @babel/core @babel/preset-env @babel/types rollup
Copy the code

Then configure.babelrc.js

const removePlugin = require('./lib/remove')
const presets = ['@babel/preset-env']
const plugins = [
  [
    removePlugin,
    {
      ignore: ['warn'],
    },
  ],
]

module.exports = { presets, plugins }
Copy the code

The test source

console.log('dfsafasdf22')
console.warn('dddd')
let a = 'sdfasdfsdf'
Copy the code

Compile the results

"use strict";

console.warn('dddd');
var a = 'sdfasdfsdf';

Copy the code

Plug-in core code

const removePlugin = function ({ types: t, template }) { return { name: 'transform-remove-console', visitor: {// The name of the node to access // The accessor is injected with two arguments by default, path (analogous to dom), State ExpressionStatement(path, {opts}) {// Get the object and property of console.log The property name for the log const {object, property} = path. The node. The expression. The callee / / if the expression statement object name is not for the console, If (object.name! == 'console') return // If not, delete this statement if (! opts.ignore || ! opts.ignore.length || ! opts.ignore.includes(property.name) ) path.remove() }, }, } }Copy the code
ExpressionStatement Key nodes are resolved

  • Specific syntax tree view
  • Please see the source code for details.

You can also run this code at Babael

Demo2 – babel-plugin-import

There are two implementations of this, both very similar

  • Implementation in Element UI – babel-plugin-Component
  • Implementation in ANTD – babel-plugin-import

It is recommended to see the implementation of ANTD, this paper is also imitation of ANTD

What does this plugin do

Babel-plugin-import implements on-demand loading and automatic introduction of styles. In our daily use of the ANTD style, we just need to:

import { Button } from 'antd';
Copy the code

The Button style was introduced via this plugin and compiled to:

var _button = require('antd/lib/button');
require('antd/lib/button/style');
Copy the code

How does it parse?

Daily look AST comes, AST links

We can see several key nodes as follows:

What we want to listen on in Vivsitor is the ImportDeclaration type node, gathering all associated dependencies.

babel-plugin-importparsing

1. Initialize plugin parameters

Const Program = {// ast entry Enter (path, {opts = defaultOption}) {const {libraryName, libraryDirectory, style, TransformToDefaultImport,} = opts / / initialize the plug-in instance if (! plugins) { plugins = [ new ImportPlugin( libraryName, libraryDirectory, style, t, transformToDefaultImport ), } applyInstance('ProgramEnter', arguments, this)}, // ast exit exit() {applyInstance('ProgramExit', arguments, this) }, }Copy the code

2. Just listenImportDeclaration| CallExpression

['ImportDeclaration', 'CallExpression'].forEach((method) => {
    result.visitor[method] = function () {
      applyInstance(method, arguments, result.visitor)
    }
  })
Copy the code

3. ListenImportDeclaration

ImportDeclaration(path, state) { const { node } = path; if (! node) return; // Import package const {value} = node.source; Const {libraryName} = this; // babel-type utility function const {types} = this; // Internal state const pluginState = this.getpluginState (state); If (value === libraryName) {node.specifiers import what node.specifiers. ForEach (spec => {// Check whether the node is of the ImportSpecifier type. , that is, whether the braces the if (types. IsImportSpecifier (spec)) {/ / collect rely on / / namely pluginState specified. The Button = Button / / local name Is an imported alias, For example, MyButton // imported. Name of import {Button as MyButton} from 'antd' is the real exported variable name pluginState.specified[spec.local.name] = spec.imported.name; } else {// ImportDefaultSpecifier and ImportNamespaceSpecifier pluginState.libraryObjs[spec.local.name] = true; }}); pluginState.pathsToRemove.push(path); }}Copy the code

4. Check whether it is used

CallExpression(path, state) { const { node } = path; const file = (path && path.hub && path.hub.file) || (state && state.file); // method caller's name const {name} = node.callee; // Internal state const pluginState = this.getpluginState (state); // If the method caller is of type Identifier if (this.t.i. sIdentifier(node.callee)) {if (pluginstate.specified [name]) {node.callee = this.importMethod(pluginState.specified[name], file, pluginState); } // Specifier node.arguments = node.arguments. Map (arg => {const {name: argName} = arg; if ( pluginState.specified[argName] && path.scope.hasBinding(argName) && path.scope.getBinding(argName).path.type === 'ImportSpecifier') {// Find specifier Return this. ImportMethod (pluginState. Specified [argName], file, pluginState); } return arg; }); }Copy the code

5. Content replacement

AddSideEffect and addDefault are two utility functions in babel-helper-module-imports

AddSideEffect creates the import method

import "source"

import { addSideEffect } from "@babel/helper-module-imports";
addSideEffect(path, 'source');
Copy the code
addDefault

import hintedName from "source"

import { addDefault } from "@babel/helper-module-imports"; // If nameHint is not set, the function will generate a UUID for the name itself, as in '_named' addDefault(path, 'source', {nameHint: "hintedName"}).Copy the code

The core code isimportMethodCode implementation

Antd-desgin import source code address

1 Let’s look at converting component names

/ / whether or not to use the underscore '_' or dash '-' as a connector, give priority to underline const transformedMethodName = this. Camel2UnderlineComponentName? transCamel(methodName, '_') : this.camel2DashComponentName ? transCamel(methodName, '-') : methodName;Copy the code

2. Convert the import

/ / 1 enclosing transformToDefaultImport assignment during plug-in initialization, The default value is true / / 2 is the default. Use the default name pluginState selectedMethods [methodName] = this. TransformToDefaultImport / / eslint-disable-line ? addDefault(file.path, path, { nameHint: methodName }) : addNamed(file.path, methodName, path); If (this.customStylename) {const stylePath = winPath(this.customStylename (transformedMethodName)); addSideEffect(file.path, `${stylePath}`); } else if (this.styleLibraryDirectory) { const stylePath = winPath( join(this.libraryName, this.styleLibraryDirectory, transformedMethodName, this.fileName), ); addSideEffect(file.path, `${stylePath}`); } else if (style === true) { addSideEffect(file.path, `${path}/style`); } else if (style === 'css') { addSideEffect(file.path, `${path}/style/css`); } else if (typeof style === 'function') { const stylePath = style(path, file); if (stylePath) { addSideEffect(file.path, stylePath); }}Copy the code
  • For example,customStyleNameThis implementation

Is to support the following parameters

{ libraryName: 'antd', libraryDirectory: 'lib', style: true, customName: (name: string) => { if (name === 'Button') { return 'antd/lib/custom-button'; } return `antd/lib/${name}`; }}Copy the code
Their own simple implementation to code as follows

The source address

conclusion

Babel-plugin-import is a plug-in that can be added to the Babel plugin.

If the first two are easy, consider converting React into applets or vue code. See JsX-Compiler, @tarojs/ Transformer-wx, and React to micro applets: From the React class definition to the Component invocation.

Hopefully, after reading this article, you will have a clear understanding of some simple plug-in logic,