国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

CSS TreeShking 原理揭秘: 手寫 PurgeCss

開發 前端
TreeShking 是通過靜態分析的方式找出源碼中不會被使用的代碼進行刪除,達到減小編譯打包產物的代碼體積的目的。

[[439953]]

TreeShking 是通過靜態分析的方式找出源碼中不會被使用的代碼進行刪除,達到減小編譯打包產物的代碼體積的目的。

JS 我們會用 Webpack、Terser 進行 Tree Shking,而 CSS 會用 PurgeCss。

PurgeCss 會分析 html 或其他代碼中 css 選擇器的使用情況,進而刪除沒有被使用的 css。

是否對 PurgeCss 怎么找到無用的 css 的原理比較好奇呢?今天我們就來手寫個簡易版 PurgeCss 來探究下吧。

思路分析

PurgeCss 要指定 css 應用到哪些 html,它會分析 html 中的 css 選擇器,根據分析結果來刪除沒有用到的 css:

  1. const { PurgeCSS } = require('purgecss'
  2. const purgeCSSResult = await new PurgeCSS().purge({ 
  3.   content: ['**/*.html'], 
  4.   css: ['**/*.css'
  5. }) 

我們要做的事情就可以分為兩部分:

  • 提取 html 中的可能的 css 選擇器,包括 id、class、tag 等
  • 分析 css 中的 rule,根據選擇器是否被 html 使用,刪掉沒被用到的部分

從 html 中提取信息的部分,叫做 html 提取器(extractor)。

我們可以基于 posthtml 來實現 html 的提取器,它可以做 html 的 parse、分析、轉換等,api 和 postcss 類似。

css 的部分使用 postcss,通過 ast 可以分析出每一條 rule。

遍歷 css 的 rule,對每個 rule 的選擇器都判斷下是否在從 html 中提取到選擇器中,如果沒有,就代表沒有被使用,就刪掉該選擇器。

如果一個 rule 的所有的選擇器都刪掉了,那么就把這個 rule 刪掉。

這就是 purgecss 的實現思路。我們來寫下代碼。

代碼實現

我們來寫一個 postcss 插件來做這件事情,postcss 插件就是基于 AST 做 css 的分析和轉換的。

  1. const purgePlugin = (options) => { 
  2.    
  3.     return { 
  4.         postcssPlugin: 'postcss-purge'
  5.         Rule (rule) {} 
  6.     } 
  7.  
  8. module.exports = purgePlugin; 

postcss 插件的形式是一個函數,接收插件的配置參數,返回一個對象。對象里聲明 Rule、AtRule、Decl 等的 listener,也就是對不同 AST 的處理函數。

這個 postcss 插件的名字叫做 purge,可以被這樣調用:

  1. const postcss = require('postcss'); 
  2. const purge = require('./src/index'); 
  3. const fs = require('fs'); 
  4. const path = require('path'); 
  5. const css = fs.readFileSync('./example/index.css'); 
  6.  
  7. postcss([purge({ 
  8.     html: path.resolve('./example/index.html'), 
  9. })]).process(css).then(result => { 
  10.     console.log(result.css); 
  11. }); 

通過參數傳入 html 的路徑,插件里可以通過 option.html 拿到。

接下來我們來實現下這個插件。

前面分析過,實現過程整體分為兩步:

  • 通過 posthtml 提取 html 中的 id、class、tag
  • 遍歷 css 的 ast,刪掉沒被 html 使用的部分

我們封裝一個 htmlExtractor 來做提取的事情:

  1. const purgePlugin = (options) => { 
  2.     const extractInfo = { 
  3.         id: [], 
  4.         class: [], 
  5.         tag: [] 
  6.     }; 
  7.  
  8.     htmlExtractor(options && options.html, extractInfo); 
  9.  
  10.     return { 
  11.         postcssPlugin: 'postcss-purge'
  12.         Rule (rule) {} 
  13.     } 
  14.  
  15. module.exports = purgePlugin; 

htmlExtractor 的具體實現就是讀取 html 的內容,對 html 做 parse 生成 AST,遍歷 AST,記錄 id、class、tag:

  1. function htmlExtractor(html, extractInfo) { 
  2.     const content = fs.readFileSync(html, 'utf-8'); 
  3.  
  4.     const extractPlugin = options => tree => {       
  5.         return tree.walk(node => { 
  6.             extractInfo.tag.push(node.tag); 
  7.             if (node.attrs) { 
  8.               extractInfo.id.push(node.attrs.id) 
  9.               extractInfo.class.push(node.attrs.class) 
  10.             } 
  11.             return node 
  12.         }); 
  13.     } 
  14.  
  15.     posthtml([extractPlugin()]).process(content); 
  16.  
  17.     // 過濾掉空值 
  18.     extractInfo.id = extractInfo.id.filter(Boolean); 
  19.     extractInfo.class = extractInfo.class.filter(Boolean); 
  20.     extractInfo.tag = extractInfo.tag.filter(Boolean); 

posthtml 的插件形式和 postcss 類似,我們在 posthtml 插件里遍歷 AST 并記錄了一些信息。

最后,過濾掉 id、class、tag 中的空值,就完成了提取。

我們先不著急做下一步,先來測試下現在的功能。

我們準備這樣一個 html:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  7.     <title>Document</title> 
  8. </head> 
  9. <body> 
  10.     <div class="aaa"></div> 
  11.  
  12.     <div id="ccc"></div> 
  13.  
  14.     <span></span> 
  15. </body> 
  16. </html> 

測試下提取的信息:

可以看到,id、class、tag 都正確的從 html 中提取了出來。

接下來,我們繼續做下一步:從 css 的 AST 中刪掉沒被使用的部分。

我們聲明了 Rule 的 listener,可以拿到 rule 的 AST。要分析的是 selector 部分,需要先根據 “,” 做拆分,然后對每一個選擇器做處理。

  1. Rule (rule) {                         
  2.      const newSelector = rule.selector.split(',').map(item => { 
  3.         // 對每個選擇器做轉換 
  4.     }).filter(Boolean).join(','); 
  5.  
  6.     if(newSelector === '') { 
  7.         rule.remove(); 
  8.     } else { 
  9.         rule.selector = newSelector; 
  10.     } 

選擇器可以用 postcss-selector-parser 來做 parse、分析和轉換。

處理以后的選擇器如果都被刪掉了,就說明這個 rule 的樣式就沒用了,就刪掉這個 rule。否則可能只是刪掉了部分選擇器,該樣式還會被用到。

  1. const newSelector = rule.selector.split(',').map(item => { 
  2.     const transformed = selectorParser(transformSelector).processSync(item); 
  3.     return transformed !== item ? '' : item; 
  4. }).filter(Boolean).join(','); 
  5.  
  6. if(newSelector === '') { 
  7.     rule.remove(); 
  8. else { 
  9.     rule.selector = newSelector; 

接下來實現對選擇器的分析和轉換,也就是 transformSelector 函數。

這部分的邏輯就是對每個選擇器判斷下是否在從 html 提取到的選擇器中,如果不在,就刪掉。

  1. const transformSelector = selectors => { 
  2.     selectors.walk(selector => { 
  3.         selector.nodes && selector.nodes.forEach(selectorNode => { 
  4.             let shouldRemove = false
  5.             switch(selectorNode.type) { 
  6.                 case 'tag'
  7.                     if (extractInfo.tag.indexOf(selectorNode.value) == -1) { 
  8.                         shouldRemove = true
  9.                     } 
  10.                     break; 
  11.                 case 'class'
  12.                     if (extractInfo.class.indexOf(selectorNode.value) == -1) { 
  13.                         shouldRemove = true
  14.                     } 
  15.                     break; 
  16.                 case 'id'
  17.                     if (extractInfo.id.indexOf(selectorNode.value) == -1) { 
  18.                         shouldRemove = true
  19.                     } 
  20.                     break; 
  21.             } 
  22.  
  23.             if(shouldRemove) { 
  24.                 selectorNode.remove(); 
  25.             } 
  26.         }); 
  27.     }); 
  28. }; 

我們完成了 html 中選擇器信息的提取,和 css 根據 html 提取的信息做無用 rule 的刪除,插件的功能就已經完成了。

我們來測試下效果:

css:

  1. .aaa, ee , ff{ 
  2.     color: red; 
  3.     font-size: 12px; 
  4. .bbb { 
  5.     color: red; 
  6.     font-size: 12px; 
  7.  
  8. #ccc { 
  9.     color: red; 
  10.     font-size: 12px; 
  11.  
  12. #ddd { 
  13.     color: red; 
  14.     font-size: 12px; 
  15.  
  16. p { 
  17.     color: red; 
  18.     font-size: 12px; 
  19. span { 
  20.     color: red; 
  21.     font-size: 12px; 

html:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  7.     <title>Document</title> 
  8. </head> 
  9. <body> 
  10.     <div class="aaa"></div> 
  11.  
  12.     <div id="ccc"></div> 
  13.  
  14.     <span></span> 
  15. </body> 
  16. </html> 

按理說, p、#ddd、.bbb 的選擇器和樣式,ee、ff 的選擇器都會被刪除。

我們使用下該插件:

  1. const postcss = require('postcss'); 
  2. const purge = require('./src/index'); 
  3. const fs = require('fs'); 
  4. const path = require('path'); 
  5. const css = fs.readFileSync('./example/index.css'); 
  6.  
  7. postcss([purge({ 
  8.     html: path.resolve('./example/index.html'), 
  9. })]).process(css).then(result => { 
  10.     console.log(result.css); 
  11. }); 

經測試,功能是對的:

這就是 PurgeCss 的實現原理。我們完成了 css 的 three shaking!

代碼上傳到了 github:https://github.com/QuarkGluonPlasma/postcss-plugin-exercize

當然,我們只是簡易版實現,有的地方做的不完善:

  • 只實現了 html 提取器,而 PurgeCss 還有 jsx、pug、tsx 等提取器(不過思路都是一樣的)
  • 只處理了單文件,沒有處理多文件(再加個循環就行)
  • 只處理了 id、class、tag 選擇器,沒處理屬性選擇器(屬性選擇器的處理稍微復雜一些)

雖然沒有做到很完善,但是 PurgeCss 的實現思路已經通了,不是么~

總結

JS 的 TreeShking 使用 Webpack、Terser,而 CSS 的 TreeShking 使用 PurgeCss。

我們實現了一個簡易版的 PurgeCss 來理清了它的實現原理:

通過 html 提取器提取 html 中的選擇器信息,然后對 CSS 的 AST 做過濾,根據 Rule 的 selector 是否被使用到來刪掉沒用到的 rule,達到 TreeShking 的目的。

實現這個工具的過程中,我們學習了 postcss 和 posthtml 插件的寫法,這兩者形式上很類似,只不過一個針對 css 做分析和轉換,一個針對 html。

Postcss 可以分析和轉換 CSS,比如這里的刪除無用 css 就是一個很好的應用。你還見過別的 postcss 的很棒的應用場景么,不妨一起來討論下吧~

 

責任編輯:姜華 來源: 神光的編程秘籍
相關推薦

2010-08-24 13:34:11

CSSpadding

2022-04-26 08:32:36

CSS前端

2020-11-02 09:35:04

ReactHook

2020-12-03 08:14:45

Axios核心Promise

2010-08-25 13:54:29

CSStop

2021-05-13 23:30:17

JavaScript 原理揭秘

2010-09-14 09:24:40

CSS實例

2010-08-26 10:33:27

CSSborder

2019-11-15 15:12:19

Windows激活KMS

2010-09-06 09:50:34

id選擇器CSS

2010-08-23 10:43:21

DIVCSS

2010-09-15 15:03:52

CSS positio

2010-08-16 14:18:49

DIV+CSS

2010-09-17 15:25:03

JAVAJVM

2021-12-01 06:40:32

Bind原理實現

2010-08-25 08:47:16

CSScellspacingcellpadding

2010-09-03 13:23:07

absoluterelativeCSS

2010-08-31 15:07:45

CSS居中

2010-09-02 14:17:56

CSS浮動

2010-09-14 15:32:51

CSSdisplay:inl
點贊
收藏

51CTO技術棧公眾號

中文字幕av一区二区| 国产午夜精品一区理论片| 中文在线8资源库| 最近2019免费中文字幕视频三| 另类av一区二区| 天堂аⅴ在线最新版在线| 国产精品视频久久久久| 欧美三级蜜桃2在线观看| 精品国精品国产自在久国产应用| 日本在线视频www色| 日本片在线观看| 尤物九九久久国产精品的分类| 日韩制服一区| 中文字幕五月欧美| julia京香一区二区三区| 亚洲国产古装精品网站| 欧美日韩国产v| 日韩欧美一级在线播放| 欧美日韩经典丝袜| 亚洲视频999| 午夜激情电影在线播放| 在线观看成人黄色| 欧美日韩亚洲一区二区三区在线| 日本亚洲欧美三级| 成人在线啊v| 中文字幕在线不卡一区二区三区| 91精彩视频在线观看| 久久99久久99精品免视看婷婷| 久久夜色精品国产欧美乱| 美女羞羞视频在线观看| 亚洲精品国产精品久久清纯直播| 欧洲午夜精品| 欧美成人乱码一区二区三区| 午夜影院观看视频免费| 日本高清不卡在线观看| 国产不卡人人| 日本高清视频一区| 成人免费91| 国产日韩欧美视频| 亚洲bt欧美bt精品777| 少妇精69xxtheporn| 精品国产日韩欧美| 日韩亚洲不卡在线| 久久久久国产免费免费| 精品亚洲成a人片在线观看| 在线播放国产一区二区三区| 国内精品视频在线观看| 欧美国产日韩一区| 欧美日韩综合| 国产91对白刺激露脸在线观看| 亚洲国产日韩精品| a欧美人片人妖| 亚洲一区二区三区四区在线播放| 成人在线综合网| 久青草国产在线| 精品国产成人在线| 午夜影院在线免费观看| 国产一区二区三区在线免费观看| 欧美成人自拍| 国产黄色特级片| 欧美一区二区精品在线| 成人动漫免费在线观看| av影院在线播放| 欧美日韩一区视频| 曰本一区二区三区视频| 你懂的av在线| 欧美一区二区三区免费| 欧州一区二区| 91看片在线免费观看| 亚洲欧洲日产国码av系列天堂| 一区二区三区国产精华| 四虎最新网站| 一区二区成人av| 久久一区二区三区超碰国产精品| 欧美连裤袜在线视频| 午夜日韩在线电影| 另类图片第一页| 亚洲精品蜜桃久久久久久| 91精品久久久久久久91蜜桃| 日韩在线精品| www.成人精品免费网站青椒| 久久影院资源网| 东方欧美亚洲色图在线| 国产福利影院在线观看| 一区二区免费在线| 精品国产亚洲一区二区三区在线| 国产成人亚洲综合91精品| 91免费视频大全| se01亚洲视频| 老司机av福利| 亚洲成人久久一区| 青青草精品视频| 成人日日夜夜| 国产欧美一区二区三区视频| 国产精品久久久久天堂| 伊人久久亚洲| 精品国产成人av在线免| 欧美成人午夜剧场免费观看| 激情小说一区| 国产亚洲天堂网| 亚洲成av人片www| 91日韩在线| www.夜夜爽| 欧美一二三视频| 国产午夜精品一区二区三区欧美 | 欧美xxxx18国产| 久久一夜天堂av一区二区三区 | 日本熟妇人妻xxxx| 伊人伊人伊人久久| 久久黄色级2电影| 精品91久久| 人妻无码久久一区二区三区免费 | 538任你躁在线精品免费| 97超碰国产精品女人人人爽| 99久久99精品久久久久久| 小说区图片区亚洲| 国产男女爽爽爽| 99久久精品久久久久久ai换脸| 欧美人妖巨大在线| 久草热8精品视频在线观看| 日韩中文在线播放| 激情综合网五月激情| 欧美一区二区美女| 国产成人精品一区二区三区网站观看| 中文在线最新版地址| 国产精品igao| 成人夜晚看av| 国产情人综合久久777777| 九九亚洲精品| аⅴ资源新版在线天堂| 96精品视频在线| 欧美在线小视频| 国产精品一二三四五| 大型av综合网站| 在线视频一二三区| 久久频这里精品99香蕉| 欧美日韩中国免费专区在线看| 老牛国内精品亚洲成av人片| 男同在线观看| 在线视频一区观看| 欧美在线免费视频| 日韩亚洲欧美高清| 91精品成人| 精品精品导航| 天天爱天天做色综合| 精品麻豆av| 日韩在线一区二区三区免费视频| 亚洲资源中文字幕| 国产精品综合av一区二区国产馆| 亚洲aaa级| 2001个疯子在线观看| 天堂中文字幕一二区| 欧美日韩免费高清| 97久久久久久| 日韩精品视频观看| 久久av资源站| 成人黄色小视频| 久久人体大尺度| 黄色网址在线播放| 人妻无码久久一区二区三区免费| 成人免费视频网| 少妇高潮久久77777| 欧美图区在线视频| 国内精品久久久久国产盗摄免费观看完整版| 日韩av影片| 蜜桃日韩视频| 亚洲成va人在线观看| 国产精品99久久久久久有的能看| 先锋资源久久| 欧美一区 二区 三区| 免费在线性爱视频| 欧美成人三级在线播放| 400部精品国偷自产在线观看 | 成人黄色av片| 韩国一区二区三区美女美女秀| 久久久久久有精品国产| 久久久天堂av| 喷白浆一区二区| free性欧美| 天堂网www中文在线| 国产精品入口免费软件| 亚洲爆乳无码精品aaa片蜜桃| 成人免费视频网站| 国产精品久久久久久亚洲影视| 最新的欧美黄色| 精品噜噜噜噜久久久久久久久试看| 欧美日韩一区二区精品| 亚洲视频一区二区免费在线观看| 成人综合婷婷国产精品久久蜜臀 | 久久人人97超碰精品888| 亚洲精品一二区| 日韩一区二区高清| 91.成人天堂一区| 色女孩综合影院| 一本色道综合亚洲| 久久精品国产一区二区三区免费看| 亚洲精品电影| 日韩毛片视频| 日韩一区电影| 伊人久久大香线蕉|