前言
上篇從多線程打包
和縮小打包作用域
兩個方面入手,對webpack打包層面做出優(yōu)化。本篇描述從動態(tài)鏈接庫思想
方面繼續(xù)深入探究打包層面的深度優(yōu)化德迹。
動態(tài)鏈接庫與DLLPlugin
動態(tài)鏈接庫(Dynamic Link Library 或者 Dynamic-link Library雀监,縮寫為 DLL),是微軟公司在微軟Windows操作系統(tǒng)中在塔,實(shí)現(xiàn)共享函數(shù)庫概念的一種方式幻件。這些庫函數(shù)的擴(kuò)展名是 ”.dll"、".ocx"(包含ActiveX控制的庫)或者 ".drv"(舊式的系統(tǒng)驅(qū)動程序)蛔溃。
當(dāng)一段相同的子程序被多個程序調(diào)用時绰沥,為了減少內(nèi)存消耗,可以將這段子程序存儲為一個可執(zhí)行文件贺待,當(dāng)被多個程序調(diào)用時只在內(nèi)存中生成和使用同一個實(shí)例徽曲。
今天要介紹的主角“DLLPlugin”則借鑒了動態(tài)鏈接庫的思路,對于第三方模塊或者一些不常變化的模塊預(yù)先進(jìn)行編譯和打包麸塞,然后再項(xiàng)目實(shí)際構(gòu)建過程中直接取用秃臣。不過區(qū)別還是有的,DLLPlugin實(shí)際生成的文件是JS文件而不是動態(tài)鏈接庫。在打包vendor的時候還會附加生成一份vendor的模塊清單奥此,這份清單將會在工程業(yè)務(wù)模塊打包時起到鏈接和索引的作用弧哎。
1 vendor配置
首先需要為動態(tài)鏈接庫單獨(dú)創(chuàng)建一個Webpack配置文件,例如:webpack.vendor.config.js稚虎,注意要與webpack.config.js區(qū)分開來撤嫩。
例:
// webpack.vendor.config.js
const path = require('path');
const webpack = require('webpack');
const dllAssetPath = path.join(__dirname, 'dll');
const dllLibraryName = 'dllExample';
module.exports = {
entry: ['react'],
output: {
path: dllAssetPath,
filename: 'vendor.js',
library: dllLibraryName
},
plugins: [
new webpack.DllPlugin({
name: dllLibraryName,
path: path.join(dllAssetPath, 'manifest.json')
})
]
}
其中,entry指定了將哪些模塊打包為vendor蠢终,plugins的部分引入了DLLPlugin序攘,并有如下配置:
- name: 導(dǎo)出的dll library的名字,需要與output.library的值對應(yīng)蜕径;
- path: 資源清單的絕對路徑两踏,業(yè)務(wù)打包時將會使用這個清單進(jìn)行模塊索引;
2 vendor打包
接下來就要打包vendor并且生成資源清單兜喻。為后續(xù)方便操作梦染,可以在package.json中配置一條運(yùn)行指令:
// pachage.json
{
...
"scripts": {
...
"dll": "webpack --config webpack.vendor.config.js"
}
}
然后執(zhí)行npm run dll
,會發(fā)現(xiàn)生成了一個dll目錄朴皆,里面對應(yīng)有兩個文件:
- vendor.js: 庫的代碼
- manifest.json: 資源清單
感興趣的可以打開這兩個文件閱讀一下帕识。
3 鏈接到業(yè)務(wù)代碼
試過之后,我們就要考慮將vendor鏈接到項(xiàng)目中去了遂铡。這里推薦與DLLPlugin配套的插件“DLLReferencePlugin”肮疗,它起到索引和鏈接作用。在工程的webpack配置文件中(注意是webpack.config.js扒接,不是vendor的配置文件)伪货,通過DLLReferencePlugin來獲取剛才打包好的資源清單,然后在頁面中添加vendor.js就可以引用钾怔。
// webpack.cinfig.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
...
plugins: [
new webpack.DllReferencePlugin({
manifest: require(path.join(__dirname, 'dll/manifest.json')
})
]
}
那么 index.html 引入會即可:
...
<body>
...
<script src="dll/vendor.js"></script>
<script src="dist/app.js"></script>
</body>
設(shè)置完畢后碱呼,當(dāng)頁面執(zhí)行到vendor.js時,會聲明全局變量dllExample宗侦,而manifest相當(dāng)于注入app.js的資源地圖愚臀,app.js會通過name字段找到名為DLLExample的library,再進(jìn)一步獲取其內(nèi)部模塊矾利。
4 潛在問題
細(xì)心的小伙伴或許已經(jīng)發(fā)現(xiàn)了姑裂,在當(dāng)前配置中會存在一個問題:當(dāng)打開manifest.json文件后,可以發(fā)現(xiàn)每個模塊都會有一個id男旗,其值是按照數(shù)字順序遞增的舶斧,而業(yè)務(wù)代碼在引用vendor中模塊時也是引用這個數(shù)字id,當(dāng)我們更vendor時這個數(shù)字id也會隨之發(fā)生改變剑肯。
現(xiàn)假設(shè)我們工程目錄中有如下資源文件捧毛,并每個資源都加上了chunk hash:
- vendor@[hasn].js
- pageUser@[hasn].js
- pageIndex@[hasn].js
- util@[hasn].js
現(xiàn)在vendor中you一些模塊,例如包含了react让网,其id為5.當(dāng)嘗試添加更多模塊到vendor中的時候呀忧,那么重新進(jìn)行Dll構(gòu)建時,moment.js可能出現(xiàn)在react之前溃睹,此時react的id會變?yōu)?.而pageUser和pageIndex是通過id進(jìn)行引用的而账,因此他們的文件內(nèi)容也發(fā)生了改變。此時我們會面臨如下情況:
- 兩個頁面的chunk hash均發(fā)生了改變因篇。這是我們不希望看到的泞辐,因?yàn)樗麄儽旧聿o變化,但是vendor的改變卻驅(qū)使用戶不得不重新下載所有資源竞滓。
- 兩個頁面chunk hash沒有改變咐吼,但是這種情況更為糟糕:vendor中的模塊id改變了,但是用戶沒有更新緩存商佑,使用的還是舊版本的內(nèi)容锯茄,而引用不到新的vendor模塊,導(dǎo)致頁面發(fā)生錯誤茶没。并且對于開發(fā)者而言肌幽,這個錯誤卻難以排查,因?yàn)殚_發(fā)環(huán)境下一切正常抓半!
針對上述的問題2喂急,解決方法是在打包vendor時添加上HashedModuleIdsPlugin,如下:
// webpack.vendor.config.js
module.exports = {
...
plugins: [
new webpack.DllPlugin({
name: dllLibraryName,
path: path.join(dllAssetPath, 'manifest.json')
}),
// 添加HashedModuleIdsPlugin
new webpack.HashedModuleIdsPlugin();
]
}
HashedModuleIdsPlugin是webpack3中被引入進(jìn)來的笛求,主要就是為了解決數(shù)字id的問題廊移。HashedModuleIdsPlugin可以把id的生成算法修改為根據(jù)模塊的引用路徑生成一個字符串hash。
注:從webpack3開始探入,模塊id不僅可以是數(shù)字狡孔,也可以是字符串。
小結(jié)
本篇從動態(tài)鏈接庫思想著手新症,介紹了DLLPlugin與其配套插件DLLReferencePlugin使用步氏,將第三方庫與一些不常改動的模塊編譯打包,處理為類似于動態(tài)鏈接庫的JS文件徒爹,以此來節(jié)約服務(wù)器資源荚醒。
下一篇介紹打包優(yōu)化最后一個環(huán)節(jié):死代碼檢測與去除。