webpack SplitChunksPlugin 配置詳解

When ?什么時(shí)候進(jìn)行處理

首先我們要清楚 SplitChunksPlugin 插件侵入 webpack 流程的時(shí)機(jī),不然不能很好地理解它的配置參數(shù)到底起什么作用故痊。通過(guò)源碼得知它是在CompilationoptimizeChunksAdvanced鉤子觸發(fā)時(shí)進(jìn)行工作的谒兄,而此時(shí) chunk 已經(jīng)生成并完成了初步優(yōu)化独令。

【webpack 之 Chunk】中端朵,我們已經(jīng)分析過(guò)這時(shí) chunk 分包的狀態(tài):

  1. 同一個(gè) entry 入口模塊與它的同步依賴(直接/間接) 組織成一個(gè) chunk,還包含 runtime (webpackBootstrap 自執(zhí)行函數(shù)的形式)燃箭。
  2. 每一個(gè)異步模塊與它的同步依賴單獨(dú)組成一個(gè) chunk冲呢。其中只會(huì)包含入口 chunk 中不存在的同步依賴;若存在同步第三方包招狸,也會(huì)被單獨(dú)打包成一個(gè) chunk敬拓。

那么,SplitChunksPlugin 就是在這個(gè)基礎(chǔ)上再做優(yōu)化了裙戏,也就是對(duì)這些 chunk 進(jìn)行進(jìn)一步的組合/分割乘凸。

Why and How ?為何要代碼分割及如何分割

Code Splitting 拆包優(yōu)化的最終目標(biāo)是什么累榜?無(wú)非是:

  1. 把更新頻率低的代碼和內(nèi)容頻繁變動(dòng)的代碼分離营勤,把共用率較高的資源也拆出來(lái),最大限度利用瀏覽器緩存壹罚。
  2. 減少 http 請(qǐng)求次數(shù)的同時(shí)避免單個(gè)文件太大以免拖垮響應(yīng)速度葛作,也就是拆包時(shí)盡量實(shí)現(xiàn)文件個(gè)數(shù)更少、單個(gè)文件體積更小渔嚷。

第二點(diǎn)的兩個(gè)目標(biāo)是互相矛盾的进鸠,因此要達(dá)到兩者之間的平衡是個(gè)博弈的過(guò)程,沒(méi)有太絕對(duì)的拆包策略形病,都是力求提高性能水準(zhǔn)罷了。

具體來(lái)說(shuō)霞幅,比如一些第三方插件漠吻,更新頻率其實(shí)很低,單個(gè)體積通常又較小司恳,就很適合打包在一個(gè)文件里途乃。而 UI 組件庫(kù)更新少的同時(shí)體積卻比較大,就可以單獨(dú)打成一個(gè)包(也有直接用 CDN 外鏈的)扔傅。還有程序員自己寫(xiě)的公共組件耍共,一般寫(xiě)完后修改也不多,適合拎出來(lái)放一個(gè)文件猎塞。

webpack 配置output.filenameoutput.chunkFilename值中的[contenthash]使得重新打包時(shí)若 chunk 內(nèi)容沒(méi)有變化试读,就跳過(guò)直接使用緩存,當(dāng)然對(duì)應(yīng)的輸出文件名稱(chēng)中的 hash 值也不會(huì)改變荠耽。這樣既能提高二次構(gòu)建速度钩骇,又能不影響用戶的瀏覽器緩存。
為何配置文件名時(shí)在取值占位符里使用[contenthash]而不是[chunkhash]呢?
[chunkhash]是 chunk 級(jí)別的 hash 值倘屹。但在項(xiàng)目中我們通常的做法是把 css 都抽離出來(lái)银亲,作為模塊import到對(duì)應(yīng)的 js 文件中。如果使用[chunkhash]纽匙,兩個(gè)關(guān)聯(lián)的 js 和 css 文件名的 hash 值是一樣的务蝠。一旦其中一個(gè)改動(dòng)了,與其關(guān)聯(lián)的另一個(gè)文件即使毫無(wú)變化烛缔,文件名也會(huì)改變请梢,緩存也就失效了。
[contenthash]力穗,只關(guān)注文件本身毅弧,自身內(nèi)容不變,hash 值也不會(huì)變当窗。

其余剩下的基本就是我們的業(yè)務(wù)代碼够坐,改動(dòng)頻率就很大了,是每次發(fā)布版本都會(huì)變的崖面。

常用的代碼分離方法

  • 入口起點(diǎn):通過(guò) entry 配置手動(dòng)地分離代碼元咙。
  • 防止重復(fù):使用 Entry dependencies 或者 內(nèi)置插件 SplitChunksPlugin 去重和分離 chunk。
  • 動(dòng)態(tài)導(dǎo)入:通過(guò)異步引入模塊(如import('./m.js'))來(lái)分離代碼巫员。

webpackChunkName

異步加載的 chunk 無(wú)法通過(guò) webpack 配置自定義打包后的名稱(chēng)庶香,默認(rèn)都是以0、1简识、2...這樣的數(shù)字命名赶掖。
魔法注釋可以幫助我們自定義異步 chunk 名。

component: () => import(/* webpackChunkName: "route-login" */ '@/views/login')

如果想把某個(gè)路由下的所有組件都打包在同一個(gè)異步塊 (chunk) 中七扰。那么在webpackChunkName注釋提供相同的 chunk name 即可奢赂。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

Webpack 會(huì)將任何一個(gè)異步模塊與相同的塊名稱(chēng)組合到相同的異步塊中。

SplitChunksPlugin 配置詳解

先看下SplitChunksPlugin插件的默認(rèn)配置颈走,再結(jié)合實(shí)例來(lái)搞懂每個(gè)配置項(xiàng)真正的用處膳灶。
webpack 上的文檔地址:【SplitChunksPlugin API】

module.exports = {
  // 加上入口和輸出的配置,以便結(jié)合實(shí)際說(shuō)明
  entry: {
    index: './src/a',
    admin: './src/b'
  },
  output: {
    path: __dirname + '/dist',
    filename: '[name].[contenthash:6].js',
    chunkFilename: '[name].[contenthash:8].js',
  },
  optimization: {
    splitChunks: {
      chunks: 'async', // 2. 處理的 chunk 類(lèi)型
      minSize: 20000, // 4. 允許新拆出 chunk 的最小體積
      minRemainingSize: 0,
      minChunks: 1, // 5. 拆分前被 chunk 公用的最小次數(shù)
      maxAsyncRequests: 30, // 7. 每個(gè)異步加載模塊最多能被拆分的數(shù)量
      maxInitialRequests: 30, // 6. 每個(gè)入口和它的同步依賴最多能被拆分的數(shù)量
      enforceSizeThreshold: 50000, // 8. 強(qiáng)制執(zhí)行拆分的體積閾值并忽略其他限制
      cacheGroups: { // 1. 緩存組
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/, // 1.1 模塊路徑/文件名匹配正則
          priority: -10, // 1.2 緩存組權(quán)重
          reuseExistingChunk: true, // 1.3 復(fù)用已被拆出的依賴模塊立由,而不是繼續(xù)包含在該組一起生成
        },
        default: {
          minChunks: 2, // 5. default 組的模塊必須至少被 2 個(gè) chunk 共用 (本次分割前) 
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
1. cacheGroups

核心配置 - 緩存組轧钓,可以繼承/覆蓋來(lái)自splitChunks.*的任何選項(xiàng)。它自身?yè)碛?code>test锐膜、priorityreuseExistingChunk 三個(gè)配置項(xiàng)毕箍。
SplitChunksPlugin 就是根據(jù)cacheGroups去拆分模塊的,后面2. 3. ...等其余屬性其實(shí)是應(yīng)用到每一個(gè)緩存組的公共配置枣耀,同樣的參數(shù)以緩存組的值為準(zhǔn)霉晕。

把默認(rèn)緩存組defaultVendorsdefault設(shè)置為 false庭再,即可禁用對(duì)應(yīng)緩存組規(guī)則。
模塊必須符合某個(gè)緩存組的所有條件牺堰,才會(huì)被分割拄轻。

  • 1.1 test 模塊匹配規(guī)則,可以匹配模塊資源絕對(duì)路徑(函數(shù)或正則)或 chunk 名稱(chēng)(字符串)伟葫,匹配 chunk 名稱(chēng)時(shí)(如'app')恨搓,將選擇 chunk 中的所有模塊。
    可選值:function (module, { chunkGraph, moduleGraph }) => boolean | RegExp | string
cacheGroups: {
  chunks: 'all',
  react: { // 1. 正則匹配示例筏养,把 react 和 react-dom 分到一個(gè)名為 `lib-react` 的 js 中
    // `[\\/]` 是作為跨平臺(tái)兼容性的路徑分隔符
    test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
    name: 'lib-react',
  },
  svgIcon: { // 2. 函數(shù)匹配示例斧抱,把自定義 svg 圖標(biāo)都拆出來(lái),放到 `svgIcon.js` 中
    test(module) {
      // `module.resource` 是文件的絕對(duì)路徑
      // 用`path.sep` 代替 / or \渐溶,以便跨平臺(tái)兼容
      const path = require('path'); // path 一般會(huì)在配置文件引入辉浦,此處只是說(shuō)明 path 的來(lái)源,實(shí)際并不用加上
      return ( // 匹配 icon 文件夾下的 .svg 后綴文件
        module.resource &&
        module.resource.endsWith('.svg') &&
        module.resource.includes(`${path.sep}icons${path.sep}`)
      );
    },
    name: 'svgIcon'
  },
},
  • 1.2 priority
    <number> 默認(rèn)值:-20
    緩存組打包的優(yōu)先級(jí)/權(quán)重茎辐,數(shù)值大的優(yōu)先宪郊。
    一個(gè)模塊同時(shí)滿足多個(gè)緩存組的條件,會(huì)優(yōu)先考慮權(quán)重最高的那個(gè)緩存組拖陆。
    默認(rèn)組的優(yōu)先級(jí)為負(fù)弛槐,以允許自定義組獲得更高的優(yōu)先級(jí) (自定義組的默認(rèn)值為0)。
  • 1.3 reuseExistingChunk
    <boolean> 默認(rèn)值:true
    如果當(dāng)前 chunk 包含已從主 chunk 中拆分出的模塊依啰,那么緩存組不會(huì)在新 chunk 內(nèi)生成這個(gè)/些模塊乎串,而是去復(fù)用被拆出的 module。
    這可能會(huì)影響 chunk 的結(jié)果文件名速警。
2. chunks

<string> 默認(rèn)值:"async"
選擇進(jìn)行代碼分割的 chunk 類(lèi)型叹誉,可選值:"all" | "async"(異步) | "initial"(同步)

默認(rèn)配置只會(huì)對(duì)按需加載的代碼進(jìn)行分割。那么入口文件同步依賴的第三方包和公共模塊是無(wú)法拆出來(lái)的坏瞄。因此通常會(huì)將SplitChunk整體的值設(shè)置為"all"桂对,把初始加載的代碼也加入到分割的“受眾”中來(lái)。在具體緩存組如有需要再按實(shí)際情況再覆蓋鸠匀。
如果設(shè)為"initial",那么該緩存組只會(huì)分離應(yīng)用初始加載需要的包逾柿。有時(shí)這是有必要的缀棍,因?yàn)樵O(shè)為一味設(shè)為"all"的話,打包出來(lái)的 js 都會(huì)在應(yīng)用初始載入時(shí)加載机错,即使里面包含一些首頁(yè)用不到的模塊爬范。

3. automaticNameDelimiter

<string> 默認(rèn)值:'~'
此選項(xiàng)可以指定生成名稱(chēng)中的分隔符。

默認(rèn)情況下弱匪,若未用cacheGroups.{cacheGroup}.name自定義 chunk 名青瀑, webpack 會(huì)使用 chunk 的緩存組名和entry來(lái)源生成 chunk 名(例如default~index.jsdefaultVendors~admin.js)。

4. minSize

<number> 默認(rèn)值:20000
生成 chunk 的最小體積 (以bytes為單位)

新拆出的 chunk 的體積最小值斥难,也就是符合緩存組其他條件的前提下枝嘶,體積大于等于這個(gè)值的模塊/模塊集合才會(huì)被拆分出來(lái)。
比如我們有兩個(gè)入口 chunk哑诊,各自都包含了一個(gè)模塊m(或者均有m1m2)群扶,本來(lái)符合默認(rèn)配置中的default緩存組,但由于這個(gè)模塊(或者m1加上m2)體積不足 20kb镀裤,便無(wú)法被輸出為一個(gè)文件竞阐。

?? 即使不匹配任何一個(gè)緩存組,在 splitChunks.* 的minSize選項(xiàng)會(huì)影響異步 chunk暑劝。規(guī)則是體積大于minSize值的公共模塊會(huì)被拆出骆莹。(除非 splitChunks.* chunks: 'initial',才沒(méi)有這種影響)
公共模塊即 >= 2個(gè)異步 chunk 共享的模塊担猛,同minChunks: 2幕垦。

5. minChunks

<number> 默認(rèn)值:1
拆分前必須共享模塊的最小 chunks 數(shù)

比如數(shù)值是2毁习,那么在符合某個(gè)緩存組其他規(guī)則的前提下智嚷,拆分前必須有 2 個(gè) chunk 共用了這個(gè)模塊,才可以被歸到這個(gè)組下拆分出來(lái)纺且。
不是文件共享而是 chunk 共享盏道,所以清楚 SplitChunksPlugin 處理前 chunk 的分包情況非常有必要。

6. maxInitialRequests

<number> 默認(rèn)值:30
每個(gè)入口點(diǎn)的最大并行請(qǐng)求數(shù)载碌。

也就是每個(gè)入口和它的同步依賴最多夠被拆分/合并成幾個(gè)js文件猜嘱。對(duì)這個(gè)數(shù)量進(jìn)行限制為的是避免初始js請(qǐng)求過(guò)多。

注意幾點(diǎn):

  • 入口文件本身算一個(gè)請(qǐng)求
  • 如單獨(dú)拆出了runtimeChunk嫁艇,不算在內(nèi)
  • 單獨(dú)拆出的css文件不算在內(nèi)
  • 若同時(shí)有兩個(gè)模塊滿足cacheGroup規(guī)則要進(jìn)行拆分朗伶,但maxInitialRequests只允許再拆出一個(gè)文件,那么體積較大的模塊會(huì)被拆分出來(lái)步咪。
7. maxAsyncRequests

<number> 默認(rèn)值:30
每個(gè)按需加載模塊的最大并行請(qǐng)求數(shù)论皆。

也就是一個(gè)異步加載模塊和它的同步依賴最多能被拆分成幾個(gè)js。除了處理對(duì)象不同猾漫,應(yīng)該很好理解点晴。

注意幾點(diǎn):

  • import()文件本身算一個(gè)請(qǐng)求
  • 同樣不算js以外的公共資源請(qǐng)求如css
  • 若同時(shí)有兩個(gè)模塊滿足cacheGroup規(guī)則要進(jìn)行拆分,但maxAsyncRequests只允許再拆出一個(gè)文件悯周,那么體積較大的模塊會(huì)被拆分出來(lái)粒督。
8. enforceSizeThreshold

<number> 默認(rèn)值:50000
如果符合緩存組其他條件(不包括下面三項(xiàng))的模塊/模塊集超過(guò)這個(gè)體積閾值,就忽略minRemainingSize, maxAsyncRequests, maxInitialRequests的配置禽翼,總是為這個(gè)緩存組創(chuàng)建 chunk屠橄。
也就是說(shuō)即使超出了maxAsyncRequestsmaxInitialRequests指定的可拆分次數(shù)族跛,只要緩存組模塊體積大于50kb,仍然會(huì)分出新 chunk锐墙。

干貨講完礁哄,實(shí)戰(zhàn)另開(kāi)一篇一面:【webpack SplitChunksPlugin vue-cli 4 拆包實(shí)戰(zhàn)】

參考文章:
webpack高級(jí)概念code splitting 和 splitChunks (系列五)
有點(diǎn)難的知識(shí)點(diǎn): Webpack Chunk 分包規(guī)則詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贮匕,隨后出現(xiàn)的幾起案子姐仅,更是在濱河造成了極大的恐慌,老刑警劉巖刻盐,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掏膏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡敦锌,警方通過(guò)查閱死者的電腦和手機(jī)馒疹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乙墙,“玉大人颖变,你說(shuō)我怎么就攤上這事√耄” “怎么了腥刹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)汉买。 經(jīng)常有香客問(wèn)我衔峰,道長(zhǎng),這世上最難降的妖魔是什么蛙粘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任垫卤,我火速辦了婚禮,結(jié)果婚禮上出牧,老公的妹妹穿的比我還像新娘穴肘。我一直安慰自己,他們只是感情好舔痕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布评抚。 她就那樣靜靜地躺著,像睡著了一般伯复。 火紅的嫁衣襯著肌膚如雪盈咳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天边翼,我揣著相機(jī)與錄音,去河邊找鬼鸣剪。 笑死组底,一個(gè)胖子當(dāng)著我的面吹牛丈积,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播债鸡,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼江滨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了厌均?” 一聲冷哼從身側(cè)響起唬滑,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棺弊,沒(méi)想到半個(gè)月后晶密,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡模她,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年稻艰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侈净。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尊勿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畜侦,到底是詐尸還是另有隱情元扔,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布旋膳,位于F島的核電站澎语,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏溺忧。R本人自食惡果不足惜咏连,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲁森。 院中可真熱鬧祟滴,春花似錦、人聲如沸歌溉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)痛垛。三九已至草慧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匙头,已是汗流浹背漫谷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹂析,地道東北人舔示。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓碟婆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親惕稻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子竖共,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容