您的当前位置:首页正文

webpack dll打包重复问题优化的解决

2020-11-27 来源:星星旅游

关于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分析图

    可以看到这俩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()))
    }

    整体变成了如下结构

    最关键的一步就是plugin.dl.js会引用vue.dll.js的manifest文件,这样公共部分vue,就只会出现在vue.dll.js中了,plugin.dll.js打包后的bundle分析图如下

    可以很明显地看到plugin.dll.js中已经没有vue dist的身影了,包的体积得到了优化:v:

    可优化项

    上述优化其实只考虑了一个依赖项,那么如果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进行打包