Hello, I’m wang silly, recently read the elder brother of Babel customs book of small volume, light also calculate the understanding of AST have a little bit, so based on the small volume of insert function call parameters and automatic submerged point of two chapters, based on some of the thinking, wrote a Babel based automatic submerged points, and small toys, hope everyone have their own understanding after the play, If there is anything wrong, you are welcome to correct it. You can also look at the relevant article of brother Light (God says to have light) to understand it skillfully. (I planned to finish writing this article during the Spring Festival. Sorry for waiting so long.)
Related knowledge points
- What is an AST abstract syntax tree
- The compilation process of a program
- The purpose of the AST
- The principle of Babel
- Personal implementation of Babel based buried point examples and thinking
What is an AST abstract syntax tree
The compilation process of a program
What is program compilation? As we all know, in the traditional compiled language flow, a piece of code in a program goes through three steps before it is executed, and the execution process of this step is also the program compilation process.
- Word segmentation (lexical analysis)
The process of lexical analysis is the first step, we write code is essentially a string of strings, and the process of lexical analysis is to break these strings of characters into meaningful code blocks. Such as:
let a = 1
// let a = 1
Copy the code
In this program, let, a, =, 1, and 1 are split, depending on whether special placeholders (such as Spaces) have any real meaning.
- Parsing (grammar analysis)
The process of grammar analysis is to combine the results of lexical analysis according to certain rules, associate the hashed code blocks and form a tree representing the syntax structure of the program, also known as abstract syntax tree (AST). Abstract syntax tree is a tree representation of the abstract syntax structure of the source code, and each node on the tree represents a structure in the source code. The reason why it is abstract is that the abstract representation transforms the JS code into a structured data structure. This data structure is essentially a big JSON object, which we’re all familiar with, like a tree with many leaves. With roots, trunks, branches and leaves, no matter how small or big, it is a complete tree. The code we write is converted into a tree structure according to certain rules.
Specific AST content you can also passhereInput to view, in addition, for this tool respectively to choose the language and dismantling tools, you can also choose the corresponding environment according to their own language
- Code generation
Code generation is also the last section of the compilation process, which transforms the AST abstract syntax tree from the parsing phase into executable code and then switches us. As for what kind of code is generated, it is up to us to decide, theoretically according to the rules of the language.
The purpose of the AST
After knowing what AST is, we must have some answers about the uses of AST. AST is used not only for compiling JavaScript engines, but also for actual development. For example, we commonly use Babel plug-in to transform ES6 into ES5, use UglifyJS to compress the code, CSS preprocessor, development of WebPack plug-in, VUE-CLI front-end automation tools and so on. These underlying principles are realized based on AST, AST is very powerful. Helps developers understand the essence of JavaScript. With this, we can control exactly how our code is handled at run time and compile time.
For example: Big Idiot came across an issue about Vue3. X while browsing GIthub. In this case, when using Vue3.0, I suddenly found that if using JSX writing method will cause some such as V-once to be not supported, what should I do? After Google search on Baidu, I came to the issue without understanding it. Here I have a thought, because it is JSX syntax, we must not go to the issue of Vue, but must be a conversion tool. Here we used babel-plugin-jsx, and found the following issue
After a fierce argument, Big Head lost and settled down to find two places in the code compilation.1.2V-once and other instructions are not processed accordingly.
This small example also illustrates the role that the AST can play. For example, we may suspect that some errors are due to a library or framework, but it may also be due to inconsistent rules or a failure to expose errors at compile time. If we can accurately analyze the reasons for the mistakes, then my mother no longer needs to worry about my disorderly mention of the issue.
The principle of Babel
The rise of front-end engineering has exposed us to more language tools, and Babel is a unique tool here.
The way we usually think about Babel is that it helps us deal with compatibility, which is that there are some new JavaScript features that we might want to use that are not yet supported by some browsers, Babel allows us to downgrade our code to a browser-compatible executable version, making it easier to develop two sets of code in one operation in both development and production environments.
- The Babel plug-in works on abstract syntax trees
- The three main processing steps of Babel are parse, transform and generate.
- parsing
Parsing is the equivalent of a combination of lexology and parsing in our compilation process, parsing code into abstract syntax trees (AST). Each JS engine (such as V8 in Chrome) has its own AST parser, and Babel is implemented through Babylon. The parsing process has two stages: lexical analysis and syntax analysis. The lexical analysis stage transforms the string code into a stream of tokens. Tokens are like nodes in the AST. The parsing phase converts a token flow into the AST form and converts the information in the token into the AST representation structure.
- conversion
The transformation step is generally a leaky processing step where nodes are added, updated, and removed. Depth-first traversal with traverse maintains the overall state of the AST tree and allows replacement, deletion, or addition of nodes. The result returned is our processed AST.
- generate
The build phase is to transform our two-phase final AST into our string code and create a code map, known as source-Map. Code generation involves deep traversing of the entire AST and converting it to a string that represents the converted code through generate.
Personal implementation of Babel based buried point examples and thinking
We buried when points are usually in the form of a function, to specify the parameters by buried points, so we in the development process if the need to bury some place for some special logo (in which I am using the console. The log), is it possible to by when we code before executing tool to deal with the mass of the buried buried place and unification . For the whole process, I suggest you refer to the AST builder website above to read and write
The first is tacker.js, which basically handles the AST generated from our source code in two main ways
- Import our buried function in this module
- Walk through our identity area to replace it
const { declare } = require('@babel/helper-plugin-utils');
const importModule = require('@babel/helper-module-imports');
const {default: template} = require("@babel/template");
const autoTrackPlugin = declare((api, options, dirname) = > {
api.assertVersion(7); // Indicates version 7
return {
visitor: {
Program: {
enter (path, state) {
path.traverse({
ImportDeclaration (curPath) {
const requirePath = curPath.get('source').node.value;
if (requirePath === options.trackerPath) {
const specifierPath = curPath.get('specifiers.0');
if (specifierPath.isImportSpecifier()) {
state.trackerImportId = specifierPath.toString();
} else if(specifierPath.isImportNamespaceSpecifier()) {
state.trackerImportId = specifierPath.get('local').toString(); } path.stop(); }}});if(! state.trackerImportId) { state.trackerImportId = importModule.addDefault(path,'tracker', {nameHint: path.scope.generateUid('tracker') }).name; }}},'ClassMethod|ArrowFunctionExpression|FunctionExpression|FunctionDeclaration'(path, state) // This is to find the AST node that conforms to the function {
const targetCalleeName = ['log'.'info'.'error'.'debug'].map(item= > `console.${item}`);
// TODO finds child nodes
const bodyPath = path.get('body');
if (bodyPath.isBlockStatement()) {// find the block-level scope first
const bodyPath2 = bodyPath.get('body.0') // The first block-level body content is found
console.log(bodyPath.get('body').type)
if(bodyPath2.isExpressionStatement()){// This is the ast statement corresponding to console
const calleeName = bodyPath2.get('expression').get('callee').toString()//
const bodyPath3 = bodyPath2.get('expression')
if (targetCalleeName.includes(calleeName)) {
let arg = []
bodyPath3.node.arguments.forEach((item,index,array) = >{
if(array[0].value==='tracker') {// If the first value of our console is tracker, it is a buried point; otherwise, it is our normal console.log
if(index>0) {let ret = item.value || item.name
arg.push(ret)
}
}
})
if(arg.length>0){
state.trackerAST = template.expression(`${state.trackerImportId}(${arg.join(', ')}) `) (); bodyPath3.remove()// Remove the original console code
bodyPath.node.body.unshift(state.trackerAST);// Insert the latest of our own code}}}}}}}});module.exports = autoTrackPlugin;
Copy the code
Then there’s our startTracker.js file, which is our entry function that we can pass Through Node when testing locally The startTracker.js command runs this code, and its main function is to convert it into an AST and give it to our tracker function to process the AST, get the AST and generate new code
const { transformFromAstSync } = require('@babel/core');
const parser = require('@babel/parser');
const autoTrackPlugin = require('./tracker');
const fs = require('fs');
const path = require('path');
const sourceCode = fs.readFileSync(path.join(__dirname, './code.js'), {
encoding: 'utf-8'
});
const ast = parser.parse(sourceCode, {
sourceType: 'unambiguous'
});
const { code } = transformFromAstSync(ast, sourceCode, {
plugins: [[autoTrackPlugin, {
trackerPath: 'tracker'
}]]
//
/* * Call the function to convert * 1 passed ast content * 2 passed map AST error problem mapping to map file * 3 an object is configuration-related * 1 passed plugins are an array array is different plugins can also be identified with arrays * plugins array * The first is the plugins used * the second is the plugins provided by the configuration can be customized constants into the received options * */
});
console.log(code);
Copy the code
Finally, there is our test Code (currently only simulating the content of a block-level scope, multiple block-level scopes are not written, you can watch AST to improve the next)
const obj={
a:111
}
function a () {
console.log(obj);
}
class B {
bb() {
console.log('tracker'.232)
return 'bbb'; }}const c = () = > 'ccc';
const d = function () {
console.log('tracker'.'1818'.11);
}
Copy the code
Finally, we can see the output result and the comparison with the source code. How’s that? Doesn’t that feel like fun. I hope you have a preliminary understanding after reading the article, you can also find some information to consolidate the next study. And make some small tools of my own to increase the impression, finally wish everyone in the New Year, work and life are tiger tiger vigorous!! (Like big silly children’s shoes can point a careful heart plus wave attention, thank you)