AST in CSS

前言

hello,祝各位小伙伴们七月快乐噻!

我是来自推啊前端团队的D同学
今天要跟大家分享一个ASTPostCss的相关知识。阅读时长大概5-8分钟。如有错误,敬请指正。

序章

某年某?的某☀️,张三(法外狂徒)做的一个项目出了问题,在各种浏览器上部分CSS层面的表现不一致,定位到原因:是各大浏览器对某些属性支持程度不一样。于是张三就去敲定解决方案如下。

  • 手动更改

  • 脚本处理

    • 单一正则处理:遍历需要处理的文件,正则匹配。

    • 转化为易操作的对象进行处理

张三经过思索,还是选择了最后一个方案,然后就陷入了沉思,如何转化成易操作的对象呢❓
有句话是这么讲的,当你遇到棘手的问题,一时半会解决不掉时,已经有很多人遇到类似的的问题,并且给出了解决思路或者方案。

于是张三决定站在前人的肩膀上,去找寻一个突破口—JavaScript方向的AST解析过程。那么首先要了解一下什么是快乐星球 AST?

一、什么是AST?

AST即抽象语法树(Abstract Syntax Tree),是源码语法结构的一种抽象表示。它以树的形式表现编程语言的语法,结构树上的每个节点都表示源代码中的一种结构。

人话:AST形式上的本质就是一段JSON,或者说无数个JSON节点组成的一颗大的JSON?

举个?:

const randomNum = 0.1

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

复制代码

上述的js代码,转化成的AST如下所示:

{
    "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"
}
复制代码

由上述JSON对象,我们可以轻而易举的从得到的AST中看出源代码是什么样的。

换句话说,我们能从得到的AST中完全还原源代码.

二、AST的常规编译过程

image.png

张三了解完JavaScript类型的AST流程之后,略加思索?,有了!比着葫芦画个瓢,读书人的东西,怎么能叫偷呢?这叫借鉴思想。

于是张三就借鉴着JavaScriptAST流程,编写CSS层面的AST解析代码逻辑(即PostCss的前置工作,将源文件转为话AST)。具体分为以下几步。

1、读取源代码文件后,定义若干标识符,用于分割节点。

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 = '*'.charCodeAt(0);
const minus = '-'.charCodeAt(0);
const plus = '+'.charCodeAt(0);
const pound = '#'.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 = '@'.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;
复制代码

2、根据标识符加上正则匹配的到若干Tokens

[
  // 第一行第一列-第一行第四列为type='word'的值'#app'
  [ 'word', '#app', 1, 1, 1, 4 ],
  // 一个空格
  [ 'space', ' ' ],
  // 第一行第六列type='{'的值‘{’
  [ '{', '{', 1, 6 ],
  // 一个换行,两个空格
  [ 'space', '\n  ' ],
  // 第二行第三列-第二行第七列为type='word'的值'color'
  [ 'word', 'color', 2, 3, 2, 7 ],
  // 一个换行
  [ 'space', '\n' ],
  // 第三行第一列type='}'的值‘}’
  [ '}', '}', 3, 1 ]
]
复制代码

这里有细心的小伙伴可能发现了,’color=red’的属性声明中,red好像并不能从Tokens中得到。

其实在这里,PostCss并非严格按照上图解析过程执行的,它在生成Tokens的同时,已经生成了一颗不完整的AST了。

我们可以从PostCss源码中/libs/parser.js中看出,在生成Token时,已经将值挂在到AST的节点上了。

image.png
3、根据Tokens将不完整的AST补充完成,得到如下数据。

{
    "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
        }
    }
}
复制代码

张三看着自己的劳动成果,满意的拍了拍脑袋上的假发?。然后又开始思考,大费周章得到一个CSSAST,就单单为了补全一下兼容前缀吗?是不是有点大材小用,头重脚轻呢?

同时张三又开始思考,如何增强这个工具库的健壮性和功能多样性呢。毫无疑问,插件机制是最适合这种工具库的一种模式。

三、PostCss的插件机制

插件机制的核心,在于提供了十分简单的操作目标对象的一种方式,PostCss它仅仅作为一个运行时,它的初始功能只有源文件生成AST树,AST树生成新文件,并没有什么功能性的操作。

但是它搭配上各种各样的插件,便有了无限种可能。跟Webpack中加载各式各样的loader思想上是一致的。

image (1).png

张三看着自己的成果,大为满意?。这样的话,自己把最脏最累的活干完了,然后提供了一颗完整的AST,在在这样灵活的插件生态下,任何人都可以直接拿到AST自己去开发定制化插件,实现自己的功能。

简易版 AutoPrefixer

AutoPrefixer实现原理:用从 Can I Use 网站获取的数据为 CSS 规则添加特定厂商的前缀。 Autoprefixer 自动获取浏览器的流行度和能够支持的属性,并根据这些数据帮你自动为 CSS 规则添加前缀。

image (2).png

简易版 StyleLint

image (5).png

image (4).png

张三的故事未完待续?…

项目地址: github.com/ding2650/AS…

参考文献

投稿来源:juejin.cn/post/697365…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享