This article comes from @yo hey classmate of the front end of Flying pig. Girly girl teaches you how to use AST hand in hand. This article is very well written and worth reading.
This article is suitable for developers who are still interested in AST without knowledge of the principle. This article does not have any professionalism or rigor. It may be nothing but practical.
On the AST introduction, the Internet has been a lot of, not only awkward and difficult to understand, but also come with a second to persuade the attribute. In fact, we can be very (hao) (Bu) (Yan) qi (jin) to understand a high-end atmosphere of things, for example, AST is a code to solve the dark magic of a tree can change. So, once we know how to say the spell, the world opens. Interestingly, the magic spell looks like jQuery
Welcome, Sorcerer
To become a magician, we need four things: handy tools, short and common techniques, authoritative apis that you can use even if you don’t understand them, and a wild imagination.
🍭 a handy tool for the magic wand
🔗 AST exporer
This is an online ast debugging tool, with it, we can very intuitive to see the ast generation before and after and code conversion, it is divided into five areas. We rely on this tool for the rest of our code.
🔗 jscodeshift
It is an AST converter that translates the original code into an AST syntax tree, operates on the AST using its open API, and finally converts it into the code we want.
Jscodeshift’s API is encapsulated based on Recast and has a syntax very close to jquery. Recast is an encapsulation of Babel/Travers & Babel /types. It provides easy ast operations, while Travers is the tool used to operate ast in Babel. Types can be understood as a dictionary, which describes the types of structural trees.
Jscodeshift also provides additional capabilities that allow developers to operate during the engineering phase of a project, or even during development, without being aware of the processes before and after Babel translation, focusing on how to manipulate or change the tree and get the results.
Although Jscodeshift lacks Chinese documentation, its source code is highly readable, which is one of the important reasons why it is recommended. About its API operation skills, will be in practice for you to reveal.
📖 the definitive API for grimoires
🔗 babel-types
The AST syntax dictionary, which allows us to quickly refer to the type of the structure tree, is one of the most important tools when we want to generate a line of code from an AST.
Know the AST
I thought the AST
Actual AST
Let’s say we have such a code
var a = 1
Copy the code
We converted it to an AST, which is shown below in JSON format
{
"type": "Program"."sourceType": "script"."body": [{"type": "VariableDeclaration"."kind": "var"."declarations": [{"type": "VariableDeclarator"."id": {
"type": "Identifier"."name": "a"
},
"init": {
"type": "Literal"."value": 1}}}}Copy the code
When I change the value of value in init to 2, the corresponding JS will also change to var a = 2. When I change the value of name in ID to b, the corresponding JS will also change to var b = 2
When you see this, you suddenly find that the operation of AST is nothing more than the operation of a set of regular JSON, find a new world? So as long as understand the rules, is not soon can master a world there! Wood! There are!
Understanding AST Nodes
Explore the AST node types
Common Node Meaning Reference tableAfter looking at the rule, I immediately understand what type is in the AST JSON that I don’t understandbabel-types”Is really just a grammar word! Originally master a world unexpectedly can be so simple! Single!
Jscodeshift is easy to operate
To find the
api | type | Receive parameters | describe |
---|---|---|---|
find | fn | Type: indicates the AST type | |
All AST nodes of the AST type matching the filter conditions are found and an array is returned. | |||
filter | fn | Callback: Accepts a callback, passing the called AST node by default | Filters the SPECIFIED AST nodes and returns an array |
forEach | fn | Callback: Accepts a callback, passing the called AST node by default | Traversing the AST node, same as js forEach function |
28. According to the knight survey,
delete
api | type | Receive parameters | describe |
---|---|---|---|
remove | fn | Type: indicates the AST type | |
Filter: indicates the filter condition | All AST nodes of the AST type matching the filter conditions are found and an array is returned. |
Add & Modify
api | type | Receive parameters | describe |
---|---|---|---|
replaceWith | fn | Nodes: INDICATES an AST node | Replace the AST node. If the value is empty, the AST node is deleted |
insertBefore | fn | fn | Nodes: INDICATES an AST node |
insertAfter | fn | fn | Nodes: INDICATES an AST node |
toSource | fn | Options: configuration items | Ast node translation, returning JS |
28. According to the knight survey,
other
Operations on child nodes include getAST() and Nodes (). Specify the search for ast nodes, such as findJSXElements(), hasAttributes(), hasChildren(), and so on.
More information about jscodeshift/ Collections can be viewed from the Ast Explore console in the operation area, or directly from jscodeshift/ Collections
The command
// -t The file path of the conversion file can be local or URL
// myTransforms AST executes the file
// fileA fileB Specifies the file to be operated
// --params=options Used to execute the parameters received by the file
jscodeshift -t myTransforms fileA fileB --params=options
Copy the code
For more commands, see 🔗 jscodeshift
practice
Next, I’ll pass on the skills in practice.
Simple example
Let’s start with an example that assumes the following code
import * as React from 'react';
import styles from './index.module.scss';
import { Button } from "@alifd/next";
const Button = () = > {
return (
<div>
<h2>Before translation</h2>
<div>
<Button type="normal">Normal</Button>
<Button type="primary">Prirmary</Button>
<Button type="secondary">Secondary</Button>
<Button type="normal" text>Normal</Button>
<Button type="primary" text>Primary</Button>
<Button type="secondary" text>Secondary</Button>
<Button type="normal" warning>Normal</Button>
</div>
</div>
);
};
export default Button;
Copy the code
Execute file (operated through jscodeshift)
module.exports = (file, api) = > {
const j = api.jscodeshift;
const root = j(file.source);
root
.find(j.ImportDeclaration, { source: { value: "@alifd/next" } })
.forEach((path) = > {
path.node.source.value = "antd";
})
root
.find(j.JSXElement, {openingElement: { name: { name: 'h2' } }})
.forEach((path) = > {
path.node.children = [j.jsxText('After translation')]
})
root
.find(j.JSXOpeningElement, { name: { name: 'Button' } })
.find(j.JSXAttribute)
.forEach((path) = > {
const attr = path.node.name
const attrVal = ((path.node.value || {}).expression || {}).value ? path.node.value.expression : path.node.value
if (attr.name === "type") {
if (attrVal.value === 'normal') {
attrVal.value = 'default'}}if (attr.name === "size") {
if (attrVal.value === 'medium') {
attrVal.value = 'middle'}}if (attr.name === "warning") {
attr.name = 'danger'
}
if (attr.name === "text") {
const attrType = path.parentPath.value.filter(item= > item.name.name === 'type')
attr.name = 'type'
if (attrType.length) {
attrType[0].value.value = 'link'
j(path).replaceWith(' ')}else {
path.node.value = j.stringLiteral('link')}}});return root.toSource();
}
Copy the code
The example code is loosely interpreted as follows
- Convert js to AST
- Walk through all the reference modules in the code that contain @alifd/next and do the following
- Change the module name to ANTD.
- Find the code block with the signature H2 and modify the copywriting within that tag.
- Walk through all the Button labels in the code and do the following
- Change the value of the type and size attributes in the tag
- Change the text attribute in the tag to type = “link”
- Change the warning attribute in the label to danger
- Returns js converted from the AST.
Final output
import * as React from 'react';
import styles from './index.module.scss';
import { Button } from "antd";
const Button = () = > {
return (
<div>
<h2>After the translation</h2>
<div>
<Button type="default">Normal</Button>
<Button type="primary">Prirmary</Button>
<Button type="secondary">Secondary</Button>
<Button type="link" >Normal</Button>
<Button type="link" >Primary</Button>
<Button type="link" >Secondary</Button>
<Button type="default" danger>Normal</Button>
</div>
</div>
);
};
export default Button;
Copy the code
Sentence by sentence read
Get the necessary data
// Get the API used to manipulate the AST, get the body content of the file to be compiled, and convert it to the AST structure.
const j = api.jscodeshift;
const root = j(file.source);
Copy the code
After you run the jscodeshift command, the execution file receives three parameters
file
attribute | describe |
---|---|
path | The file path |
source | The body of the file that we’re working on, and that’s what we’re going to use. |
api
attribute | describe |
---|---|
jscodeshift | We mainly use this for references to the Jscodeshift library. |
stats | --dry The ability to collect statistics during runtime |
report | Prints the passed string to stdout |
options
Receive additional parameters when you run the jscodeshift command. This section describes the parameters that are not needed.
Code conversion
// root: the converted AST and node
root
// ImportDeclaration corresponds to the import sentence
.find(j.ImportDeclaration, { source: { value: "@alifd/next" } })
.forEach((path) = > {
// Path. node is the AST node corresponding to the import sentence
path.node.source.value = "antd";
})
Copy the code
Reading:
- Walk through all the reference modules in the code that contain @alifd/next and do the following
- Change the module name to ANTD.
root
// JSXElement =
...
// openingElement is an open tag sentence corresponding to element, such as
.find(j.JSXElement, {openingElement: { name: { name: 'h2' } }})
.forEach((path) = > {
// jsxText Corresponds to text
path.node.children = [j.jsxText('After translation')]})Copy the code
Reading:
- Filter HTML with tag H2 and change the text of the contents of that tag to “after translation”
root
// Filter the element open sentence of Button
.find(j.JSXOpeningElement, { name: { name: 'Button'}})// JSXAttribute Attribute syntax corresponding to element, for example, type="normal"...
.find(j.JSXAttribute)
.forEach((path) = > {
const attr = path.node.name
const attrVal = ((path.node.value || {}).expression || {}).value ? path.node.value.expression : path.node.value
if (attr.name === "type") {
if (attrVal.value === 'normal') {
attrVal.value = 'default'}}if (attr.name === "size") {
if (attrVal.value === 'medium') {
attrVal.value = 'middle'}}if (attr.name === "warning") {
attr.name = 'danger'
}
if (attr.name === "text") {
// Determine whether the sibling node of the ast node has a type,
// If yes, change the value of type to link. If no, change the current node to type= "link".
const attrType = path.parentPath.value.filter(item= > item.name.name === 'type')
attr.name = 'type'
if (attrType.length) {
attrType[0].value.value = 'link'
j(path).replaceWith(' ')}else {
// stringLiteral indicates the value of a string field
path.node.value = j.stringLiteral('link')}}});Copy the code
Reading:
- Walk through all the Button labels in the code and do the following
- Change the value of the type and size attributes in the tag
- Change the text attribute in the tag to type = “link”
- Change the warning attribute in the label to danger
return root.toSource();
Copy the code
Reading:
- Returns js converted from the AST.
Wild imagination comes from laziness
If we want to insert a large chunk of code, the ast would have to use a large number of types to generate a large number of node objects, which is unnecessary. There is always a violent solution 🌝.
const formRef = j('const formRef = React.createRef(); ').nodes()[0].program.body[0]
path.insertAfter(formRef)
Copy the code
Let’s say we want to convert an element’s text to an attr tag.
const getStringEle = (source) = > {
if (Array.isArray(source)) {
let arr = []
source.forEach((item, i, items) = > {
if(! item.replace(/\s+|\n/g.' ').length && i! = =0&& i! == (items.length -1 )){
arr.push('< > < / a >')
}
arr.push(item)
})
return arr.join(' ')}else {
return source
}
}
...
.find(j.JSXAttribute)
.forEach(path= > {
const attrVal = ((path.node.value || {}).expression || {}).value ? path.node.value.expression : path.node.value
const childrenEleStr = getStringEle(j(path).toSource())
j(path).replaceWith(j.jsxIdentifier(
`attr={[${childrenEleStr.replace(/<><\/>/g.', ')}]} `))})Copy the code
The more you know how to write chains, the more you can do with them.
Let the document run together with the project
All of the above are based on AST Exporer, which cannot be applied to project scenarios or meet engineering needs. A real engineering scenario is not satisfied with just one document. If you want to make AST engineering and truly implement it in a project, use AST to refactor business code and liberate redundant labor force, the following is a good solution.
Here are two tools I recommend based on Node
npx & execa
Use NPX to implement a complex command to create a simple CLI. Execute Jscodeshift in batches through ExecA.
The key code is as follows
package.json
"bin": {
"ast-cli": "bin/index.js"
},
Copy the code
index.js
#! /usr/bin/env node
require('./cli').main()
Copy the code
main()
.const path = require('path')
const execa = require('execa');
const jscodeshiftBin = require.resolve('.bin/jscodeshift');
module.exports.main = async () => {
...
const astFilesPath = ...
astFilesPath.forEach(async (transferPath, i) => {
const outdrr = await execa.sync(jscodeshiftBin, ['-t', transferPath, src])
if (outdrr.failed) {
console.log('Compile error:${outdrr}`)}})... }...Copy the code