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 isimportMethod
Code 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,
customStyleName
This 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,