splitChunks精細(xì)控制代碼分割降低包大小
背景
前端小伙伴都知道,為了降低包大小,經(jīng)常會(huì)把依賴(lài)的前端模塊獨(dú)立打包,比如把 vue
、vue-router
打到一個(gè)單獨(dú)的包 vendor
中。另外,常會(huì)將存在多個(gè)路由的復(fù)雜頁(yè)面的每個(gè)頁(yè)面都單獨(dú)打一個(gè)包,只有訪問(wèn)某個(gè)頁(yè)面的時(shí)候,再去下載該頁(yè)面的js包,以此來(lái)加快首頁(yè)的渲染。
無(wú)論是 react
還是 vue
都提供了完善的工具,幫我們屏蔽了繁瑣的配置工作。當(dāng)我們對(duì)代碼進(jìn)行構(gòu)建時(shí),已經(jīng)自動(dòng)幫我們完成了代碼的拆分工作。
所以,很多小伙伴并不知道背后到底發(fā)生了什么事。至于為什么這么拆分,到底如何控制代碼的拆分,更是一頭霧水了。
問(wèn)題測(cè)驗(yàn)
講解開(kāi)始之前,大家先看一個(gè)問(wèn)題。如果你已經(jīng)知道問(wèn)題的答案,而且明白為什么,就不必往下閱讀了。如果不知道答案或者知道答案,但不知道原因。那么,強(qiáng)烈建議閱讀本文。
// webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: { app: "./src/index.js" }, output: { filename: "[name].js", path: path.resolve(__dirname, "dist") }, optimization: { splitChunks: { chunks: "all" } }, plugins: [ new HtmlWebpackPlugin() ] };
// index.js import "vue" import(/*webpackChunkName: 'a' */ "./a"); import(/*webpackChunkName: 'b' */ "./b");
// a.js import "vue-router"; import "./someModule"; // 模塊大小大于30kb
// b.js import "vuex"; import "./someModule"; // 模塊大小大于30kb
// someModule.js // 該模塊大小超過(guò)30kb // ...
代碼分割的三種方式
webpack 中以下三種常見(jiàn)的代碼分割方式:
- 入口起點(diǎn):使用
entry
配置手動(dòng)地分離代碼。 - 動(dòng)態(tài)導(dǎo)入:通過(guò)模塊的內(nèi)聯(lián)函數(shù)調(diào)用來(lái)分離代碼。
- 防止重復(fù):使用
splitChunks
去重和分離 chunk。 第一種方式,很簡(jiǎn)單,只需要在entry
里配置多個(gè)入口即可:
entry: { app: "./index.js", app1: "./index1.js" }
第二種方式,就是在代碼中自動(dòng)將使用 import()
加載的模塊分離成獨(dú)立的包:
//... import("./a"); //...
第三種方式,是使用 splitChunks
插件,配置分離規(guī)則,然后 webpack
自動(dòng)將滿(mǎn)足規(guī)則的 chunk
分離。一切都是自動(dòng)完成的。
前兩種拆分方式,很容易理解。本文主要針對(duì)第三種方式進(jìn)行討論。
splitChunks 代碼拆分
splitChunks 默認(rèn)配置
splitChunks: { // 表示選擇哪些 chunks 進(jìn)行分割,可選值有:async,initial和all chunks: "async", // 表示新分離出的chunk必須大于等于minSize,默認(rèn)為30000,約30kb。 minSize: 30000, // 表示一個(gè)模塊至少應(yīng)被minChunks個(gè)chunk所包含才能分割。默認(rèn)為1。 minChunks: 1, // 表示按需加載文件時(shí),并行請(qǐng)求的最大數(shù)目。默認(rèn)為5。 maxAsyncRequests: 5, // 表示加載入口文件時(shí),并行請(qǐng)求的最大數(shù)目。默認(rèn)為3。 maxInitialRequests: 3, // 表示拆分出的chunk的名稱(chēng)連接符。默認(rèn)為~。如chunk~vendors.js automaticNameDelimiter: '~', // 設(shè)置chunk的文件名。默認(rèn)為true。當(dāng)為true時(shí),splitChunks基于chunk和cacheGroups的key自動(dòng)命名。 name: true, // cacheGroups 下可以可以配置多個(gè)組,每個(gè)組根據(jù)test設(shè)置條件,符合test條件的模塊,就分配到該組。模塊可以被多個(gè)組引用,但最終會(huì)根據(jù)priority來(lái)決定打包到哪個(gè)組中。默認(rèn)將所有來(lái)自 node_modules目錄的模塊打包至vendors組,將兩個(gè)以上的chunk所共享的模塊打包至default組。 cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 }, // default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } }
以上配置,概括如下4個(gè)條件:
- 模塊在代碼中被復(fù)用或者來(lái)自
node_modules
文件夾 - 模塊的體積大于等于30kb(壓縮之前)
- 當(dāng)按需加載 chunks 時(shí),并行請(qǐng)求的最大數(shù)量不能超過(guò)5
- 初始頁(yè)面加載時(shí),并行請(qǐng)求的最大數(shù)量不能超過(guò)將3
// index.js import("./a"); // ...
// a.js import "vue"; // ...
以上代碼,在默認(rèn)配置下的構(gòu)建結(jié)果如下:
原因分析:
index.js
作為入口文件,屬于入口起點(diǎn)手動(dòng)配置分割代碼的情況,因此會(huì)獨(dú)立打包。(app.js)a.js
通過(guò)import()
進(jìn)行加載,屬于動(dòng)態(tài)導(dǎo)入的情況,因此會(huì)獨(dú)立打出一個(gè)包。(1.js)vue
來(lái)自node_modules
目錄,并且大于30kb;將其從a.js
拆出后,與a.js
并行加載,并行加載的請(qǐng)求數(shù)為2,未超過(guò)默認(rèn)的5;vue
拆分后,并行加載的入口文件并無(wú)增加,未超過(guò)默認(rèn)的3。vue
也符合splitChunks
的拆分條件,單獨(dú)打了一個(gè)包(2.js)
理解 chunks
chunks
用以告訴 splitChunks
的作用對(duì)象,其可選值有 async
、 initial
和 all
。默認(rèn)值是 async
,也就是默認(rèn)只選取異步加載的chunk進(jìn)行代碼拆分。這個(gè)我們?cè)陂_(kāi)頭的例子里已經(jīng)驗(yàn)證。這里我們通過(guò)兩個(gè)例子來(lái)看一下當(dāng)chunks的值為 initial
和 all
時(shí),打包結(jié)果如何。 首先將chunks值改為 initial
:
chunks: "initial"
構(gòu)建結(jié)果如下:
原因分析:
當(dāng) chunks
值為 initial
時(shí),splitChunks
的作用范圍變成了非異步加載的初始 chunk,例如我們的 index.js
就是初始化的時(shí)候就存在的chunk。而 vue 模塊是在異步加載的chunk a.js
中引入的,所以并不會(huì)被分離出來(lái)。
chunks
仍使用 initial
, 我們對(duì) index.js
和 a.js
稍作修改:
// index.js import 'vue' import('./a')
// a.js console.log('a')
構(gòu)建結(jié)果如下:
原因分析:
vue
在 index.js
直接被引入,而 index.js
是初始chunk,所以分離出來(lái)打到了 vendors~app.js
中。
能不能讓 splitChunks
既處理初始chunk也處理異步chunk呢?答案是可以,只需要將 chunks
改為 all
:
chunks: "all"
對(duì) index.js
和 a.js
稍作修改:
// index.js import 'vue-router' import('./a')
// a.js import 'vue' console.log('a')
構(gòu)建結(jié)果如下:
原因分析:
chunks
值為 all
時(shí),splitChunks
的處理范圍包括了初始chunk和異步chunk兩種場(chǎng)景,因此 index.js
中的 vue-router
被分拆到了 vendors~app.js
中,而異步加載的chunk a.js
中的 vue
被分拆到了 3.js
中。推薦在開(kāi)發(fā)中將 chunks
設(shè)置為 all
。
理解 maxInitialRequests
maxIntialRequests
表示 splitChunks
在拆分chunk后,頁(yè)面中需要請(qǐng)求的初始chunk數(shù)量不超過(guò)指定的值。所謂初始chunk,指的是頁(yè)面渲染時(shí),一開(kāi)始就需要下載的js,區(qū)別于在頁(yè)面加載完成后,通過(guò)異步加載的js。
對(duì) splitChunks
做以下修改,其他使用默認(rèn)配置:
chunks: 'initial', maxInitialRequests: 1
對(duì) index.js 稍作修改:
// index.js import 'vue'
構(gòu)建結(jié)果如下:
原因分析:
因?yàn)?maxInitialRequests
為1,如果 vue
從 index.js
中拆出的話(huà),新創(chuàng)建的chunk作為初始chunk index.js
的前置依賴(lài),是需要在頁(yè)面初始化的時(shí)候就先請(qǐng)求的。那么初始化時(shí)的請(qǐng)求數(shù)變成了2,因此不滿(mǎn)足拆分條件,所以 splitChunks
沒(méi)有對(duì) index.js
進(jìn)行拆分。
理解 maxAsyncRequests
與 maxInitialRequests
相對(duì),maxAsyncRequests
表示 splitChunks
在拆分chunk后,并行加載的異步 chunk 數(shù)不超過(guò)指定的值。
對(duì) splitChunks
做以下修改,其他使用默認(rèn)配置:
maxAsyncRequests: 1
對(duì) index.js
稍作修改:
// index.js import('./a')
// a.js import 'vue' console.log('a')
構(gòu)建結(jié)果如下:
原因分析: 因?yàn)?maxAsyncRequests
為1,由于 a.js
是通過(guò) import()
異步加載的,此時(shí)并行的異步請(qǐng)求數(shù)是1。如果將 vue
從 a.js
中拆出的話(huà),拆出的包也將成為一個(gè)異步請(qǐng)求chunk。這樣的話(huà),當(dāng)異步請(qǐng)求 a.js
的時(shí)候,并行請(qǐng)求數(shù)有2個(gè)。因此,不滿(mǎn)足拆分條件,所以 splitChunks
沒(méi)有對(duì) a.js
進(jìn)行拆分。
理解 minChunks
minChunks
表示一個(gè)模塊至少應(yīng)被指定個(gè)數(shù)的 chunk 所共享才能分割。默認(rèn)為1。
對(duì) splitChunks
做以下修改,其他使用默認(rèn)配置:
chunks: 'all', minChunks: 2
對(duì) index.js
稍作修改:
// index.js import 'vue'
構(gòu)建結(jié)果如下:
原因分析:
因?yàn)?minChunks
為 2,所以只有當(dāng) vue
至少被2個(gè) chunk 所共享時(shí),才會(huì)被拆分出來(lái)。
思考題
請(qǐng)問(wèn)如下代碼,構(gòu)建結(jié)果是什么?
chunks: 'all', minChunks: 2
// index.js import 'vue' import './a'
// a.js import 'vue' console.log('a')
理解 cache groups
cacheGroups
繼承 splitChunks
里的所有屬性的值,如 chunks
、minSize
、minChunks
、maxAsyncRequests
、maxInitialRequests
、automaticNameDelimiter
、name
,我們還可以在 cacheGroups
中重新賦值,覆蓋 splitChunks
的值。另外,還有一些屬性只能在 cacheGroups
中使用:test
、priority
、reuseExistingChunk
。
通過(guò) cacheGroups
,我們可以定義自定義 chunk 組,通過(guò) test
條件對(duì)模塊進(jìn)行過(guò)濾,符合條件的模塊分配到相同的組。
cacheGroups
有兩個(gè)默認(rèn)的組,一個(gè)是 vendors
,將所有來(lái)自 node_modules
目錄的模塊;一個(gè) default
,包含了由兩個(gè)以上的 chunk 所共享的模塊。
前面的例子中,你可能注意到了怎么有的拆分出的chunk名字這么奇怪,例如 vendors~app
(默認(rèn)由 cacheGroups
中組的 key + 源chunk名組成)。我們看一下如何自定義拆分出的chunk名。
首先找到該chunk所屬的分組,該例為 vendors
分組,作如下修改,其他使用默認(rèn)配置:
chunks:'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: "customName", priority: -10 } }
對(duì) index.js 稍作修改:
// index.js import 'vue'
構(gòu)建結(jié)果如下:
原因分析:
vue 來(lái)自 node_modules
目錄,被分配到了默認(rèn)的 vendors
組中,如果不指定 name
的話(huà),會(huì)使用默認(rèn)的chunk名,這里我們指定了 name
,因此最終的chunk名為customName
。
模塊還可以分配到多個(gè)不同的組,但最終會(huì)根據(jù) priority
優(yōu)先級(jí)決定打包到哪個(gè) chunk。
新增一個(gè)分組:
chunks:'all', cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, name: "customName", priority: -10 }, customGroup: { test: /[\\/]node_modules[\\/]/, name: "customName1", priority: 0 } }
構(gòu)建結(jié)果:
原因分析:
雖然 vendors
和 customGroup
這個(gè)兩個(gè)組的條件都符合,但由于后者的優(yōu)先級(jí)更高,所以最終將 vue
打包到了 customName1.js
中。
總結(jié)
講解到這里,想必你對(duì) webpack
如何進(jìn)行代碼分割有了深刻地理解了。對(duì)于文章開(kāi)頭的問(wèn)題,可以給出你的答案了吧?更多關(guān)于splitChunks代碼分割的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript getElementById 使用方法及用法
顧明思義,get-Element-By-Id,就是通過(guò)ID來(lái)設(shè)置/返回HTML標(biāo)簽的屬性及調(diào)用其事件與方法。用這個(gè)方法基本上可以控制頁(yè)面所有標(biāo)簽,條件很簡(jiǎn)單就是給每個(gè)標(biāo)簽分配一個(gè)ID號(hào)2008-11-11JavaScript中的值是按值傳遞還是按引用傳遞問(wèn)題探討
這篇文章主要介紹了JavaScript中的值是按值傳遞還是按引用傳遞問(wèn)題探討,本文講解了按值傳遞、按引用傳遞、按共享傳遞、基本類(lèi)型的不可變(immutable)性質(zhì)等內(nèi)容,需要的朋友可以參考下2015-01-01JS數(shù)據(jù)類(lèi)型(基本數(shù)據(jù)類(lèi)型、引用數(shù)據(jù)類(lèi)型)及堆和棧的區(qū)別分析
這篇文章主要介紹了JS數(shù)據(jù)類(lèi)型(基本數(shù)據(jù)類(lèi)型、引用數(shù)據(jù)類(lèi)型)及堆和棧的區(qū)別,結(jié)合實(shí)例形式分析了JS基本數(shù)據(jù)類(lèi)型、引用數(shù)據(jù)類(lèi)型概念、用法,以及堆和棧的區(qū)別,需要的朋友可以參考下2020-03-03JS正則截取兩個(gè)字符串之間及字符串前后內(nèi)容的方法
這篇文章主要介紹了JS正則截取兩個(gè)字符串之間及字符串前后內(nèi)容的方法,結(jié)合實(shí)例形式簡(jiǎn)單分析了JS正則截取字符串操作的常用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01JS獲取月份最后天數(shù)、最大天數(shù)與某日周數(shù)的方法
這篇文章主要介紹了JS獲取月份最后天數(shù)、最大天數(shù)與某日周數(shù)的方法,涉及JavaScript針對(duì)日期與實(shí)現(xiàn)的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12Javascript 生成無(wú)限下拉列表實(shí)現(xiàn)代碼
js生成無(wú)線下拉列表的實(shí)現(xiàn)代碼。2009-03-03小程序如何獲取多個(gè)formId實(shí)現(xiàn)詳解
這篇文章主要介紹了小程序如何獲取多個(gè)formId實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09postman自定義函數(shù)實(shí)現(xiàn) 時(shí)間函數(shù)的思路詳解
Postman是一款功能強(qiáng)大的網(wǎng)頁(yè)調(diào)試與發(fā)送網(wǎng)頁(yè)HTTP請(qǐng)求的Chrome插件。這篇文章主要給大家介紹postman自定義函數(shù)實(shí)現(xiàn) 時(shí)間函數(shù)的思路詳解,感興趣的朋友一起看看吧2019-04-04