<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          零基礎(chǔ)理解 PostCSS 的主流程

          共 10333字,需瀏覽 21分鐘

           ·

          2022-03-10 23:10

          來(lái)自內(nèi)部 霍超群 同學(xué)的分享

          本文適用于所有前端開發(fā)人員。文章會(huì)介紹 PostCSS 的主功能實(shí)現(xiàn)原理,不是介紹 api,也不會(huì)介紹所有功能的原理,如果有需要了解全部功能或者查閱 API,可查看官方文檔:https://postcss.org/api/。

          什么是 PostCSS

          官網(wǎng)說(shuō):“PostCSS,一個(gè)使用 JavaScript 來(lái)處理CSS的框架”。這句話高度概括了 PostCSS 的作用,但是太抽象了。按我理解,PostCSS 主要做了三件事:

          1. parse:把 CSS 文件的字符串解析成抽象語(yǔ)法樹(Abstract Syntax Tree)的框架,解析過(guò)程中會(huì)檢查 CSS 語(yǔ)法是否正確,不正確會(huì)給出錯(cuò)誤提示。
          2. runPlugin: 執(zhí)行插件函數(shù)。PostCSS 本身不處理任何具體任務(wù),它提供了以特定屬性或者規(guī)則命名的事件。有特定功能的插件(如 autoprefixer、CSS Modules)會(huì)注冊(cè)事件監(jiān)聽器。PostCSS 會(huì)在這個(gè)階段,重新掃描 AST,執(zhí)行注冊(cè)的監(jiān)聽器函數(shù)。
          3. generate: 插件對(duì) AST 處理后,PostCSS 把處理過(guò)的 AST 對(duì)象轉(zhuǎn)成 CSS string。
          38a68c735bef43994692160eb6b6728c.webp

          「如果沒有插件」,那么初始傳入的 CSS string 和 generate 生成的 CSS string 是一樣的。由此可見,PostCSS 本身并不處理任何具體的任務(wù),只有當(dāng)我們?yōu)槠涓郊痈鞣N插件之后,它才具有實(shí)用性。

          下面分別詳細(xì)分析三個(gè)階段做的事。

          第一階段:parse

          CSS 語(yǔ)法簡(jiǎn)述

          CSS 規(guī)則集(rule-set)由選擇器和聲明塊組成:

          2b1f9e5e84fc9f0b82ad0d85882af24d.webp
          • 選擇器指向您需要設(shè)置樣式的 HTML 元素。
          • 聲明塊包含一條或多條用分號(hào)分隔的聲明。
          • 每條聲明都包含一個(gè) CSS 屬性名稱和一個(gè)值,以冒號(hào)分隔。
          • 多條 CSS 聲明用分號(hào)分隔,聲明塊用花括號(hào)括起來(lái)。

          五類對(duì)象

          AST 用五類對(duì)象描述 CSS 語(yǔ)法。這里舉個(gè)具體的例子,再打印出對(duì)應(yīng)的 AST 結(jié)果,對(duì)照了解 AST 五類對(duì)象和 CSS 語(yǔ)法的對(duì)應(yīng)關(guān)系。

          app.css 文件中寫如下內(nèi)容:

          @import?url('./app-02.css');

          .container?{
          ??color:?red;
          }

          Declaration 對(duì)象

          Declaration 對(duì)象用來(lái)描述 CSS 中的每一條聲明語(yǔ)句。

          • type 標(biāo)記當(dāng)前對(duì)象的類型
          • parent 記錄父對(duì)象的實(shí)例
          • prop 記錄聲明中的屬性名
          • value 記錄聲明中的值
          • raws 字段記錄聲明前的字符串、聲明屬性和值之間的符號(hào)的字符串
          • 其余字段解釋見代碼中的注釋。

          上邊 CSS 文件中的color: red;會(huì)被描述成如下對(duì)象:

          {
          ????parent:?Rule,???????//?外層的選擇器被轉(zhuǎn)譯成?Rule?對(duì)象,是當(dāng)前聲明對(duì)象的?parent
          ????prop:?"color",??????//?prop?字段記錄聲明的屬性
          ????raws:?{?????????????// raws 字段記錄聲明前、后的字符串,聲明屬性和值之間的字符串,以及前邊語(yǔ)句是否分號(hào)結(jié)束。
          ????????before:?'\n?',??//?raws.before?字段記錄聲明前的字符串
          ????????between:?':?',?//?raws.between?字段記錄聲明屬性和值之間的字符串
          ????},
          ????source:?{??????????//?source?字段記錄聲明語(yǔ)句的開始、結(jié)束位置,以及當(dāng)前文件的信息
          ????????start:?{?offset:?45,?column:?3,?line:?4?},
          ????????end:?{?offset:?55,?column:?13,?line:?4?},
          ????????input:?Input?{
          ????????????css:?'@import?url('./app-02.css');\n\n.container?{\n??color:?red;\n}',
          ????????????file:?'/Users/admin/temp/postcss/app.css',
          ????????????hasBOM:?false,
          ????????????Symbol(fromOffsetCache):?[0,?29,?30,?43,?57]
          ????????}
          ????},
          ????Symbol('isClean'):?false,??// Symbol(isClean)?字段默認(rèn)值都是 false,用于記錄當(dāng)前對(duì)象關(guān)聯(lián)的 plugin 是否執(zhí)行。plugin 會(huì)在后續(xù)解釋
          ????Symbol('my'):?true,????????//?Symbol(my)?字段默認(rèn)值都是?true,用于記錄當(dāng)前對(duì)象是否是對(duì)應(yīng)對(duì)象的實(shí)例,如果不是,可以根據(jù)類型把對(duì)象的屬性設(shè)置為普通對(duì)象的?prototype?屬性
          ????type:?'decl',????????????//?type?記錄對(duì)象類型,是個(gè)枚舉值,聲明語(yǔ)句的?type?固定是?decl
          ????value:?"red"?????????????//?value?字段記錄聲明的值
          }

          每個(gè)字段的含義和功能已經(jīng)以注釋的形式進(jìn)行了解釋。

          Rule 對(duì)象

          Rule 對(duì)象是描述選擇器的。

          • type 記錄對(duì)象的類型
          • parent 記錄父對(duì)象的實(shí)例
          • nodes 記錄子對(duì)象的實(shí)例
          • selector 記錄選擇器的字符串
          • raws 記錄選擇器前的字符串、選擇器和大括號(hào)之間的字符串、最后一個(gè)聲明和結(jié)束大括號(hào)之間的字符串
          • 其余字段解釋見代碼中的注釋。

          上邊 app.css 文件中.container經(jīng)過(guò) postcss 轉(zhuǎn)譯后的對(duì)象是(每個(gè)字段的含義和功能已經(jīng)以注釋的形式進(jìn)行了解釋):

          {
          ????nodes:?[Declaration],?//?nodes?記錄包含關(guān)系,Rule?對(duì)象包含?Declaration?對(duì)象
          ????parent:?Root,????????//?根對(duì)象是?Root?對(duì)象,是當(dāng)前聲明對(duì)象的?parent
          ????raws:?{??????????????//?raws?字段記錄如下
          ????????before:?'\n\n',??//?raws.before?字段記錄選擇器前的字符串
          ????????between:?'?',????//?raws.between?字段記錄選擇器和大括號(hào)之間的字符串
          ????????semicolon:?true,?//?raws.semicolon?字段記錄前置聲明語(yǔ)句是正常分號(hào)結(jié)束
          ????????after:?'\n'??????//?raws.after?字段記錄最后一個(gè)聲明和結(jié)束大括號(hào)之間的字符串
          ????},
          ????selector:'.container',?//?selector?記錄?selector
          ????source:?{????????????//?source?字段記錄選擇器語(yǔ)句的開始、結(jié)束位置,以及當(dāng)前文件的信息
          ????????start:?{?offset:?30,?column:?1,?line:?3?},
          ????????input:?Input?{
          ????????????css:?'@import?url('./app-02.css');\n\n.container?{\n??color:?red;\n}',
          ????????????file:?'/Users/admin/temp/postcss/app.css',
          ????????????hasBOM:?false,
          ????????????Symbol(fromOffsetCache):?[0,?29,?30,?43,?57]
          ????????},
          ????????end:?{?offset:?57,?column:?1,?line:?5?}
          ????},
          ????Symbol('isClean'):?false,??// Symbol(isClean)?字段默認(rèn)值都是 false,用于記錄當(dāng)前對(duì)象關(guān)聯(lián)的 plugin 是否執(zhí)行。plugin 會(huì)在后續(xù)解釋
          ????Symbol('my'):?true,????????//?Symbol(my)?字段默認(rèn)值都是?true,用于記錄當(dāng)前對(duì)象是否是對(duì)應(yīng)對(duì)象的實(shí)例,如果不是,可以根據(jù)類型把對(duì)象的屬性設(shè)置為普通對(duì)象的?prototype
          ????type:?'rule'???????????//?type?記錄對(duì)象類型,是個(gè)枚舉值,聲明語(yǔ)句的?type?固定是?rule
          }

          Root 對(duì)象

          Root 對(duì)象是 AST 對(duì)象的根對(duì)象。

          • type 記錄當(dāng)前對(duì)象的類型
          • nodes 屬性記錄子節(jié)點(diǎn)對(duì)應(yīng)對(duì)象的實(shí)例。

          上邊 app.css 文件中 root 對(duì)象是(每個(gè)字段的含義和功能已經(jīng)以注釋的形式進(jìn)行了解釋):

          {
          ????nodes:?[AtRule,?Rule],?//?nodes?記錄子對(duì)象(選擇器和?@開頭的對(duì)象),AtRule?對(duì)象會(huì)在后邊提到
          ????raws:?{????????????????//?raws?字段記錄如下
          ????????semicolon:?false,??//?raws.semicolon?最后是否是分號(hào)結(jié)束
          ????????after:?''??????????//?raws.after?最后的空字符串
          ????},
          ????source:?{??????????????//?source?字段記錄根目錄語(yǔ)句的開始,以及當(dāng)前文件的信息
          ????????start:?{?offset:?0,?column:?1,?line:?1?},
          ????????input:?Input?{
          ????????????css:?'@import?url('./app-02.css');\n\n.container?{\n??color:?red;\n}',
          ????????????file:?'/Users/admin/temp/postcss/app.css',
          ????????????hasBOM:?false,
          ????????????Symbol(fromOffsetCache):?[0,?29,?30,?43,?57]
          ????????}
          ????},
          ????Symbol('isClean'):?false,??// Symbol(isClean)?字段默認(rèn)值都是 false,用于記錄當(dāng)前對(duì)象關(guān)聯(lián)的 plugin 是否執(zhí)行。plugin 會(huì)在后續(xù)解釋
          ????Symbol('my'):?true,????????//?Symbol(my)?字段默認(rèn)值都是?true,用于記錄當(dāng)前對(duì)象是否是對(duì)應(yīng)對(duì)象的實(shí)例,如果不是,可以根據(jù)類型把對(duì)象的屬性設(shè)置為普通對(duì)象的?prototype
          ????type:?'root'???????????//?type?記錄對(duì)象類型,是個(gè)枚舉值,聲明語(yǔ)句的?type?固定是?root
          }

          AtRule 對(duì)象

          CSS 中除了選擇器,還有一類語(yǔ)法是 @ 開頭的,例如 @import@keyframes@font-face,PostCSS 把這類語(yǔ)法解析成 AtRule 對(duì)象。

          • type 記錄當(dāng)前對(duì)象的類型
          • parent 記錄當(dāng)前對(duì)象的父對(duì)象
          • name 記錄@緊跟著的單詞
          • params 記錄 name 值

          例如 @import url("./app-02.css"); 將被解析成如下對(duì)象:

          {
          ????name:?"import",??????????????????//?name?記錄?@?緊跟著的單詞
          ????params:?"url('./app-02.css')",???//?params?記錄?name?值
          ????parent:?Root,????????????????????//?parent?記錄父對(duì)象
          ????raws:?{??????????????????????????//?raws?字段記錄如下
          ????????before:?'',??????????????????//?raws.before?記錄?@語(yǔ)句前的空字符串
          ????????between:?'',?????????????????//?raws.between?記錄?name?和?{?之間的空字符串
          ????????afterName:?'',????????????????//?raws.afterName?記錄?name?和?@?語(yǔ)句之間的空字符串
          ????????after:?'',???????????????????//?raws.after?記錄大括號(hào)和上一個(gè)?rule?之間的空字符串
          ????????semicolon:?false?????????????//?raws.semicolon?上一個(gè)規(guī)則是否是分號(hào)結(jié)束
          ????},
          ????source:?{????????????????????????//?source?字段記錄@語(yǔ)句的開始,以及當(dāng)前文件的信息
          ????????start:?{?offset:?0,?column:?1,?line:?1?},
          ????????end:?{?offset:?27,?column:?28,?line:?1?},
          ????????input:?Input?{
          ????????????css:?'@import?url('./app-02.css');\n\n.container?{\n??color:?red;\n}',
          ????????????file:?'/Users/admin/temp/postcss/app.css',
          ????????????hasBOM:?false,
          ????????????Symbol(fromOffsetCache):?[0,?29,?30,?43,?57]
          ????????}
          ????},
          ????Symbol('isClean'):?false,??// Symbol(isClean)?字段默認(rèn)值都是 false,用于記錄當(dāng)前對(duì)象關(guān)聯(lián)的 plugin 是否執(zhí)行。plugin 會(huì)在后續(xù)解釋
          ????Symbol('my'):?true,????????//?Symbol(my)?字段默認(rèn)值都是?true,用于記錄當(dāng)前對(duì)象是否是對(duì)應(yīng)對(duì)象的實(shí)例,如果不是,可以根據(jù)類型把對(duì)象的屬性設(shè)置為普通對(duì)象的?prototype
          ????type:?'atrule'??????????//?type?記錄對(duì)象類型,是個(gè)枚舉值,聲明語(yǔ)句的?type?固定是?atrule
          }

          Comment 對(duì)象

          css 文件中的注釋被解析成 Comment 對(duì)象。text 字段記錄注釋內(nèi)容。/* 你好 */被解析成:

          {
          ????parent:?Root,?????????????//?parent?記錄父對(duì)象
          ????raws:?{???????????????????//?raws?字段記錄如下
          ????????before:?'',???????????//?raws.before?記錄注釋語(yǔ)句前的空字符串
          ????????left:?'?',????????????//?raws.left?記錄注釋語(yǔ)句左側(cè)的空字符串
          ????????right:?'?'????????????//?raws.right?記錄注釋語(yǔ)句右側(cè)的空字符串
          ????},
          ????source:?{?????????????????//?source?字段記錄注釋語(yǔ)句的開始、結(jié)束位置,以及當(dāng)前文件的信息
          ????????start:?{…},?input:?Input,?end:?{…}
          ????},
          ????Symbol('isClean'):?false,??// Symbol(isClean)?字段默認(rèn)值都是 false,用于記錄當(dāng)前對(duì)象關(guān)聯(lián)的 plugin 是否執(zhí)行。plugin 會(huì)在后續(xù)解釋
          ????Symbol('my'):?true,????????//?Symbol(my)?字段默認(rèn)值都是?true,用于記錄當(dāng)前對(duì)象是否是對(duì)應(yīng)對(duì)象的實(shí)例,如果不是,可以根據(jù)類型把對(duì)象的屬性設(shè)置為普通對(duì)象的?prototype
          ????text:?'你好',?????????????//?text?記錄注釋內(nèi)容
          ????type:?'comment'??????????//?type?記錄對(duì)象類型,是個(gè)枚舉值,聲明語(yǔ)句的?type?固定是?comment
          }

          圖解五類對(duì)象之間的繼承關(guān)系

          從上一段可以知道,CSS 被解析成 Declaration、Rule、Root、AtRule、Comment 對(duì)象。這些對(duì)象有很多公共方法,PostCSS 用了面向?qū)ο蟮睦^承思想,把公共方法和公共屬性提取到了父類中。

          Root、Rule、AtRule 都是可以有子節(jié)點(diǎn)的,都有 nodes 屬性,他們?nèi)齻€(gè)繼承自 Container 類,對(duì) nodes 的操作方法都寫在 Container 類中。Container、Declaration、Comment 繼承自 Node 類,所有對(duì)象都有 Symbol('isClean')、Symbol('my')、raws、source、type 屬性,都有toString()、error()等方法,這些屬性和方法都定義在 Node 類中。

          Container、Node 是用來(lái)提取公共屬性和方法,不會(huì)生成他們的實(shí)例。

          五個(gè)類之間的繼承關(guān)系如下圖所示:

          1762acf718923359df0d81ee63d146af.webp

          圖中沒有窮舉類的方法,好奇的同學(xué)可以看直接看源碼文件: https://github.com/postcss/postcss/tree/main/lib 。

          把 CSS 語(yǔ)法解析成 AST 對(duì)象的具體算法

          算法對(duì)應(yīng)源碼中位置是:postcss/lib/parser.js中的parse方法,代碼量不大,可自行查看。

          第二階段:runPlugin

          PostCSS 本身并不處理任何具體的任務(wù),只有當(dāng)我們?yōu)槠涓郊痈鞣N插件之后,它才具有實(shí)用性。

          PostCSS 在把 CSS string 解析成 AST 對(duì)象后,會(huì)掃描一邊 AST 對(duì)象,每一種 AST 的對(duì)象都可以有對(duì)應(yīng)的監(jiān)聽器。在遍歷到某類型的對(duì)象時(shí),如果有對(duì)象的監(jiān)聽器,就會(huì)執(zhí)行其監(jiān)聽器。

          第一類監(jiān)聽器

          PostCSS 提供的「以特定屬性或者規(guī)則命名」的事件監(jiān)聽器,如下:

          CHILDREAN 代表子節(jié)點(diǎn)的事件監(jiān)聽器。

          //?root
          ['Root',?CHILDREN,?'RootExit']

          //?AtRule
          ['AtRule',?'AtRule-import',?CHILDREN,?'AtRuleExit',?'AtRuleExit-import']

          //?Rule
          ['Rule',?CHILDREN,?'RuleExit']

          //?Declaration
          ['Declaration',?'Declaration-color',?'DeclarationExit',?'DeclarationExit-color']

          //?Comment
          ['Comment',?'CommentExit']

          PostCSS 以深度優(yōu)先的方式遍歷 AST 樹。

          • 遍歷到 Root 根對(duì)象,第一步會(huì)執(zhí)行所有插件注冊(cè)的 Root 事件監(jiān)聽器,第二步檢查 Root 是否有子對(duì)象,如果有,則遍歷子對(duì)象,執(zhí)行子對(duì)象對(duì)應(yīng)的事件監(jiān)聽器;如果沒有子對(duì)象,則直接進(jìn)入第三步,第三步會(huì)執(zhí)行所有插件注冊(cè)的 RootExit 事件監(jiān)聽器。插件注冊(cè)的 Root、RootExit 事件的監(jiān)聽器只能是函數(shù)。函數(shù)的第一個(gè)參數(shù)是當(dāng)前訪問(wèn)的 AST 的 Root 對(duì)象,第二個(gè)參數(shù)是 postcss 的 Result 對(duì)象和一些其他屬性,通過(guò) Result 對(duì)象可以獲取 css string、opts 等信息。
          {
          ??Root:?(rootNode,?helps)?=>?{},
          ??RootExit:?(rootNode,?helps)?=>?{}
          }
          • 遍歷到 Rule 對(duì)象,則和訪問(wèn) Root 根對(duì)象是一樣的邏輯,先執(zhí)行所有插件注冊(cè)的 Rule 事件監(jiān)聽器,再遍歷子對(duì)象,最后執(zhí)行所有插件注冊(cè)的 RuleExit 事件監(jiān)聽器。插件注冊(cè)的 Rule、RuleExit 事件的監(jiān)聽器只能是函數(shù)。
          {
          ??Rule:?(ruleNode,?helps)?=>?{},
          ??RuleExit:?(ruleNode,?helps)?=>?{}
          }
          • 遍歷到 AtRule 對(duì)象。插件注冊(cè)的 AtRule 的事件監(jiān)聽器可以是函數(shù),也可以是對(duì)象。對(duì)象類型的監(jiān)聽器,對(duì)象屬性的 key 是 AtRule 對(duì)象的 name 值,value 是函數(shù)。AtRuleExit 是一樣的邏輯。事件的執(zhí)行順序是:['AtRule', 'AtRule-import', CHILDREN, 'AtRuleExit', 'AtRuleExit-import']。CHILDREAN 代表子節(jié)點(diǎn)的事件。``` // 函數(shù) { AtRule: (atRuleNode, helps) => {} }
          //?對(duì)象
          {
          ??AtRule:?{
          ??????import:?(atRuleNode,?helps)?=>?{},
          ??????keyframes:?(atRuleNode,?helps)?=>?{}
          ??}
          }
          • 遍歷到 Declaration 對(duì)象。插件注冊(cè)的 Declaration 的事件監(jiān)聽器可以是函數(shù),也可以是對(duì)象,對(duì)象屬性的 key 是 Declaration 對(duì)象的 prop 值,value 是函數(shù)。DeclarationExitExit 是一樣的邏輯。事件的執(zhí)行順序是:['Declaration', 'Declaration-color', 'DeclarationExit', 'DeclarationExit-color']。Declaration 沒有子對(duì)象,只需要執(zhí)行當(dāng)前對(duì)象的事件,不需要深度執(zhí)行子對(duì)象的事件。
          //?函數(shù)
          {
          ??Declaration:?(declarationNode,?helps)?=>?{}
          }

          //?對(duì)象
          {
          ??Declaration:?{
          ??????color:?(declarationNode,?helps)?=>?{},
          ??????border:?(declarationNode,?helps)?=>?{}
          ??}
          }
          • 遍歷到 Comment 對(duì)象。依次執(zhí)行所有插件注冊(cè)的 Comment 事件監(jiān)聽器,再執(zhí)行所有插件注冊(cè)的 CommentExit 事件監(jiān)聽器。

          第二類監(jiān)聽器

          除以特定屬性或者規(guī)則命名的事件監(jiān)聽器,PostCSS 還有以下四個(gè):

          {
          ??postcssPlugin:?string,
          ??prepare:?(result)?=>?{},
          ??Once:?(root,?helps)?=>?{},
          ??OnceExit:?(root,?helps)?=>?{},
          }

          PostCSS 插件事件的整體執(zhí)行是:[prepare, Once, ...一類事件,OnceExit],postcssPlugin 是插件名稱,不是事件監(jiān)聽器。

          • postcssPlugin:字符串類型,插件的名字,在插件執(zhí)行報(bào)錯(cuò),提示用戶是哪個(gè)插件報(bào)錯(cuò)了。
          • prepare:函數(shù)類型,prepare 是最先執(zhí)行的,在所有事件執(zhí)行前執(zhí)行的,插件多個(gè)監(jiān)聽器間共享數(shù)據(jù)時(shí)使用。prepare 的入?yún)⑹?Result 對(duì)象,返回值是監(jiān)聽器對(duì)象,通過(guò) Result 對(duì)象可以獲取 css string、opts 等信息。
          {
          ??postcssPlugin:?"PLUGIN?NAME",
          ??prepare(result)?{
          ????const?variables?=?{};
          ????return?{
          ??????Declaration(node)?{
          ????????if?(node.variable)?{
          ??????????variables[node.prop]?=?node.value;
          ????????}
          ??????},
          ??????OnceExit()?{
          ????????console.log(variables);
          ??????},
          ????};
          ??},
          };

          • Once:函數(shù)類型,在 prepare 后,一類事件前執(zhí)行,Once 只會(huì)執(zhí)行一次。
          {
          ???Once:?(root,?helps)?=>?{}
          }
          • OnceExit: 函數(shù)類型,在一類事件后執(zhí)行,OnceExit 只會(huì)執(zhí)行一次。

          插件源碼截圖

          此時(shí)再看市面上流行的基于 postcss 的工具,有沒有醍醐灌頂?

          autoprefixerpostcss-import-parserpostcss-modulespostcss-modules
          f1a9b0e93bd80d76582f42f0332ebc37.webp69e45d3f403270ebc8553855d9f3c3ee.webp3dc566f6d30a5ab9c49bae733a995cdf.webp5a0d2f875debf3ba95d31a87ea06901a.webpb14fd94caee25fd886b14f0b3d77f5b0.webp49045e1dd7ee67cd035b36cd88aea1c1.webp

          插件有哪些?

          基于 postcss 的插件有很多,可查閱:https://github.com/postcss/postcss/blob/main/docs/plugins.md。

          第三階段:generate

          generate 的過(guò)程依舊是以深度優(yōu)先的方式遍歷 AST 對(duì)象,針對(duì)不同的實(shí)例對(duì)象進(jìn)行字符串的拼接。算法對(duì)應(yīng)源碼中位置是:postcss/lib/stringifier.js中的stringify方法,代碼量不大,可自行查看。

          瀏覽 89
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91玉足脚交白嫩脚丫 | 香蕉一区二区三区四区 | 亚洲午夜福利电影 | 男人美女一起搞基视频网站 | 一本一道无码免费看视频 |