webpack dll打包重複問題優化

Jooger發表於2019-03-01

關於webpack dll的使用,我這裡不做過多介紹,網上都有,一擼一大把,今天我要說的是在使用dll plugin過程中出現的一個包依賴問題,這個問題導致打出來的包會包含重複的程式碼。

優化背景

最近在給公司專案優化的時候,由於內部CDN上傳檔案大小限制了500K,所以用了webpack dll來進行拆分打包,我將拆分的包分為三部分:

  • vue生態包(vuevuexvue-routervuex-classvue-class-component等周邊生態的庫)
  • vue外掛包(vee-validate、內部UI庫,圖片預覽等vue外掛庫)
  • 第三方包(axios、內部一些錯誤統計、上報,員工水印等這些脫離於vue的第三方庫)

三部分的包名分別是vue.dll.jsplugin.dll.jslib.dll.js,這樣的好處是結構清晰,最重要的原因還是分解包的大小,降低到500K以內

但是在進行dll打包後,我驚奇地發現vue.dll.jsplugin.dll.js中會包含重複的vue的dist程式碼

下面是分別是前兩部分的bundle分析圖

vue.dll.js

plugin.dll.js

可以看到這倆dll都包含了vue

那麼要分析問題原因,先說一下我的DLL的配置吧

DLL配置

因為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.配置
        })
    ]
}

複製程式碼

但是親測這樣打包出來的檔案依然有上述問題

所以結合我在之前公司所實踐的webpack multi compiler方式,參考webpack multi compiler,我把webpack的配置一分為三,每一個dll包都有一個webpack配置,即

// 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()))
}

複製程式碼

整體變成了如下結構

dll structure

最關鍵的一步就是plugin.dl.js會引用vue.dll.js的manifest檔案,這樣公共部分vue,就只會出現在vue.dll.js中了,plugin.dll.js打包後的bundle分析圖如下

plugin.dll.js

可以很明顯地看到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進行打包

原文地址:webpack dll打包重複問題優化

相關文章