For those of you who are currently working with ES6 code, the Babel plug-in is very important in the process of compiling ES6 code into ES5 code, and the code that we end up generating from Babel depends on what the plug-in does in this layer. Before exploring how this works, let’s look at some of the basics.
Abstract syntax tree
Babel’s workflow can be illustrated in the following diagram, where the code is first parsed into an abstract syntax tree (AST) via Babylon, followed by some traversal and analysis transformations (the main process), and finally generates new general code from the transformed AST.
Understanding the AST is important here, and the reason we need to convert our code to the AST is so that computers can understand it better. We can look at the following structure of the code parsed into the AST:
function abs(number) {
if (number >= 0) { // test
return number; // consequent
} else {
return -number; // alternate
}
}
Copy the code
All AST root nodes are Program nodes. As you can see from the above image, the structure of the parsed AST is very detailed for each Node type. The Babylon AST document describes each Node type in detail, and you can find the information you need from each Node type here. In this example, we focus on the contents of the function declaration. The IfStatement corresponds to the if… For the else block, we first judge the condition (test), which is a simple binary expression, and our branches continue from this condition; aggressively represents branches with true, alternate represents branches with false; aggressively represents branches with false; The last two branches return each from the ReturnStatement node.
Understanding the type of each node of an AST is crucial for subsequent plug-in writing. AST is usually quite complex, and the simple function definition above also generates a large AST. For some complex programs, we can use AstExplorer to help us analyze the structure of the AST.
Traverse the nodes
Node traversal in plug-ins requires understanding the concepts of visitor, which selects the desired node from a variety of node types, and path, which accesses the relationships between nodes.
visitor
Babel traverses the tree with babel-traverse. For each branch of the AST tree we traverse down to the end, then traverse up to exit the traversed node to find the next branch. Babel provides us with a visitor object to get the specific nodes we need in the AST to access, for example I just want to access if… Else generated node, which we can specify to fetch in the visitor:
const visitor = {
IfStatement() {
console.log('get if'); }};Copy the code
Continuing with the traversal described above, each node will be accessed twice, once down for enter and once up for exit. Therefore, each node actually has enter and exit methods. In actual operation, you need to pay attention to some problems caused by this traversal method. The above example is the shorthand for omiting Enter.
const visitor = {
IfStatement: {
enter() {},
exit() {}}}Copy the code
path
In the visitor pattern, our visits to nodes are actually visits to node paths, and in this pattern we pass path as a parameter to the node selector. Path represents a connection between two nodes, and this object allows us to access the node, its parent node, and a series of methods (dom-like operations) related to node operations.
var babel = require('babel-core');
var t = require('babel-types');
const code = `d = a + b + c`;
const visitor = {
Identifier(path) {
console.log(path.node.name); // d a b c
}
};
const result = babel.transform(code, {
plugins: [{
visitor: visitor
}]
});
Copy the code
Replace the node
With knowledge of AST and knowledge of visitor and path, you can write a simple Babel plug-in. We’ll call the abs function above with math.abs, which is natively supported.
The AST structure of abs(-8) is characterized by ExpressionStatement (ExpressionStatement).
{
type: "ExpressionStatement",
expression: {
type: "CallExpression",
callee: {
type: "Identifier",
name: "abs"
},
arguments: [{
type: "UnaryExpression",
operator: "-",
prefix: true,
arguments: {
type: "NumericLiteral",
value: 8
}
}]
}
}
Copy the code
The ABOVE AST structure can be viewed in ASTExplorer.
We can see that expression under expression statements is mainly CallExpression, so we also need to create a function CallExpression. Furthermore, math. abs is a binary operation expression, which is of type MemberExpression. The above two AST nodes can be quickly created using some of the methods provided in babel-types.
// Creates a function call expression. T. allExpression(// creates an object attribute reference. T. email Expression(T.identifier ('Math'), t.identifier('abs'), // Original node function call arguments path.node.arguments)Copy the code
Finally, we need to filter the nodes that do not match the function call, and filter out the function call whose name is not equal to ABS. Because Babel traverses recursively, if the filtering is not limited, the program will always run and finally report the error that the call stack exceeds the threshold.
RangeError: unknown: Maximum call stack size exceeded
The final code is as follows:
var babel = require('babel-core');
var t = require('babel-types'); const code = `abs(-8); `; const visitor = { CallExpression(path) {if(path.node.callee.name ! = ='abs') return;
path.replaceWith(t.CallExpression(
t.MemberExpression(t.identifier('Math'), t.identifier('abs')), path.node.arguments )); }}; const result = babel.transform(code, { plugins: [{ visitor: visitor }] }); // Math.abs(-8) console.log(result.code);Copy the code
The above example uses the Transform API to parse the transformation directly to generate the new code, and when writing the Babel plug-in separately, the exposed parameters usually contain the common babel-types object for use.
export default function({ types: t }) {
return {
visitor: {
CallExpression(path) {
if(path.node.callee.name ! = ='abs') return;
path.replaceWith(t.CallExpression(
t.MemberExpression(t.identifier('Math'),
t.identifier('abs')), path.node.arguments )); }}}; }Copy the code
Amazing, you can see whether the plugin works directly online!
Reference article:
Webpack4+Babel7 optimized for 70% speed babel-Handbook
Write a Babel plug-in from scratch
AST babel-types babel-plugin-handbook How to upload a NPM package