@babel/core is a versatile option if you need to modify js code in JS.
At @babel/core, it’s already integrated
ast
Tree Building (parse)ast
Tree traverse and Modification (traverse)ast
Node types and creation (types)ast
Output code (transfromFromAst)
And several common functions, and plug-in mode to achieve business logic decoupling.
In addition, @babel/ Generator is a separate AST code generation tool. It differs from transfromFromAst in that:
generator | transfrom |
---|---|
Can be based on any typeast Node output code |
Can only be based on integrityast The tree |
Code compression, quotation mark selection and other simple configuration | Complete code configuration, plug-in support |
In addition to @babel/core, another lightweight option is Acorn, and Babel uses Acorn for AST parsing.
The smaller Acorn, compressed to about 80KB, can be used on the Web; But the function is relatively simple, only to achieve AST parsing. Traversal of the AST tree needs to be implemented separately, and modification is performed directly on the AST tree.
Understand the AST syntax tree
Let’s first look at what an AST tree looks like:
const { parse, traverse, types, transformFromAstSync } = require('@babel/core')
const ast = parse('const a = 1')
console.log(JSON.stringify(ast, null.' '))
Copy the code
To make it easier to see, I’ve removed the number of lines of code, the start and end positions, and so on.
{
"type": "File"."errors": []."program": {
"type": "Program"."sourceType": "module"."interpreter": null."body": [{"type": "VariableDeclaration"."declarations": [{"type": "VariableDeclarator"."id": {
"type": "Identifier"."identifierName": "a"
},
"name": "a"
},
"init": {
"type": "NumericLiteral"."extra": {
"rawValue": 1."raw": "1"
},
"value": 1}}]."kind": "const"}]."directives": []},"comments": []}Copy the code
All nodes have a type attribute, and different types have different node structures.
Where, File -> Program is the inherent information of the complete AST document, so we can ignore it temporarily. Start with program. body, which is the body of the code.
VariableDeclaration
Start declaring variables, types (kind
) forconst
Contains the following variablesVariableDeclarator
id
A variable calleda
(id.name)init
Has an initial value (init is not null) of typenumber
(NumericLiteral)NumericLiteral
value
A value of1
VariableDeclarator
VariableDeclarator
- (Other variables of this declaration…)
Convert from AST to code
Since we have the complete AST documentation, we can use Transfrom directly for code generation:
const ast = parse('const a = 1')
const { code } = transformFromAstSync(ast)
console.log(code)
Copy the code
The output
% node babel.js
const a = 1;
Copy the code
I put a semicolon on it.
AST traversal
When we need to collect code information, such as analyzing dependencies to do tree-shaking, or when we need to change code, such as const/let to var, we walk through the AST to find and manipulate nodes.
const ast = parse('const a = 1')
traverse(ast, {
enter(nodePath) {
console.log(nodePath.type)
}
})
Copy the code
The output
% node babel.js
Program
VariableDeclaration
VariableDeclarator
Identifier
NumericLiteral
Copy the code
Node type method
The second argument to traverse is a map of the various node manipulation methods: name a method according to the node type, and the corresponding type of nodes will enter the method.
The special one is the Enter method, which all nodes enter before executing the method of their type.
traverse(ast, {
enter(nodePath) {
console.log(1, nodePath.type)
},
// Node type naming method
Identifier(nodePath) {
console.log(2, nodePath.type)
}
})
Copy the code
The output
% node babel.js
1 Program
1 VariableDeclaration
1 VariableDeclarator
1 Identifier
The node type method is executed after the Enter method
2 Identifier
1 NumericLiteral
Copy the code
NodePath
NodePath is a structure that describes node relationships, exposed through the node method on traverse.
Common attributes/methods include:
- NodePath. Node The node itself
- NodePath. ParentPath Indicates the parent node
- NodePath. Scope syntax context, can get global variables and other information
- Since the traversal NodePath. Traverse by ()
- NodePath. ReplaceWith () node replacement
- Remove () The node is deleted
AST changes
With traverse and exposed NodePath, we can modify the AST.
Modify Node directly
The Node Node is a reference-value property in the NodePath, and manipulating Node directly affects the final code generation. For example, we rename variable A to a:
const ast = parse('const a = 1')
traverse(ast, {
Identifier(nodePath) {
const { node } = nodePath
node.name = 'A'}})Copy the code
run
% node babel.js
const A = 1;
Copy the code
Even delete nodes
traverse(ast, {
Identifier(nodePath) {
const { node } = nodePath
node.name = 'A'
},
VariableDeclarator({ node }) {
delete node.init // node.init = null also works
},
VariableDeclaration({ node }) {
node.kind = 'var'}})Var A; '
Copy the code
According to practical experience, direct operation of nodes, especially complex nodes, will affect the traversal process and cause some unexpected problems. Therefore, it is recommended to operate complex nodes
Use NodePath
We use the method provided by NodePath to achieve the above effect:
traverse(ast, {
Identifier(nodePath) {
const { node } = nodePath
if(node.name === 'a') {
const newId = types.identifier('A')
nodePath.replaceWith(newId)
}
},
VariableDeclaration(nodePath) {
const { node } = nodePath
const { declarations, kind } = node
if(kind === 'const') {
const newNode = types.variableDeclaration('var', declarations)
nodePath.replaceWith(newNode)
}
},
NumericLiteral(nodePath) {
if(nodePath.parentPath && nodePath.parentPath.type === 'VariableDeclarator') {
nodePath.remove()
}
}
})
Copy the code
It looks more rigorous and elegant.
Create an AST node using types
After types., the first letter is lowercase, corresponding to the creation method of the node type; The first letter is uppercase, corresponding to the node type definition. @babel/core is very friendly to type and attribute hints and auto-complete.
// types.Identifier
types.identifier('A')
// types.VariableDeclaration
types.variableDeclaration('var', declarations)
Copy the code
Necessary condition judgment
Notice that in each node method, modification criteria are added.
Every time a new node is added or replaced, the traverse process will also go through and the loop will fall into an infinite loop without conditional judgment.
Maximum call stack size exceeded
Copy the code
The plug-in
Write a Babel plug-in
A plug-in is a function that returns a structure:
function plugin() {
return {
visitor: {
// ...}}}Copy the code
That visitor, that’s the second parameter to traverse.
const plugin = () = > {
return {
visitor: {
Identifier(nodePath) {
const { node } = nodePath
if(node.name === 'a') {
const newId = types.identifier('A')
nodePath.replaceWith(newId)
}
},
VariableDeclaration(nodePath) {
const { node } = nodePath
const { declarations, kind } = node
if(kind === 'const') {
const newNode = types.variableDeclaration('var', declarations)
nodePath.replaceWith(newNode)
}
},
NumericLiteral(nodePath) {
if(
nodePath.parentPath
&& nodePath.parentPath.type === 'VariableDeclarator'
) {
nodePath.remove()
}
}
}
}
}
Copy the code
The plug-in call
const ast = parse('const a = 1')
const { code } = transformFromAstSync(
ast,
null,
{ plugins: [ plugin ] } / / here
)
console.log(code)
Copy the code
run
% node babel.js
var A;
Copy the code
The Babel plugin skill get.
The official plug-in
For common code modifications, many plug-ins have been officially released, except for specific scenarios, generally for scaffolding and other applications, basically enough:
Babeljs. IO/docs/en/plu…
practice
@babel/plugin-transform-async-to-generator
The installation
# https://babeljs.io/docs/en/babel-plugin-transform-async-to-generator
npm i -D @babel/plugin-transform-async-to-generator
Copy the code
code
const ast = parse('const fn = async () => { }')
const { code } = transformFromAstSync(
ast,
null,
{ plugins: ['@babel/plugin-transform-async-to-generator']}// Here, string calls.
)
console.log(code)
Copy the code
The output
% node babel.js
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); }}function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
const fn = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(function* () {});
return function fn() {
return_ref.apply(this, arguments); }; } ();Copy the code
The above