This article will introduce the basic compilation knowledge and the operating principles of babel-loader
What is babel-loader?
As an old-fashioned packaging tool, babel-loader is probably already very familiar with it. It looks like this.
//
= {
// ...Other configurations
module: {
rules: [
{
test: /\.js$/, // Match all .js files
exclude: /node_modules/, // Exclude node_modules directory
use: 'babel-loader' // Use babel-loader to handle
}
]
}
};
How babel-loader works
babel-loader actually calls Babel's core library @babel/core to process the received code. Babel first uses a parser (such as @babel/parser) to parse JavaScript code into an abstract syntax tree (AST).
AST will not be discussed in depth here. Simply put, AST is to abstract each syntax element in the code (such as variable declarations, function definitions, expressions, etc.) into a tree-like data structure to facilitate subsequent analysis and conversion of the code.
For example, for codeconst message = 'Hello, World!'
; will be parsed into containingVariableDeclaration
、VariableDeclarator
、Identifier
andNumericLiteral
AST of the node.
// source code
const message = 'Hello, World!';
// Corresponding part AST structure
{
"type": "VariableDeclaration",
"kind": "const",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "message"
},
"init": {
"type": "StringLiteral",
"value": "Hello, World!"
}
}
]
}
AST View Online, You can view the relationship between source code and AST online through this website
Babel adds, deletes and checks AST nodes through plugins and presets. This preset can be understood as a prefabricated plugin for babel, which is essentially the same as the plugin; for example: Use@babel/preset-env
Presets can convert ES6+ code into backwards compatible JavaScript code to suit different browsers and environments.
This modification process for AST nodes is also very similar to the modification process for dom tree nodes. The plug-in is also the core of Babel conversion, and all real modification processes occur here.
After the plug-in conversion, Babel uses the code generator (@babel/generator
) Regenerate the converted AST to JavaScript code. This newly generated code is the final code after conversion
The basic process is as follows:
-
-> @babel/core
Receive processing files -
-> @beble/parser
Convert source files to AST -
-> Plugin translation/modify AST
Add, delete, modify and check AST through plug-in -
-> @babel/generator
Translate the modified AST into source code
You can see that the workflow of babel-loader is actually to help us call the babel core library.
In fact, it is possible to complete the translation task without relying on webpack and babel, but we have to manually adjust the file input and output process, and webpack+babel-loader helps us omit this tedious process.
More specific referenceBabel usage guide
What is the babel-loader plugin? What can the babel-loader plugin do?
To sum up, after understanding the workflow of babel-loader, these two questions will be answered easily.
The babel-loader plug-in is an open entry for AST nodes. You can easily add, delete, modify and check AST nodes here. It allows us to focus on the core work of file compilation and skip many other tedious steps.
Using the babel-loader plug-in well can make it easier for us to complete many file compilation tasks.
How to make a babel-loader plugin?
There are many APIs for babel plug-in, and it will be more abstract at first. However, the most core APIs for babel plug-in are actually only three
vistor accessor
It defines how to access and modify AST nodes. The accessor is a bit similar to the configuration in webpack. For example, if { test: /.ts/} is configured in webpack, then the compiler will only check the .ts file, and the same is true for the vistor accessor. You've configured itVariableDeclaration
Accessories, all variable declaration statements will enter here, and are configuredFunctionDeclaration
Accessories, all function declarations will enter here
For example:function vistor(){}
Visitor: {
FunctionDeclaration(path) {
// The name here is vistor
const functionName = ;
('Access to function declaration:', functionName);
},
};
Here are the more common vistors:
Identifier
Function: represents variable name, function name, attribute name and other identifiers. It can be used to rename variables, check specific identifiers, and other operations.
const visitor = {
Identifier(path) {
if ( === 'oldVariable') {
= 'newVariable';
}
}
};
Literal
Function: Represents various literals, such as strings, numbers, booleans, null, etc. Can be used to modify the value of a literal.
const visitor = {
Literal(path) {
if (typeof === 'string') {
= ();
}
}
};
VariableDeclaration
Function: Represents variable declaration statements, such as var, let and const declarations. Can be used to modify the type of a variable declaration, add or delete variable declarations.
Example:
const visitor = {
VariableDeclaration(path) {
if ( === 'var') {
= 'let';
}
}
};
FunctionDeclaration
Function: represents a function declaration statement. It can be used to modify function names, parameters, function bodies, etc.
Example:
const visitor = {
FunctionDeclaration(path) {
const newFunctionName = ('newFunction');
= newFunctionName;
}
};
BinaryExpression
Function: Represents binary expressions, such as a + b, a * b, etc. Can be used to modify operators or operands.
Example:
const visitor = {
BinaryExpression(path) {
if ( === '+') {
= '-';
}
}
};
CallExpression
Function: Represents function call expressions, such as func(), (), etc. It can be used to modify the call function name, parameters, etc.
Example:
const visitor = {
CallExpression(path) {
if ( === 'oldFunction') {
= 'newFunction';
}
}
};
IfStatement
Function: represents if statement. Can be used to modify the contents of a conditional expression, if block, or else block.
Example:
const visitor = {
IfStatement(path) {
const newTest = (true);
= newTest;
}
};
ReturnStatement
Function: represents the return statement. Can be used to modify the return value.
Example:
const visitor = {
ReturnStatement(path) {
const newReturnValue = (0);
= newReturnValue;
}
};
path
path is a very important API, it is used to check node path information and obtain AST node information, etc., this can be understood as equivalent to a window object in web development
1. Access node information
-
Get the node itself:pass
You can directly access the AST node currently traversed to, and then obtain various attributes of the node.
= function(babel) {
const { types: t } = babel;
return {
Visitor: {
Identifier(path) {
const node = ;
('Identifier node name:', );
}
}
};
};
-
Get the parent node:use
The parent node of the current node can be obtained, which is very useful when you need to process the current node based on the parent node information.
= function(babel) {
const { types: t } = babel;
return {
Visitor: {
Identifier(path) {
const parentNode = ;
if ((parentNode)) {
('The current Identifier is part of the variable declaration');
}
}
}
};
};
2. Node operation
-
Replace nodes:
(newNode)
Method is used to replace the current node with a new node.
= function(babel) {
const { types: t } = babel;
return {
visitor: {
Identifier(path) {
if ( === 'oldName') {
const newNode = ('newName');
(newNode);
}
}
}
};
};
-
Delete nodes:
()
Methods can be used to remove the current node from the AST.
= function(babel) {
const { types: t } = babel;
return {
Visitor: {
// Assume that all calls are to be removed
CallExpression(path) {
const callee = ;
if ((callee) &&
(, { name: 'console' }) &&
(, { name: 'log' })
) {
();
}
}
}
};
};
3. Traversal control
-
Continue to traverse the child nodes:
(visitor)
Methods allow continued traversal in the subtree of the current node, using a custom accessor object.
= function(babel) {
const { types: t } = babel;
return {
Visitor: {
FunctionDeclaration(path) {
const customVisitor = {
Identifier(subPath) {
('Identifier inside Function:', );
}
};
(customVisitor);
}
}
};
};
-
Skip child node traversal:
()
The method can skip the child node traversal of the current node and directly enter the next sibling node.
= function(babel) {
const { types: t } = babel;
return {
Visitor: {
ObjectExpression(path) {
// Skip the child node traversal of the ObjectExpression node
();
}
}
};
};
4. Scope Management
-
Get scope:
You can get the scope where the current node is located. Scope objects provide many methods for managing variables and identifiers.
= function(babel) {
const { types: t } = babel;
return {
Visitor: {
Identifier(path) {
const scope = ;
const binding = ();
if (binding) {
('Variable binding information:', binding);
}
}
}
};
};
-
Create a new identifier:
(name)
Methods are used to generate a unique identifier to avoid naming conflicts.
= function(babel) {
const { types: t } = babel;
return {
visitor: {
FunctionDeclaration(path) {
const newId = ('newFunction');
= newId;
}
}
};
};
types
It is mainly used to create, verify and operate abstract syntax tree (AST) nodes, and is also a very frequently used API, equivalent to a document object in web development.
Create an AST node
-
Create an identifier node:use
(name)
An identifier node can be created to represent variable names, function names, etc.
const babel = require('@babel/core');
const t = ;
// Create an identifier node named 'message'
const identifier = ('message');
-
Create a function declaration node:
(id, params, body)
Can be used to create function declaration nodes, whereid
It's the function name,params
is an array of parameters,body
It is a function body.
const id = ('add');
const param1 = ('a');
const param2 = ('b');
const params = [param1, param2];
const body = ([
(
('+', param1, param2)
)
]);
const functionDeclaration = (id, params, body);
Verify the AST node type
-
Determine whether the node is an identifier:
(node)
Used to determine whether a node is an identifier node.
const babel = require('@babel/core');
const t = ;
const node = ('test');
if ((node)) {
('This is an identifier node');
}
-
Determine whether the node is a function declaration:
(node)
It can determine whether a node is a function declaration node.
const id = ('multiply');
const param1 = ('x');
const param2 = ('y');
const params = [param1, param2];
const body = ([
(
('*', param1, param2)
)
]);
const functionNode = (id, params, body);
if ((functionNode)) {
('This is a function declaration node');
}
Operation AST node
-
Modify the name of the identifier node: The identifier node can be modified directly
name
property.
const babel = require('@babel/core');
const t = ;
const identifier = ('oldName');
= 'newName';
-
Modify the parameters of the function declaration node: Can modify the function declaration node
params
property.
const id = ('subtract');
const param1 = ('m');
const param2 = ('n');
const params = [param1, param2];
const body = ([
(
('-', param1, param2)
)
]);
const functionNode = (id, params, body);
// Add a new parameter
const newParam = ('c');
(newParam);
Assist in generating complex code structures
-
Generate conditional statements: Can be used
(test, consequent, alternate)
generateif
Statement.
const test = ('>', ('a'), ('b'));
const consequence = ([
(
(
(''),
[('a greater than b')]
)
)
]);
const alternate = ([
(
(
(''),
[('a is less than or equal to b')]
)
)
]);
const ifStatement = (test, consequent, alter);
If you need more API references or accessor features,Click on the detailed official document
Here is a simplest plugin demonstration:
//
= function (babel) {
// Deconstruct the type module from the babel object to operate the AST node
const { types: t } = babel;
return {
// The visitor object defines how to access and modify AST nodes
Visitor: {
// Take the Identifier node as an example here. This function will be executed when traversing to the Identifier AST node
Identifier(path) {
// path represents the path of the current node, containing the context information of the node
if ( === 'oldIdentifier') {
// If the name of the node is 'oldIdentifier', modify it to 'newIdentifier'
= 'newIdentifier';
}
}
}
};
};
Introduced in webpack
const path = require('path');
// Introduce the plugin we wrote
const myBabelPlugin = require('../my-babel-plugin/myBabelPlugin');
= {
// Entry file
entry: './src/',
// Output configuration
output: {
path: (__dirname, 'dist'),
filename: ''
},
module: {
rules: [
{
// Match .js files
test: /\.js$/,
// Exclude node_modules directory
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// Use the plugin we wrote
plugins: [myBabelPlugin]
}
}
}
]
}
};