splitChunks精細(xì)控制代碼分割降低包大小
背景
前端小伙伴都知道,為了降低包大小,經(jīng)常會把依賴的前端模塊獨(dú)立打包,比如把 vue、vue-router 打到一個(gè)單獨(dú)的包 vendor 中。另外,常會將存在多個(gè)路由的復(fù)雜頁面的每個(gè)頁面都單獨(dú)打一個(gè)包,只有訪問某個(gè)頁面的時(shí)候,再去下載該頁面的js包,以此來加快首頁的渲染。
無論是 react 還是 vue 都提供了完善的工具,幫我們屏蔽了繁瑣的配置工作。當(dāng)我們對代碼進(jìn)行構(gòu)建時(shí),已經(jīng)自動幫我們完成了代碼的拆分工作。
所以,很多小伙伴并不知道背后到底發(fā)生了什么事。至于為什么這么拆分,到底如何控制代碼的拆分,更是一頭霧水了。
問題測驗(yàn)
講解開始之前,大家先看一個(gè)問題。如果你已經(jīng)知道問題的答案,而且明白為什么,就不必往下閱讀了。如果不知道答案或者知道答案,但不知道原因。那么,強(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 // 該模塊大小超過30kb // ...
代碼分割的三種方式
webpack 中以下三種常見的代碼分割方式:
- 入口起點(diǎn):使用
entry配置手動地分離代碼。 - 動態(tài)導(dǎo)入:通過模塊的內(nèi)聯(lián)函數(shù)調(diào)用來分離代碼。
- 防止重復(fù):使用
splitChunks去重和分離 chunk。 第一種方式,很簡單,只需要在entry里配置多個(gè)入口即可:
entry: { app: "./index.js", app1: "./index1.js" }
第二種方式,就是在代碼中自動將使用 import() 加載的模塊分離成獨(dú)立的包:
//...
import("./a");
//...
第三種方式,是使用 splitChunks 插件,配置分離規(guī)則,然后 webpack 自動將滿足規(guī)則的 chunk 分離。一切都是自動完成的。
前兩種拆分方式,很容易理解。本文主要針對第三種方式進(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í),并行請求的最大數(shù)目。默認(rèn)為5。
maxAsyncRequests: 5,
// 表示加載入口文件時(shí),并行請求的最大數(shù)目。默認(rèn)為3。
maxInitialRequests: 3,
// 表示拆分出的chunk的名稱連接符。默認(rèn)為~。如chunk~vendors.js
automaticNameDelimiter: '~',
// 設(shè)置chunk的文件名。默認(rèn)為true。當(dāng)為true時(shí),splitChunks基于chunk和cacheGroups的key自動命名。
name: true,
// cacheGroups 下可以可以配置多個(gè)組,每個(gè)組根據(jù)test設(shè)置條件,符合test條件的模塊,就分配到該組。模塊可以被多個(gè)組引用,但最終會根據(jù)priority來決定打包到哪個(gè)組中。默認(rèn)將所有來自 node_modules目錄的模塊打包至vendors組,將兩個(gè)以上的chunk所共享的模塊打包至default組。
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
//
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
以上配置,概括如下4個(gè)條件:
- 模塊在代碼中被復(fù)用或者來自
node_modules文件夾 - 模塊的體積大于等于30kb(壓縮之前)
- 當(dāng)按需加載 chunks 時(shí),并行請求的最大數(shù)量不能超過5
- 初始頁面加載時(shí),并行請求的最大數(shù)量不能超過將3
// index.js
import("./a");
// ...
// a.js import "vue"; // ...
以上代碼,在默認(rèn)配置下的構(gòu)建結(jié)果如下:

原因分析:
index.js作為入口文件,屬于入口起點(diǎn)手動配置分割代碼的情況,因此會獨(dú)立打包。(app.js)a.js通過import()進(jìn)行加載,屬于動態(tài)導(dǎo)入的情況,因此會獨(dú)立打出一個(gè)包。(1.js)vue來自node_modules目錄,并且大于30kb;將其從a.js拆出后,與a.js并行加載,并行加載的請求數(shù)為2,未超過默認(rèn)的5;vue拆分后,并行加載的入口文件并無增加,未超過默認(rèn)的3。vue也符合splitChunks的拆分條件,單獨(dú)打了一個(gè)包(2.js)
理解 chunks
chunks 用以告訴 splitChunks 的作用對象,其可選值有 async、 initial 和 all。默認(rèn)值是 async,也就是默認(rèn)只選取異步加載的chunk進(jìn)行代碼拆分。這個(gè)我們在開頭的例子里已經(jīng)驗(yàn)證。這里我們通過兩個(gè)例子來看一下當(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 中引入的,所以并不會被分離出來。
chunks 仍使用 initial, 我們對 index.js 和 a.js 稍作修改:
// index.js
import 'vue'
import('./a')
// a.js
console.log('a')
構(gòu)建結(jié)果如下:

原因分析:
vue 在 index.js 直接被引入,而 index.js 是初始chunk,所以分離出來打到了 vendors~app.js 中。
能不能讓 splitChunks 既處理初始chunk也處理異步chunk呢?答案是可以,只需要將 chunks 改為 all :
chunks: "all"
對 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兩種場景,因此 index.js 中的 vue-router 被分拆到了 vendors~app.js 中,而異步加載的chunk a.js 中的 vue 被分拆到了 3.js 中。推薦在開發(fā)中將 chunks 設(shè)置為 all。
理解 maxInitialRequests
maxIntialRequests 表示 splitChunks 在拆分chunk后,頁面中需要請求的初始chunk數(shù)量不超過指定的值。所謂初始chunk,指的是頁面渲染時(shí),一開始就需要下載的js,區(qū)別于在頁面加載完成后,通過異步加載的js。
對 splitChunks 做以下修改,其他使用默認(rèn)配置:
chunks: 'initial', maxInitialRequests: 1
對 index.js 稍作修改:
// index.js import 'vue'
構(gòu)建結(jié)果如下:

原因分析:
因?yàn)?maxInitialRequests 為1,如果 vue 從 index.js 中拆出的話,新創(chuàng)建的chunk作為初始chunk index.js 的前置依賴,是需要在頁面初始化的時(shí)候就先請求的。那么初始化時(shí)的請求數(shù)變成了2,因此不滿足拆分條件,所以 splitChunks 沒有對 index.js 進(jìn)行拆分。
理解 maxAsyncRequests
與 maxInitialRequests 相對,maxAsyncRequests 表示 splitChunks 在拆分chunk后,并行加載的異步 chunk 數(shù)不超過指定的值。
對 splitChunks 做以下修改,其他使用默認(rèn)配置:
maxAsyncRequests: 1
對 index.js 稍作修改:
// index.js
import('./a')
// a.js
import 'vue'
console.log('a')
構(gòu)建結(jié)果如下:

原因分析: 因?yàn)?maxAsyncRequests 為1,由于 a.js 是通過 import() 異步加載的,此時(shí)并行的異步請求數(shù)是1。如果將 vue 從 a.js 中拆出的話,拆出的包也將成為一個(gè)異步請求chunk。這樣的話,當(dāng)異步請求 a.js 的時(shí)候,并行請求數(shù)有2個(gè)。因此,不滿足拆分條件,所以 splitChunks 沒有對 a.js 進(jìn)行拆分。
理解 minChunks
minChunks 表示一個(gè)模塊至少應(yīng)被指定個(gè)數(shù)的 chunk 所共享才能分割。默認(rèn)為1。
對 splitChunks 做以下修改,其他使用默認(rèn)配置:
chunks: 'all', minChunks: 2
對 index.js 稍作修改:
// index.js import 'vue'
構(gòu)建結(jié)果如下:

原因分析:
因?yàn)?minChunks 為 2,所以只有當(dāng) vue 至少被2個(gè) chunk 所共享時(shí),才會被拆分出來。
思考題
請問如下代碼,構(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。
通過 cacheGroups,我們可以定義自定義 chunk 組,通過 test 條件對模塊進(jìn)行過濾,符合條件的模塊分配到相同的組。
cacheGroups 有兩個(gè)默認(rèn)的組,一個(gè)是 vendors,將所有來自 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
}
}
對 index.js 稍作修改:
// index.js import 'vue'
構(gòu)建結(jié)果如下:

原因分析:
vue 來自 node_modules 目錄,被分配到了默認(rèn)的 vendors 組中,如果不指定 name 的話,會使用默認(rèn)的chunk名,這里我們指定了 name,因此最終的chunk名為customName。
模塊還可以分配到多個(gè)不同的組,但最終會根據(jù) priority 優(yōu)先級決定打包到哪個(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)先級更高,所以最終將 vue 打包到了 customName1.js 中。
總結(jié)
講解到這里,想必你對 webpack 如何進(jìn)行代碼分割有了深刻地理解了。對于文章開頭的問題,可以給出你的答案了吧?更多關(guān)于splitChunks代碼分割的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于在Typescript中做錯(cuò)誤處理的方式詳解
錯(cuò)誤處理是軟件工程重要的一部分,如果處理得當(dāng),它可以為你節(jié)省數(shù)小時(shí)的調(diào)試和故障排除時(shí)間,我發(fā)現(xiàn)了與錯(cuò)誤處理相關(guān)的三大疑難雜癥:TypeScript的錯(cuò)誤類型,變量范圍和嵌套,讓我們逐一深入了解它們帶來的撓頭問題,感興趣的朋友可以參考下2023-09-09
JavaScript中將值轉(zhuǎn)換為字符串的五種方法總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于JavaScript中將值轉(zhuǎn)換為字符串的五種方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用JavaScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
JavaScript識別網(wǎng)頁關(guān)鍵字并進(jìn)行描紅的方法
這篇文章主要介紹了JavaScript識別網(wǎng)頁關(guān)鍵字并進(jìn)行描紅的方法,通過字符串的遍歷、匹配及動態(tài)添加等操作實(shí)現(xiàn)識別與描紅的功能,非常簡單實(shí)用,需要的朋友可以參考下2015-11-11
layui數(shù)據(jù)表格 table.render 報(bào)錯(cuò)的解決方法
今天小編就為大家分享一篇layui數(shù)據(jù)表格 table.render 報(bào)錯(cuò)的解決方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
Windows Live的@live.com域名注冊漏洞 利用代碼
Windows Live的@live.com域名注冊漏洞 利用代碼...2006-12-12
javaScript實(shí)現(xiàn)浮點(diǎn)數(shù)轉(zhuǎn)十六進(jìn)制字符
浮點(diǎn)數(shù)轉(zhuǎn)十六進(jìn)制的方法有很多,在本文將為大家詳細(xì)介紹下js中時(shí)如何實(shí)現(xiàn)的,下面有個(gè)不錯(cuò)的示例,感興趣的朋友可以參考下,希望對大家有所幫助2013-10-10
JS實(shí)現(xiàn)側(cè)懸浮浮動實(shí)例代碼
這篇文章主要介紹了JS實(shí)現(xiàn)側(cè)懸浮浮動實(shí)例代碼,有需要的朋友可以參考一下2013-11-11
基于JavaScript實(shí)現(xiàn)無限加載瀑布流
這篇文章主要為大家詳細(xì)介紹了基于JavaScript實(shí)現(xiàn)無限加載瀑布流,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
IE與FF下javascript獲取網(wǎng)頁及窗口大小的區(qū)別詳解
本篇文章主要是對IE與FF下javascript獲取網(wǎng)頁及窗口大小的區(qū)別進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-01-01

