自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Webpack 性能系列四:分包優(yōu)化

開發(fā) 前端
默認(rèn)情況下,Webpack 會(huì)將所有代碼構(gòu)建成一個(gè)單獨(dú)的包,這在小型項(xiàng)目通常不會(huì)有明顯的性能問題,但伴隨著項(xiàng)目的推進(jìn),包體積逐步增長(zhǎng)可能會(huì)導(dǎo)致應(yīng)用的響應(yīng)耗時(shí)越來越長(zhǎng)。

[[434094]]

一、什么是分包

默認(rèn)情況下,Webpack 會(huì)將所有代碼構(gòu)建成一個(gè)單獨(dú)的包,這在小型項(xiàng)目通常不會(huì)有明顯的性能問題,但伴隨著項(xiàng)目的推進(jìn),包體積逐步增長(zhǎng)可能會(huì)導(dǎo)致應(yīng)用的響應(yīng)耗時(shí)越來越長(zhǎng)。歸根結(jié)底這種將所有資源打包成一個(gè)文件的方式存在兩個(gè)弊端:

  • 「資源冗余」:客戶端必須等待整個(gè)應(yīng)用的代碼包都加載完畢才能啟動(dòng)運(yùn)行,但可能用戶當(dāng)下訪問的內(nèi)容只需要使用其中一部分代碼
  • 「緩存失效」:將所有資源達(dá)成一個(gè)包后,所有改動(dòng) —— 即使只是修改了一個(gè)字符,客戶端都需要重新下載整個(gè)代碼包,緩存命中率極低

這些問題都可以通過對(duì)產(chǎn)物做適當(dāng)?shù)姆纸獠鸢鉀Q,例如 node_modules 中的資源通常變動(dòng)較少,可以抽成一個(gè)獨(dú)立的包,那么業(yè)務(wù)代碼的頻繁變動(dòng)不會(huì)導(dǎo)致這部分第三方庫(kù)資源被無(wú)意義地重復(fù)加載。為此,Webpack 專門提供了 SplitChunksPlugin 插件,用于實(shí)現(xiàn)產(chǎn)物分包。

二、使用 SplitChunksPlugin

SplitChunksPlugin 是 Webpack 4 之后引入的分包方案(此前為 CommonsChunkPlugin),它能夠基于一些啟發(fā)式的規(guī)則將 Module 編排進(jìn)不同的 Chunk 序列,并最終將應(yīng)用代碼分門別類打包出多份產(chǎn)物,從而實(shí)現(xiàn)分包功能。

使用上,SplitChunksPlugin 的配置規(guī)則比較抽象,算得上 Webpack 的一個(gè)難點(diǎn),仔細(xì)拆解后關(guān)鍵邏輯在于:

SplitChunksPlugin 通過 module 被引用頻率、chunk 大小、包請(qǐng)求數(shù)三個(gè)維度決定是否執(zhí)行分包操作,這些決策都可以通過 optimization.splitChunks 配置項(xiàng)調(diào)整定制,基于這些維度我們可以實(shí)現(xiàn):

單獨(dú)打包某些特定路徑的內(nèi)容,例如 node_modules 打包為 vendors單獨(dú)打包使用頻率較高的文件

SplitChunksPlugin 還提供配置組概念 optimization.splitChunks.cacheGroup,用于為不同類型的資源設(shè)置更有針對(duì)性的配置信息

SplitChunksPlugin 還內(nèi)置了 default 與 defaultVendors 兩個(gè)配置組,提供一些開箱即用的特性:

  • node_modules 資源會(huì)命中 defaultVendors 規(guī)則,并被單獨(dú)打包
  • 只有包體超過 20kb 的 Chunk 才會(huì)被單獨(dú)打包
  • 加載 Async Chunk 所需請(qǐng)求數(shù)不得超過 30
  • 加載 Initial Chunk 所需請(qǐng)求數(shù)不得超過 30

這里所說的請(qǐng)求數(shù)不能等價(jià)對(duì)標(biāo)到 http 資源請(qǐng)求數(shù),下文會(huì)細(xì)講

綜上,分包邏輯基本上都圍繞著 Module 與 Chunk 展開,在介紹具體用法之前,有必要回顧一下 Chunk 的基礎(chǔ)知識(shí)。

2.1 什么是 Chunk

在《有點(diǎn)難的知識(shí)點(diǎn):Webpack Chunk 分包規(guī)則詳解》一文中,我們已經(jīng)了解到 Chunk 是打包產(chǎn)物的基本組織單位,讀者可以等價(jià)認(rèn)為有多少 Chunk 就會(huì)對(duì)應(yīng)生成多少產(chǎn)物(Bundle)。Webpack 內(nèi)部包含三種類型的 Chunk:

  • Initial Chunk:基于 Entry 配置項(xiàng)生成的 Chunk
  • Async Chunk:異步模塊引用,如 import(xxx) 語(yǔ)句對(duì)應(yīng)的異步 Chunk
  • Runtime Chunk:只包含運(yùn)行時(shí)代碼的 Chunk

關(guān)于運(yùn)行時(shí)的概念,可參考《Webpack 原理系列六:徹底理解 Webpack 運(yùn)行時(shí)》

而 SplitChunksPlugin 默認(rèn)只對(duì) Async Chunk 生效,開發(fā)者也可以通過 optimization.splitChunks.chunks 調(diào)整作用范圍,該配置項(xiàng)支持如下值:

  • 字符串 'all' :對(duì) Initial Chunk 與 Async Chunk 都生效,建議優(yōu)先使用該值
  • 字符串 'initial' :只對(duì) Initial Chunk 生效
  • 字符串 'async' :只對(duì) Async Chunk 生效
  • 函數(shù) (chunk) => boolean :該函數(shù)返回 true 時(shí)生效

例如:

  1. module.exports = { 
  2.   //... 
  3.   optimization: { 
  4.     splitChunks: { 
  5.       chunks: 'all'
  6.     }, 
  7.   }, 

2.2 分包策略詳解

2.2.1 根據(jù) Module 使用頻率分包

SplitChunksPlugin 支持按 Module 被 Chunk 引用的次數(shù)決定是否進(jìn)行分包,開發(fā)者可通過 optimization.splitChunks.minChunks 設(shè)定最小引用次數(shù),例如:

  1. module.exports = { 
  2.   //... 
  3.   optimization: { 
  4.     splitChunks: { 
  5.       // 設(shè)定引用次數(shù)超過 4 的模塊才進(jìn)行分包 
  6.       minChunks: 3 
  7.     }, 
  8.   }, 

需要注意,這里“被 Chunk 引用次數(shù)”并不直接等價(jià)于被 import 的次數(shù),而是取決于上游調(diào)用者是否被視作 Initial Chunk 或 Async Chunk 處理,例如:

  1. // common.js 
  2. export default "common chunk"
  3.  
  4. // async-module.js 
  5. import common from './common' 
  6.  
  7. // entry-a.js 
  8. import common from './common' 
  9. import('./async-module'
  10.  
  11. // entry-b.js 
  12. import common from './common' 
  13.  
  14. // webpack.config.js 
  15. module.exports = { 
  16.   entry: { 
  17.     entry1: './src/entry-a.js'
  18.     entry2: './src/entry-b.js' 
  19.   }, 
  20.   // ... 
  21.   optimization: { 
  22.     splitChunks: { 
  23.       minChunks: 2 
  24.     } 
  25.   } 
  26. }; 

上例包含四個(gè)模塊,形成如下模塊關(guān)系圖:

示例中,entry-a、entry-b 分別被視作 Initial Chunk 處理;async-module 被 entry-a 以異步方式引入,因此被視作 Async Chunk 處理。那么對(duì)于 common 模塊來說,分別被三個(gè)不同的 Chunk 引入,此時(shí)引用次數(shù)為 3,命中 optimization.splitChunks.minChunks = 2 規(guī)則,因此該模塊「可能」會(huì)被單獨(dú)分包,最終產(chǎn)物:

  • entry-a.js
  • entry-b.js
  • async-module.js
  • commont.js

2.2.2 限制分包數(shù)量

在滿足 minChunks 基礎(chǔ)上,還可以通過 maxInitialRequest/maxAsyncRequests 配置項(xiàng)限定分包數(shù)量,配置項(xiàng)語(yǔ)義:

  • maxInitialRequest:用于設(shè)置 Initial Chunk 最大并行請(qǐng)求數(shù)
  • maxAsyncRequests:用于設(shè)置 Async Chunk 最大并行請(qǐng)求數(shù)

這里所說的“請(qǐng)求數(shù)”,是指加載一個(gè) Chunk 時(shí)所需同步加載的分包數(shù)。例如對(duì)于一個(gè) Chunk A,如果根據(jù)分包規(guī)則(如模塊引用次數(shù)、第三方包)分離出了若干子 Chunk A¡,那么請(qǐng)求 A 時(shí),瀏覽器需要同時(shí)請(qǐng)求所有的 A¡,此時(shí)并行請(qǐng)求數(shù)等于 ¡ 個(gè)分包加 A 主包,即 ¡+1。

舉個(gè)例子,對(duì)于上例所說的模塊關(guān)系:

圖片

若 minChunks = 2 ,則 common 模塊命中 minChunks 規(guī)則被獨(dú)立分包,瀏覽器請(qǐng)求 entry-a 時(shí),則需要同時(shí)請(qǐng)求 common 包,并行請(qǐng)求數(shù)為 1 + 1=2。

而對(duì)于下述模塊關(guān)系:

圖片

若 minChunks = 2 ,則 common-1 、common-2 同時(shí)命中 minChunks 規(guī)則被分別打包,瀏覽器請(qǐng)求 entry-b 時(shí)需要同時(shí)請(qǐng)求 common-1 、common-2 兩個(gè)分包,并行數(shù)為 2 + 1 = 3,此時(shí)若 maxInitialRequest = 2,則分包數(shù)超過閾值,SplitChunksPlugin 會(huì)放棄 common-1 、common-2 中體積較小的分包。maxAsyncRequest 邏輯與此類似,不在贅述。

并行請(qǐng)求數(shù)關(guān)鍵邏輯總結(jié)如下:

  • Initial Chunk 本身算一個(gè)請(qǐng)求
  • Async Chunk 不算并行請(qǐng)求
  • 通過 runtimeChunk 拆分出的 runtime 不算并行請(qǐng)求
  • 如果同時(shí)有兩個(gè) Chunk 滿足拆分規(guī)則,但是 maxInitialRequests(或 maxAsyncRequest) 的值只能允許再拆分一個(gè)模塊,那么體積更大的模塊會(huì)被優(yōu)先拆解

2.2.3 限制分包體積

在滿足 minChunks 與 maxInitialRequests 的基礎(chǔ)上,SplitChunksPlugin 還會(huì)進(jìn)一步判斷 Chunk 包大小決定是否分包,這一規(guī)則相關(guān)的配置項(xiàng)非常多:

  • minSize:超過這個(gè)尺寸的 Chunk 才會(huì)正式被分包
  • maxSize:超過這個(gè)尺寸的 Chunk 會(huì)嘗試?yán)^續(xù)做分包
  • maxAsyncSize:與 maxSize 功能類似,但只對(duì)異步引入的模塊生效
  • maxInitialSize:與 maxSize 類似,但只對(duì) entry 配置的入口模塊生效
  • enforceSizeThreshold:超過這個(gè)尺寸的 Chunk 會(huì)被強(qiáng)制分包,忽略上述其它 size 限制

那么,結(jié)合前面介紹的兩種規(guī)則,SplitChunksPlugin 的主體流程如下:

  1. SplitChunksPlugin 嘗試將命中 minChunks 規(guī)則的 Module 統(tǒng)一抽到一個(gè)額外的 Chunk 對(duì)象;
  2. 判斷該 Chunk 是否滿足 maxInitialRequests 閾值,若滿足則進(jìn)行下一步
  3. 判斷該 Chunk 資源的體積是否大于上述配置項(xiàng) minSize 聲明的下限閾值;
  • 如果體積「小于」 minSize 則取消這次分包,對(duì)應(yīng)的 Module 依然會(huì)被合并入原來的 Chunk
  • 如果 Chunk 體積「大于」 minSize 則判斷是否超過 maxSize、maxAsyncSize、maxInitialSize 聲明的上限閾值,如果超過則嘗試將該 Chunk 繼續(xù)分割成更小的部分

雖然 maxSize 等上限閾值邏輯會(huì)產(chǎn)生更多的包體,但緩存粒度會(huì)更小,命中率相對(duì)也會(huì)更高,配合持久緩存與 HTTP 2 的多路復(fù)用能力,網(wǎng)絡(luò)性能反而會(huì)有正向收益。

以上述模塊關(guān)系為例:

圖片

若此時(shí) Webpack 配置的 minChunks 大于 2,且 maxInitialRequests 也同樣大于 2,如果 common 模塊的體積大于上述說明的 minxSize 配置項(xiàng)則分包成功,commont 會(huì)被分離為單獨(dú)的 Chunk,否則會(huì)被合并入原來的 3 個(gè) Chunk。

注意,這些屬性的優(yōu)先級(jí)順序?yàn)椋?/p>

maxInitialRequest/maxAsyncRequests < maxSize < minSize而命中 enforceSizeThreshold 閾值的 Chunk 會(huì)直接跳過這些屬性判斷,強(qiáng)制進(jìn)行分包。

2.3 使用cacheGroups

2.3.1 理解緩存組

除上述 minChunks、maxInitialRequest、minSize 等基礎(chǔ)規(guī)則外,SplitChunksPlugin 還提供了 cacheGroups 配置項(xiàng)用于為不同文件組設(shè)置不同的規(guī)則,例如:

  1. module.exports = { 
  2.   //... 
  3.   optimization: { 
  4.     splitChunks: { 
  5.       cacheGroups: { 
  6.         vendors: { 
  7.             test: /[\\/]node_modules[\\/]/, 
  8.             minChunks: 1, 
  9.             minSize: 0 
  10.         } 
  11.       }, 
  12.     }, 
  13.   }, 
  14. }; 

示例通過 cacheGroups 屬性設(shè)置 vendors 緩存組,所有命中 vendors.test 規(guī)則的模塊都會(huì)被視作 vendors 分組,優(yōu)先應(yīng)用該組下的 minChunks、minSize 等分包配置。

除了 minChunks 等分包基礎(chǔ)配置項(xiàng)之外,cacheGroups 還支持一些與分組邏輯強(qiáng)相關(guān)的屬性,包括:

  • test:接受正則表達(dá)式、函數(shù)及字符串,所有符合 test 判斷的 Module 或 Chunk 都會(huì)被分到該組
  • type:接受正則表達(dá)式、函數(shù)及字符串,與 test 類似均用于篩選分組命中的模塊,區(qū)別是它判斷的依據(jù)是文件類型而不是文件名,例如 type = 'json' 會(huì)命中所有 JSON 文件
  • idHint:字符串型,用于設(shè)置 Chunk ID,它還會(huì)被追加到最終產(chǎn)物文件名中,例如 idHint = 'vendors' 時(shí),輸出產(chǎn)物文件名形如 vendors-xxx-xxx.js
  • priority:數(shù)字型,用于設(shè)置該分組的優(yōu)先級(jí),若模塊命中多個(gè)緩存組,則優(yōu)先被分到 priority 更大的組

緩存組的作用在于能為不同類型的資源設(shè)置更具適用性的分包規(guī)則,一個(gè)典型場(chǎng)景是將所有 node_modules 下的模塊統(tǒng)一打包到 vendors 產(chǎn)物,從而實(shí)現(xiàn)第三方庫(kù)與業(yè)務(wù)代碼的分離。

2.3.2 默認(rèn)分組

Webpack 提供了兩個(gè)開箱即用的 cacheGroups,分別命名為 default 與 defaultVendors,默認(rèn)配置:

  1. module.exports = { 
  2.   //... 
  3.   optimization: { 
  4.     splitChunks: { 
  5.       cacheGroups: { 
  6.         default: { 
  7.           idHint: ""
  8.           reuseExistingChunk: true
  9.           minChunks: 2, 
  10.           priority: -20 
  11.         }, 
  12.         defaultVendors: { 
  13.           idHint: "vendors"
  14.           reuseExistingChunk: true
  15.           test: /[\\/]node_modules[\\/]/i, 
  16.           priority: -10 
  17.         } 
  18.       }, 
  19.     }, 
  20.   }, 
  21. }; 

這兩個(gè)配置組能幫助我們:

  • 將所有 node_modules 中的資源單獨(dú)打包到 vendors-xxx-xx.js 命名的產(chǎn)物
  • 對(duì)引用次數(shù)大于等于 2 的模塊,也就是被多個(gè) Chunk 引用的模塊,單獨(dú)打包

開發(fā)者也可以將默認(rèn)分組設(shè)置為 false,關(guān)閉分組配置,例如:

  1. module.exports = { 
  2.   //... 
  3.   optimization: { 
  4.     splitChunks: { 
  5.       cacheGroups: { 
  6.         defaultfalse 
  7.       }, 
  8.     }, 
  9.   }, 
  10. }; 

2.4 配置項(xiàng)回顧

最后,我們?cè)倩仡櫼幌?SplitChunksPlugin 支持的配置項(xiàng):

  • minChunks:用于設(shè)置引用閾值,被引用次數(shù)超過該閾值的 Module 才會(huì)進(jìn)行分包處理
  • maxInitialRequest/maxAsyncRequests:用于限制 Initial Chunk(或 Async Chunk) 最大并行請(qǐng)求數(shù),本質(zhì)上是在限制最終產(chǎn)生的分包數(shù)量
  • minSize:超過這個(gè)尺寸的 Chunk 才會(huì)正式被分包
  • maxSize:超過這個(gè)尺寸的 Chunk 會(huì)嘗試?yán)^續(xù)做分包
  • maxAsyncSize:與 maxSize 功能類似,但只對(duì)異步引入的模塊生效
  • maxInitialSize:與 maxSize 類似,但只對(duì) entry 配置的入口模塊生效
  • enforceSizeThreshold:超過這個(gè)尺寸的 Chunk 會(huì)被強(qiáng)制分包,忽略上述其它 size 限制
  • cacheGroups:用于設(shè)置緩存組規(guī)則,為不同類型的資源設(shè)置更有針對(duì)性的分包策略

三、拆分運(yùn)行時(shí)包

在《Webpack 原理系列六:徹底理解 Webpack 運(yùn)行時(shí)》一文中,已經(jīng)比較深入介紹 Webpack 運(yùn)行時(shí)的概念、組成、作用與生成機(jī)制,大致上我們可以將運(yùn)行時(shí)理解為一種補(bǔ)齊模塊化、異步加載等能力的應(yīng)用骨架,用于支撐 Webpack 產(chǎn)物在各種環(huán)境下的正常運(yùn)行。

運(yùn)行時(shí)代碼的內(nèi)容由業(yè)務(wù)代碼所使用到的特性決定,例如當(dāng) Webpack 檢測(cè)到業(yè)務(wù)代碼中使用了異步加載能力,就會(huì)將異步加載相關(guān)的運(yùn)行時(shí)注入到產(chǎn)物中,因此業(yè)務(wù)代碼用到的特性越多,運(yùn)行時(shí)就會(huì)越大,有時(shí)甚至可以超過 1M 之多。

此時(shí),可以將 optimization.runtimeChunk 設(shè)置為 true,以此將運(yùn)行時(shí)代碼拆分到一個(gè)獨(dú)立的 Chunk,實(shí)現(xiàn)分包。

四、最佳實(shí)踐

那么,如何設(shè)置最適合項(xiàng)目情況的分包規(guī)則呢?這個(gè)問題并沒有放諸四海皆準(zhǔn)的通用答案,因?yàn)檐浖到y(tǒng)與現(xiàn)實(shí)世界的復(fù)雜性,決定了很多計(jì)算機(jī)問題并沒有銀彈,不過我個(gè)人還是總結(jié)了幾條可供參考的最佳實(shí)踐:

「盡量將第三方庫(kù)拆為獨(dú)立分包」

例如在一個(gè) React + Redux 項(xiàng)目中,可想而知應(yīng)用中的大多數(shù)頁(yè)面都會(huì)依賴于這兩個(gè)庫(kù),那么就應(yīng)該將它們從具體頁(yè)面剝離,避免重復(fù)加載。

但對(duì)于使用頻率并不高的第三方庫(kù),就需要按實(shí)際情況靈活判斷,例如項(xiàng)目中只有某個(gè)頁(yè)面 A 接入了 Three.js,如果將這個(gè)庫(kù)跟其它依賴打包在一起,那用戶在訪問其它頁(yè)面的時(shí)候都需要加載 Three.js,最終效果可能反而得不償失,這個(gè)時(shí)候可以嘗試使用異步加載功能將 Three.js 獨(dú)立分包

「保持按路由分包,減少首屏資源負(fù)載」

設(shè)想一個(gè)超過 10 個(gè)頁(yè)面的應(yīng)用,假如將這些頁(yè)面代碼全部打包在一起,那么用戶訪問其中任意一個(gè)頁(yè)面都需要等待其余 9 個(gè)頁(yè)面的代碼全部加載完畢后才能開始運(yùn)行應(yīng)用,這對(duì) TTI 等性能指標(biāo)明顯是不友好的,所以應(yīng)該盡量保持按路由維度做異步模塊加載,所幸很多知名框架如 React、Vue 對(duì)此都有很成熟的技術(shù)支持

「盡量保持」 **chunks = 'all'**optimization.splitChunks.chunks 配置項(xiàng)用于設(shè)置 SplitChunksPlugin 的工作范圍,我們應(yīng)該盡量保持 chunks = 'all' 從而最大程度優(yōu)化分包邏輯

 

責(zé)任編輯:姜華 來源: Tecvan
相關(guān)推薦

2020-09-19 21:26:56

webpack

2021-10-25 10:23:49

Webpack 前端Tree shakin

2019-03-15 15:00:49

Webpack構(gòu)建速度前端

2021-11-15 09:44:49

Webpack 前端 Scope Hois

2013-12-17 16:21:17

iOSiOS性能優(yōu)化

2019-03-26 10:02:16

WebpackJavascript前端

2019-07-25 13:22:43

AndroidAPK文件優(yōu)化

2020-12-03 10:40:23

webpack加載原理前端

2019-03-05 10:20:49

WebWebpack分離數(shù)據(jù)

2022-03-29 13:27:22

Android優(yōu)化APP

2022-07-19 16:47:53

Android抖音

2021-09-03 09:44:13

移動(dòng)端性能優(yōu)化U-APM

2023-04-27 08:35:20

Webpack 4性能優(yōu)化

2010-04-28 12:02:37

Forefront網(wǎng)絡(luò)優(yōu)化

2009-07-17 16:43:02

JRuby性能優(yōu)化

2021-06-07 05:32:53

Webpack Chunk 前端

2013-01-10 09:47:09

HBase性能優(yōu)化

2017-07-11 15:50:11

前端webpack2優(yōu)化

2023-05-31 08:19:23

Webpack4Webpack 5

2021-09-06 06:45:06

Webpack優(yōu)化MindMaster
點(diǎn)贊
收藏

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