关于 webpack dll 的使用, 我这里不做过多介绍, 网上都有, 一撸一大把, 今天我要说的是在使用 dll plugin 过程中出现的一个包依赖问题, 这个问题导致打出来的包会包含重复的代码.
优化背景
最近在给公司项目优化的时候, 由于内部 CDN 上传文件大小限制了 500K, 所以用了 webpack dll 来进行拆分打包, 我将拆分的包分为三部分:
vue 生态包 (vue,vuex,vue-router,vuex-class,
vue-class-component
等周边生态的库)
vue 插件包 (vee-validate, 内部 UI 库, 图片预览等 vue 插件库)
第三方包 (axios, 内部一些错误统计, 上报, 员工水印等这些脱离于 vue 的第三方库)
三部分的包名分别是 vue.dll.JS,plugin.dll.JS,lib.dll.JS, 这样的好处是结构清晰, 最重要的原因还是分解包的大小, 降低到 500K 以内
但是在进行 dll 打包后, 我惊奇地发现 vue.dll.JS 和 plugin.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 的身影了, 包的体积得到了优化
可优化项
上述优化其实只考虑了一个依赖项, 那么如果 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 进行打包
来源: https://juejin.im/post/5bba1f496fb9a05d1c14a653