偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

CSS TreeShking 原理揭秘: 手寫 PurgeCss

開發(fā) 前端
TreeShking 是通過靜態(tài)分析的方式找出源碼中不會(huì)被使用的代碼進(jìn)行刪除,達(dá)到減小編譯打包產(chǎn)物的代碼體積的目的。

[[439953]]

TreeShking 是通過靜態(tài)分析的方式找出源碼中不會(huì)被使用的代碼進(jìn)行刪除,達(dá)到減小編譯打包產(chǎn)物的代碼體積的目的。

JS 我們會(huì)用 Webpack、Terser 進(jìn)行 Tree Shking,而 CSS 會(huì)用 PurgeCss。

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

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

思路分析

PurgeCss 要指定 css 應(yīng)用到哪些 html,它會(huì)分析 html 中的 css 選擇器,根據(jù)分析結(jié)果來刪除沒有用到的 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,根據(jù)選擇器是否被 html 使用,刪掉沒被用到的部分

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

我們可以基于 posthtml 來實(shí)現(xiàn) html 的提取器,它可以做 html 的 parse、分析、轉(zhuǎn)換等,api 和 postcss 類似。

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

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

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

這就是 purgecss 的實(shí)現(xiàn)思路。我們來寫下代碼。

代碼實(shí)現(xiàn)

我們來寫一個(gè) postcss 插件來做這件事情,postcss 插件就是基于 AST 做 css 的分析和轉(zhuǎn)換的。

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

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

這個(gè) postcss 插件的名字叫做 purge,可以被這樣調(diào)用:

  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. }); 

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

接下來我們來實(shí)現(xiàn)下這個(gè)插件。

前面分析過,實(shí)現(xiàn)過程整體分為兩步:

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

我們封裝一個(gè) 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 的具體實(shí)現(xiàn)就是讀取 html 的內(nèi)容,對 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 中的空值,就完成了提取。

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

我們準(zhǔn)備這樣一個(gè) 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 中提取了出來。

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

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

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

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

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

  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; 

接下來實(shí)現(xiàn)對選擇器的分析和轉(zhuǎn)換,也就是 transformSelector 函數(shù)。

這部分的邏輯就是對每個(gè)選擇器判斷下是否在從 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 根據(jù) html 提取的信息做無用 rule 的刪除,插件的功能就已經(jīng)完成了。

我們來測試下效果:

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 的選擇器都會(huì)被刪除。

我們使用下該插件:

  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. }); 

經(jīng)測試,功能是對的:

這就是 PurgeCss 的實(shí)現(xiàn)原理。我們完成了 css 的 three shaking!

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

當(dāng)然,我們只是簡易版實(shí)現(xiàn),有的地方做的不完善:

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

雖然沒有做到很完善,但是 PurgeCss 的實(shí)現(xiàn)思路已經(jīng)通了,不是么~

總結(jié)

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

我們實(shí)現(xiàn)了一個(gè)簡易版的 PurgeCss 來理清了它的實(shí)現(xiàn)原理:

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

實(shí)現(xiàn)這個(gè)工具的過程中,我們學(xué)習(xí)了 postcss 和 posthtml 插件的寫法,這兩者形式上很類似,只不過一個(gè)針對 css 做分析和轉(zhuǎn)換,一個(gè)針對 html。

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

 

責(zé)任編輯:姜華 來源: 神光的編程秘籍
相關(guān)推薦

2022-04-26 08:32:36

CSS前端

2010-08-24 13:34:11

CSSpadding

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實(shí)例

2010-08-26 10:33:27

CSSborder

2019-11-15 15:12:19

Windows激活KMS

2010-09-15 15:03:52

CSS positio

2010-09-06 09:50:34

id選擇器CSS

2010-08-23 10:43:21

DIVCSS

2010-08-16 14:18:49

DIV+CSS

2010-09-17 15:25:03

JAVAJVM

2010-09-02 14:17:56

CSS浮動(dòng)

2010-09-06 11:17:19

CSS相對定位CSS絕對定位

2010-09-14 15:32:51

CSSdisplay:inl

2010-09-07 12:56:49

id選擇器CSS

2023-07-05 10:11:02

2021-12-01 06:40:32

Bind原理實(shí)現(xiàn)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)