前言
上篇從多線程打包
和縮小打包作用域
兩個方面入手,對webpack打包層面做出優化。本篇描述從動態鏈接庫思想
方面繼續深入探究打包層面的深度優化。
動態鏈接庫與DLLPlugin
動態鏈接庫(Dynamic Link Library 或者 Dynamic-link Library,縮寫爲 DLL),是微軟公司在微軟Windows操作系統中,實現共享函數庫概念的一種方式。這些庫函數的擴展名是 ”.dll"、".ocx"(包含ActiveX控制的庫)或者 ".drv"(舊式的系統驅動程序)。
當一段相同的子程序被多個程序調用時,爲了減少內存消耗,可以將這段子程序存儲爲一個可執行文件,當被多個程序調用時只在內存中生成和使用同一個實例。
今天要介紹的主角“DLLPlugin”則借鑑了動態鏈接庫的思路,對於第三方模塊或者一些不常變化的模塊預先進行編譯和打包,然後再項目實際構建過程中直接取用。不過區別還是有的,DLLPlugin實際生成的文件是JS文件而不是動態鏈接庫。在打包vendor的時候還會附加生成一份vendor的模塊清單,這份清單將會在工程業務模塊打包時起到鏈接和索引的作用。
1 vendor配置
首先需要爲動態鏈接庫單獨創建一個Webpack配置文件,例如:webpack.vendor.config.js,注意要與webpack.config.js區分開來。
例:
// 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: 導出的dll library的名字,需要與output.library的值對應;
- path: 資源清單的絕對路徑,業務打包時將會使用這個清單進行模塊索引;
2 vendor打包
接下來就要打包vendor並且生成資源清單。爲後續方便操作,可以在package.json中配置一條運行指令:
// pachage.json
{
...
"scripts": {
...
"dll": "webpack --config webpack.vendor.config.js"
}
}
然後執行npm run dll
,會發現生成了一個dll目錄,裏面對應有兩個文件:
- vendor.js: 庫的代碼
- manifest.json: 資源清單
感興趣的可以打開這兩個文件閱讀一下。
3 鏈接到業務代碼
試過之後,我們就要考慮將vendor鏈接到項目中去了。這裏推薦與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>
設置完畢後,當頁面執行到vendor.js時,會聲明全局變量dllExample,而manifest相當於注入app.js的資源地圖,app.js會通過name字段找到名爲DLLExample的library,再進一步獲取其內部模塊。
4 潛在問題
細心的小夥伴或許已經發現了,在當前配置中會存在一個問題:當打開manifest.json文件後,可以發現每個模塊都會有一個id,其值是按照數字順序遞增的,而業務代碼在引用vendor中模塊時也是引用這個數字id,當我們更vendor時這個數字id也會隨之發生改變。
現假設我們工程目錄中有如下資源文件,並每個資源都加上了chunk hash:
- vendor@[hasn].js
- pageUser@[hasn].js
- pageIndex@[hasn].js
- util@[hasn].js
現在vendor中you一些模塊,例如包含了react,其id爲5.當嘗試添加更多模塊到vendor中的時候,那麼重新進行Dll構建時,moment.js可能出現在react之前,此時react的id會變爲6.而pageUser和pageIndex是通過id進行引用的,因此他們的文件內容也發生了改變。此時我們會面臨如下情況:
- 兩個頁面的chunk hash均發生了改變。這是我們不希望看到的,因爲他們本身並無變化,但是vendor的改變卻驅使用戶不得不重新下載所有資源。
- 兩個頁面chunk hash沒有改變,但是這種情況更爲糟糕:vendor中的模塊id改變了,但是用戶沒有更新緩存,使用的還是舊版本的內容,而引用不到新的vendor模塊,導致頁面發生錯誤。並且對於開發者而言,這個錯誤卻難以排查,因爲開發環境下一切正常!
針對上述的問題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中被引入進來的,主要就是爲了解決數字id的問題。HashedModuleIdsPlugin可以把id的生成算法修改爲根據模塊的引用路徑生成一個字符串hash。
注:從webpack3開始,模塊id不僅可以是數字,也可以是字符串。
小結
本篇從動態鏈接庫思想着手,介紹了DLLPlugin與其配套插件DLLReferencePlugin使用,將第三方庫與一些不常改動的模塊編譯打包,處理爲類似於動態鏈接庫的JS文件,以此來節約服務器資源。
下一篇介紹打包優化最後一個環節:死代碼檢測與去除。