DLL是什麼,用它來幹啥?
DLL(Dynamic Link Libray)原來特指windows系統中實作共享函數庫的一種方式,擴充名通常為.dll。玩過老windows遊戲的同學應該對這種檔案不陌生,很多遊戲的安裝盤下就有很多.dll的檔案。DLL通常是已經編譯、連結的二進制檔案,友善程式直接調用。
前端應用場景
在大型項目的開發過程中,往往會用到很多公共庫,公共庫的内容不同于業務代碼,在很長的一個時間周期内都不會有改動。這部分公共庫通常會被打包在commonChunk中,webpack配置節選如下:
optimization: {
splitChunks: {
minSize: 1000000,
cacheGroups: {
vendor: {
name: "common",
test: /[\\/]node_modules[\\/]/,
chunks: "initial",
minSize: 30000,
minChunks: 1,
priority: 8
}
},
}
},
這樣可以把長時間不變的公共包内容打包進一個名為common的chunk中,同業務代碼進行隔離,生成穩定的緩存key,以便浏覽器端實作緩存。但是這也帶來一個問題:盡管使用者端實作了緩存,但是我們在打包的時候,webpack依然會對所有用到的公共包進行周遊,解析,處理。嚴重影響打包速度。
webpack中的DLL打包優化,同windows中的DLL的原理類似。通過另寫一份打包配置,把長期不變的公共包内容單獨打包,然後在業務代碼打包時,通用webpack内置插件DllReferencePlugin引用之前打包的動态連結内容,而不是每次都打包同樣的公共包内容,進而加快打包速度。
項目實戰
接下來筆者将以自己項目為例,進行webpack dll優化。
使用原來的打包配置,結果如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbwxCdh1mcvZ2LcV2Zh1Wa9M3clN2byBXLzN3btg3PZR0TpZFVZVTWUpFNBRkTppFRaJTR6xkMJpHT1gzUPhXQq1kd4cVY1VFSkBHauxUdSJTW0F1RiZHZXxUeWJzYxkTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
打包耗時:
接下來,在項目中添加webpack.dll.config.js檔案,對一些固定不變的内容提前打成dll資源:
const webpack = require('webpack')
const library = '[name]_dll'
const path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: {
vendors: ['react', 'mobx', 'mobx-react', 'antd', 'socket.io-client']
},
output: {
filename: '[name]_dll.js',
path: path.resolve(__dirname, 'dist'),
//publicPath: path.resolve(__dirname, 'dist'),
publicPath: './',
library
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'dist/[name]-manifest.json'),
// This must match the output.library option above
name: library
}),
new BundleAnalyzerPlugin({
analyzerPort: 8899
})
],
}
打包結果如下(vendors_dll.js: 685kb):
這個dll包壓縮後還有600多kb!
接下來我們再對項目代碼進行打包,在webpack配置檔案中添加如下插件:
plugins: [
......
// 動态連結庫
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/vendors-manifest.json')
}),
......
]
打包大小:
打包時間:
通過如上實驗我們可以發現,使用DllReferencePlugin插件後,打包時間大幅縮小,從原來的16s縮減到10秒左右,這是由于相當部分的資源已經提前建構好,在業務代碼改變的時候,自然就不用重複打包浪費時間了。但同時暴露出一個問題:原先的打包方式打封包件的體積大概是378kb,使用dll之後,打包體積是dll資源與業務代碼之和(685kb+92kb),整整大了兩倍。要知道,dll檔案浏覽器也是需要下載下傳的,這樣完全是無法接受的。通過對打包内容的分析,我們可以發現webpack.dll.config.js檔案的入口不是業務代碼,直接是各個公共包,這就意味着dll顯然是無法使用treeShake等等優化特性的,比如antd這個包,優化前使用treeShake按需加載,打包體積隻有168kb左右,而在dll包中,antd整個包都進行了引入,體積膨脹到了250kb左右。由此我們可以得到一個重要的實戰經驗:針對antd,lodash等等可以使用按需加載的公共庫,不能提前打包在dll中,像react,mobx等等必然會全盤引入的公共庫才是最适合放入dll中的。
接下來我們修改一下webpack.dll.config.js的entry配置(删掉antd):
entry: {
vendors: ['react', 'mobx', 'mobx-react', 'socket.io-client']
},
dll打包結果(vendors_dll:84kb):
業務代碼打包結果:
二者相加,打封包件體積與使用DllReferencePlugin前相仿。打包時間縮減了兩秒,在不增加打封包件體積的前提下,減少了打包時間。
如何将vendors_dll.js插入html?
筆者在實踐過程中還發現一個問題,打包後的vendors_dll.js并沒有被HtmlWebpackPlugin所識别,導緻輸出的HTML檔案裡沒有這個資源,解決方法如下,在html模闆檔案的body添加一個script标簽,并且附帶内容模闆:
<body>
<div id='main'></div>
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.vendor %>"></script>
</body>
然後在webpack檔案中作如下更改:
// 頭部引入打包好的檔案
const manifest = require('./dist/vendors-manifest.json');
......
plugins: [
......
new HtmlWebpackPlugin({
filename: '../dist/index.html',
template: './views/template.html',
inject: 'body',
// 添加vendor,替換html模闆中的内容
vendor: './' + manifest.name + '.js' //manifest就是dll生成的json
}),
......
]
最後在輸出的html中将會帶上我們的dll檔案。
上文中用到的例子:https://github.com/dianluyuanli-wp/chat