preface

Hello, I wish you a happy July!

I am D student from tui Front End Team. Today I want to share with you a AST and PostCss related knowledge. The reading should last about 5-8 minutes. Please correct any mistakes.

prologue

A certain 🌙 ☀️, Zhang SAN (outlaw fanatic) to do a project problems, in various browsers on the performance of part of the CSS level is inconsistent, positioning the reason: is different browsers to support some attributes are not the same. So Zhang SAN went to finalize the solution as follows.

  • Manual changes

  • Script processing

    • Single re processing: Traverse the file to be processed, re matching.

    • Convert to an easily operable object for processing.

Zhang SAN after thinking, or choose the last scheme, and then fell into a meditation, how to transform into easy to operate the object ❓ there is a saying that, when you encounter a difficult problem, half will not be solved, there have been many people encounter similar problems, and give the solution or scheme.

So Zhang SAN decided to stand on the shoulders of predecessors to find a breakthrough – the AST parsing process in the direction of JavaScript. So first of all, what is happy Planet AST?

What is AST?

ASTAbstract Syntax Tree is an Abstract representation of source Syntax structure. It represents the syntax of a programming language in the form of a tree, where each node represents a structure in the source code.

Human: The essence of AST form is a piece of JSON, or a large JSON composed of countless JSON nodes 🌲.

An 🌰 :

const randomNum = 0.1

if(randomNum < 0.5) {console.log('right')}else{
 console.log('error')}Copy the code

The js code above is converted to the AST as follows:

{
    "type":"Program"."body":[
        {
            "type":"VariableDeclaration"."declarations":[
                {
                    "type":"VariableDeclarator"."id": {"type":"Identifier"."name":"randomNum"
                    },
                    "init": {"type":"Literal"."value":0.1."raw":"0.1"}}]."kind":"var"
        },
        {
            "type":"IfStatement"."test": {"type":"BinaryExpression"."operator":"<"."left": {"type":"Identifier"."name":"randomNum"
                },
                "right": {"type":"Literal"."value":0.5."raw":"0.5"}},"consequent": {"type":"BlockStatement"."body":[
                    {
                        "type":"ExpressionStatement"."expression": {"type":"CallExpression"."callee": {"type":"MemberExpression"."computed":false."object": {"type":"Identifier"."name":"console"
                                },
                                "property": {"type":"Identifier"."name":"log"}},"arguments":[
                                {
                                    "type":"Literal"."value":"right"."raw":"'right'"}]}}]},"alternate": {"type":"BlockStatement"."body":[
                    {
                        "type":"ExpressionStatement"."expression": {"type":"CallExpression"."callee": {"type":"MemberExpression"."computed":false."object": {"type":"Identifier"."name":"console"
                                },
                                "property": {"type":"Identifier"."name":"log"}},"arguments":[
                                {
                                    "type":"Literal"."value":"error"."raw":"'error'"}]}}],"sourceType":"script"
}
Copy the code

With the JSON object above, we can easily see what the source code looks like from the resulting AST.

In other words, we can completely restore the source code from the resulting AST.

Second,ASTThe general compilation process

After zhang SAN understood the AST process of JavaScript type, he thought a little 🤔, there! Comparing gourd to draw a gourd gourd, what the scholar does, how can call steal? It’s called borrowing ideas.

Therefore, Zhang SAN drew on the AST process of JavaScript to write the AST parsing code logic at the CSS level (that is, the prework of PostCss to convert source files into AST). Specifically divided into the following steps.

1. After reading the source code file, define several identifiers for node segmentation.

const openBracket = '{'.charCodeAt(0);
const closeBracket = '} '.charCodeAt(0);
const openParen = '('.charCodeAt(0);
const closeParen = ') '.charCodeAt(0);
const singleQuote = '\' '.charCodeAt(0);
const doubleQuote = '"'.charCodeAt(0);
const backslash = '\ \'.charCodeAt(0);
const slash = '/'.charCodeAt(0);
const period = '. '.charCodeAt(0);
const comma = ', '.charCodeAt(0);
const colon = ':'.charCodeAt(0);
const asterisk = The '*'.charCodeAt(0);
const minus = The '-'.charCodeAt(0);
const plus = '+'.charCodeAt(0);
const pound = The '#'.charCodeAt(0);
const newline = '\n'.charCodeAt(0);
const space = ' '.charCodeAt(0);
const feed = '\f'.charCodeAt(0);
const tab = '\t'.charCodeAt(0);
const cr = '\r'.charCodeAt(0);
const at = The '@'.charCodeAt(0);
const lowerE = 'e'.charCodeAt(0);
const upperE = 'E'.charCodeAt(0);
const digit0 = '0'.charCodeAt(0);
const digit9 = '9'.charCodeAt(0);
const lowerU = 'u'.charCodeAt(0);
const upperU = 'U'.charCodeAt(0);
const atEnd = /[ \n\t\r\{\(\)'"\\;,/]/g;
const wordEnd = /[ \n\t\r\(\)\{\}\*:;@!&'"\+\|~>,\[\]\\]|\/(? =\*)/g;
const wordEndNum = /[ \n\t\r\(\)\{\}\*:;@!&'"\-\+\|~>,\[\]\\]|\//g;
const alphaNum = /^[a-z0-9]/i;
const unicodeRange = /^[a-f0-9?\-]/i;
Copy the code

2. Add regular Tokens to match with Tokens.

[
  Type ='word' '#app'
  [ 'word'.'#app'.1.1.1.4].// a space
  [ 'space'.' '].Type ='{';
  [ '{'.'{'.1.6].// One newline with two Spaces
  [ 'space'.'\n '].Type ='word' value 'color';
  [ 'word'.'color'.2.3.2.7].// a line break
  [ 'space'.'\n'].Type ='}';
  [ '} '.'} '.3.1]]Copy the code

“Red” is not a token.

In this case, PostCss does not strictly follow the parsing process shown above. It generates Tokens and an incomplete AST at the same time.

We can see from the PostCss source /libs/parser.js that when the Token is generated, the value is already hung on the node to the AST.

3. Complement the AST with Tokens to obtain the following data.

{
    "raws": {"semicolon":false."after":""
    },
    "type":"root"."nodes":[
        {
            "raws": {"before":""."between":""."semicolon":true."after":""
            },
            "type":"rule"."nodes":[
                {
                    "raws": {"before":""."between":":"
                    },
                    "type":"decl"."source": {"start": {"line":2."column":3
                        },
                        "input": {"css":"#app {color: red; }"."hasBOM":false."id":"<input css 1>"
                        },
                        "end": {"line":2."column":13}},"prop":"color"."value":"red"}]."source": {"start": {"line":1."column":1
                },
                "input": {"css":"#app {color: red; }"."hasBOM":false."id":"<input css 1>"
                },
                "end": {"line":3."column":1}},"selector":"#app"}]."source": {"input": {"css":"#app {color: red; }"."hasBOM":false."id":"<input css 1>"
        },
        "start": {"line":1."column":1}}}Copy the code

Zhang SAN looked at his labor results, satisfaction patted the wig on the head 🤯. Then I started to wonder, did you go to all the trouble to get an AST of CSS just to complete the compatibility prefix? Is it a little overqualified, top-heavy?

At the same time, Zhang SAN began to think about how to enhance the robustness and functional diversity of this tool library. Without a doubt, the plug-in mechanism is the most appropriate pattern for this tool library.

Three,PostCssPlugin mechanism for

The core of plug-in mechanism, is to provide a very simple operation of the target object, PostCss it only as a runtime, its initial function only source files generate AST tree, AST tree generate new files, and no functional operations.

But with a variety of plugins, the possibilities are endless. This is consistent with the idea of loading various loaders in Webpack.

Zhang SAN looked at his results, greatly satisfied 💯. In this way, the dirtiest and most tiring work is done by themselves, and then a complete AST is provided. In such a flexible plug-in ecosystem, anyone can directly take the AST to develop customized plug-ins to achieve their own functions.

Simple AutoPrefixer

AutoPrefixer works by adding vendor-specific prefixes to CSS rules using data retrieved from the Can I Use website. Autoprefixer automatically gets the browser’s popularity and supported attributes, and automatically prefixes CSS rules based on this data.

Simple StyleLint

Zhang SAN’s story to be continued 💥…

Project Address:Github.com/ding2650/AS…

reference

  • PostCss Api

  • MDN Parser

  • Esprima

Contribution source: juejin.cn/post/697365…