關於webpack dll的使用,我這裏不作過多介紹,網上都有,一擼一大把,今天我要說的是在使用dll plugin過程當中出現的一個包依賴問題,這個問題致使打出來的包會包含重複的代碼。vue
最近在給公司項目優化的時候,因爲內部CDN上傳文件大小限制了500K,因此用了webpack dll來進行拆分打包,我將拆分的包分爲三部分:webpack
vue
、vuex
、vue-router
、vuex-class
、vue-class-component
等周邊生態的庫)vee-validate
、內部UI庫,圖片預覽等vue插件庫)axios
、內部一些錯誤統計、上報,員工水印等這些脫離於vue的第三方庫)三部分的包名分別是vue.dll.js
、plugin.dll.js
、lib.dll.js
,這樣的好處是結構清晰,最重要的緣由仍是分解包的大小,下降到500K之內ios
可是在進行dll打包後,我驚奇地發現vue.dll.js
和plugin.dll.js
中會包含重複的vue的dist代碼web
下面是分別是前兩部分的bundle分析圖vue-router
能夠看到這倆dll都包含了vuevuex
那麼要分析問題緣由,先說一下個人DLL的配置吧axios
由於webpack支持多entry,因此通常多入口dll打包的話,首先會考慮一個webpack配置,多個entry入口,因此可能會出現優化
// webpack.dll.conf.js module.exports = { // 其餘配置先省略 entry: { vue: ['vue', 'vuex', 'vue-router', ...], plugin: ['vee-validate', '內部UI庫', ...], lib: ['axios', 'dayjs', ...] }, plugins: [ new webpack.DllPlugin({ // dll.配置 }) ] }
可是親測這樣打包出來的文件依然有上述問題ui
因此結合我在以前公司所實踐的webpack multi compiler方式,參考webpack multi compiler,我把webpack的配置一分爲三,每個dll包都有一個webpack配置,即spa
// config.js exports.dll = [ { name: 'vue', libs: ['vue', 'vuex', 'vue-router', 'vuex-class', 'vue-class-component'] }, { name: 'lib', libs: [axios', 'dayjs', '第三方庫'] }, { name: 'plugin', libs: ['vee-validate', 'v-viewer', 'vue插件庫'] } ]
// webpack.dll.conf.js module.exports = config.dll.map(function (vendor) { return { // 省略其餘配置 entry: { [vendor.name]: vendor.libs }, plugins: [ new webpack.DllPlugin({ // dll.配置 }) ] } })
// dll.js const dllConfig = require('./webpack.dll.conf') webpack(dllConfig, function (err, stats) { if (err) throw err // 處理stats相關信息 })
本覺得這樣能夠解決問題,可是現實倒是不能,因此得先分析一下問題所在
通過仔細的排查,發現是因爲內部UI庫中單獨引用了vue,即在庫中有
import Vue from 'vue' // ... // Vue相關操做 // Vue.prototype.$isServer等
這樣不論是多入口打包仍是multi compiler方式下都會出現重複的包
分析dll的原理,其實dll在打包的時候會將全部包含的庫作一個索引,寫在一個manifest文件中,而後在引用dll的時候只須要引用這個manifest文件便可
因此我就在想,若是plugin.dll.js依賴於vue.dll.js中的vue,那麼是否能夠先打包vue.dll.js,而後在打包plugin.dll.js的時候引用vue.dll.js呢?
心動不如行動,趕忙嘗試一下,作出以下修改
// config.js exports.dll = [ { name: 'vue', libs: ['vue', 'vuex', 'vue-router', 'vuex-class', 'vue-class-component'] }, { name: 'lib', libs: [axios', 'dayjs', '第三方庫'] }, { name: 'plugin', libs: ['vee-validate', 'v-viewer', 'vue插件庫'], ref: 'vue' } ]
// webpack.dll.conf.js // generate config const gen = function (vendors) { return vendors.map(function (item) { const base = { entry: { [item.name]: item.libs }, plugins: [ new webpack.DllPlugin({ // dll配置 }) ] } if (item.ref) { // 重點在這 // 在有ref的dll配置中,插入dll reference的plugin,內容是所依賴的dll包的manifest base.plugins.push(new webpack.DllReferencePlugin({ // dll reference其餘配置 manifest: '所依賴的dll包的manifest文件路徑' })) } return base }) } // 根據是否有ref依賴項,區分base config和ref config const [baseVendors, refVendors] = config.dll.vendors.reduce((config, v) => { config[v.ref ? 1 : 0].push(v) return config }, [ [], [] ]) // 生成base config const getConfig = function () { return gen(baseVendors) } // 生成ref config const getRefConfig = function () { return gen(refVendors) } module.exports = { getConfig, getRefConfig }
// dll.js const dllConfig = require('./webpack.dll.conf') // 由於ref config依賴於base config,因此要保證base config先打包出來 const runWebpack = function (config) { return new Promise(function (resolve) { webpack(config, function (err, stats) { if (err) throw err // ... resolve() }) }) } module.exports = function run () { runWebpack(dllConfig.getConfig()) .then(() => runWebpack(dllConfig.getRefConfig())) }
總體變成了以下結構
最關鍵的一步就是plugin.dl.js會引用vue.dll.js的manifest文件,這樣公共部分vue,就只會出如今vue.dll.js中了,plugin.dll.js打包後的bundle分析圖以下
能夠很明顯地看到plugin.dll.js中已經沒有vue dist的身影了,包的體積獲得了優化✌️
上述優化其實只考慮了一個依賴項,那麼若是plugin.dll.js同時依賴於vue.dll.js和lib.dll.js呢?若是此時vue.dll.js也依賴於lib.dll.js呢?
若是出現上述狀況,那麼請先考慮dll包是否須要拆分?拆分是否合理?
而後再思考如何根據依賴順序思考打包順序,以及若是出現循環依賴,該怎麼辦?
因爲目前優化需求中還未出現這種狀況(這種狀況應該不多不多不多見),因此我這邊就沒有解決這些問題了
參考日常打包經過dll reference plugin來引用dll包的manifest的方式,若是多個dll包內出現了依賴,致使打包重複,那麼是能夠在依賴包中運用dll reference plugin來引用被依賴包的dll manifest,不過這樣的話,須要注意dll包的打包順序,被依賴包的dll要先於依賴包dll進行打包