研究了好几天webpack打包,我们项目是vue的高版本,已经没有了webpack.config.js文件了,是直接在vue.config.js里的chainWebpack方法直接配置,这样做法的好处是用户既可以保留webpack的默认配置,又可以通过chainWebpack设置更具针对性的参数,链式的写法正是避免了用户直接面对webpack多而复杂的配置项,想要更专业的配置再去学习webpack更深层的知识,大大降低了webpack使用难度和大多数小项目群体学习成本,一般项目只需要使用默认的配置即可满足要求,项目比较大的时候就需要深入的学习了,下面我们来说说webpack里的分块方法optimization.splitChunks以及缓存组cacheGroups的配置方法;
当我们npm run build进行打包的时候,事实上是根据cacheGroups的默认配置进来分包并包的:
splitChunks: {
chunks: "all",
minSize: 30000,//默认值,超过30K才独立分包
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/, // 匹配node_modules目录下的文件
priority: -10 // 打包优先级权重值
},
default: {
minChunks: 2,
priority: -20, // 打包优先级权重值
reuseExistingChunk: true//遇到重复包直接引用,不重新打包
}
}
}
默认配置会将 node_mudules下的模块打包进一个叫 vendors的bundle中,所有引用超过两次(minChunks:2)的模块分配到 default的bundle中,这就是我们打包完常见的xxx.bundle.js类;
但是如果node_mudules都打进一个包,通常vendor.bundle.js会很大,因此不得不抽离一部分依赖成单独块,先看一下常见的简单配置:
vue.config.js:
module.exports = {
chainWebpack( config ) {
//process.env.NODE_ENV的值要在.env.development和.env.production文件分别配置,不想判断的直接删除when方法这一行
config.when( process.env.NODE_ENV==="production", ( config )=>{
config.optimization.splitChunks({
chunks:"all",//initial同步,async异步,all同步或者异步
automaticNameDelimiter:"-",//打包文件名默认连接符号
cacheGroups: {
elementUI: {
name: "chunk-elementUI",
priority: 30,
test: /[\/]node_modules[\/]_?element-ui(.*)/,
},
nodesInitial: {
name:"chunk-nodesInitial",
test: /[\/]node_modules[\/]/,
priority: 10,
minChunks: 1,
chunks: "initial",//仅打包同步引用的依赖
reuseExistingChunk: true
},
nodesAsync: {
name:"chunk-nodesAsync",
test: /[\/]node_modules[\/]/,
priority: 9,
minChunks: 1,
chunks: "async",//仅打包异步引用的依赖
reuseExistingChunk: true
},
components: {
name: "chunk-components",
test: resolve('src/components'),
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk("single")
})
}
}
可以见到上面的配置已经将node_modules里的elementUI与公共组件components抽离成独立块,同步和异步引用的依赖分开打包,运行打包之后感觉还不错,这里要注意的是minSize的值,缓存组里独立打包的块如果小于30k,是不会打包的,所以可以针对文件的大小设置打包最小值minSize,使其成为独立块打包出来,下图是小编项目打包后webpack-bundle-analyzer插件分析出来的结果:
因为本项目文件确实比较多比较大,所以可以看到好几个超过1M的大文件,原来默认的时候app.a900c452.js是2M多的,按上面的设置了以后,大于1M的只剩3个,已经是不小的进步,但是明显还可以再抽离一部分,于是在再对webpack-bundle-analyzer插件图分析,看哪些还可以分割出来的,同步异步那两个文件再做分割:
vue.config.js:
module.exports = {
chainWebpack( config ) {
//process.env.NODE_ENV的值要在.env.development和.env.production文件分别配置,不想判断的直接删除when方法这一行
config.when( process.env.NODE_ENV==="production", ( config )=>{
config.optimization.splitChunks({
chunks:"all",//initial同步,async异步,all同步或者异步
automaticNameDelimiter:"-",
cacheGroups: {
elementUI: {
name: "chunk-elementUI",
priority: 40,
test: /[\/]node_modules[\/]_?element-ui(.*)/,
minSize:0
},
formMaking: {
name:"chunk-formMaking",
test: resolve('lib/vue-form-making/dist'),
priority: 39,
minSize:0,
enforce:true
},
antDesignVue: {
name:"chunk-antDesignVue",
test: /[\/]node_modules[\/]_?ant-design-vue(.*)/,
priority: 39,
minSize:0,
enforce:true
},
antDesign: {
name:"chunk-antDesign",
test: /[\/]node_modules[\/]_?@ant-design(.*)/,
priority: 39,
minSize:0,
enforce:true
},
moment: {
name: "chunk-moment",
priority: 39,
test: /[\/]node_modules[\/]_?moment(.*)/,
minSize:0,
enforce:true
},
jeecg: {
name:"chunk-jeecg",
test: /[\/]node_modules[\/]_?@jeecg(.*)/,
priority: 39,
minSize:0,
enforce:true
},
quill: {
name:"chunk-quill",
test: /[\/]node_modules[\/]_?quill(.*)/,
priority: 39,
minSize:0,
chunks: "async",//仅打包异步引用的依赖
},
nodesAsync: {
name:"chunk-nodesAsync",
test: /[\/]node_modules[\/]/,
priority: 10,
minChunks: 2,
chunks: "async",//仅打包异步引用的依赖
reuseExistingChunk:true
},
nodesInitial: {
name:"chunk-nodesInitial",
test: /[\/]node_modules[\/]/,
priority: 9,
minChunks: 1,
chunks: "initial",//仅打包同步引用的依赖
reuseExistingChunk:true
},
components: {
name: "chunk-components",
test: resolve('src/components'),
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
})
config.optimization.runtimeChunk("single")
config.plugin('webpack-bundle-analyzer').use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
})
}
}
打包后的结果图如下:
可以明显看到这次的分块里nodesInitial已经不见了,因为已经被分成其他的小块,但为什么这次的包总大小比上次的大300K左右呢,其实是因为抽离出来之后有部分依赖在其他模块还有引用,无法完全分割只能重复打包,这个可以根据打包情况调整代码的引用方式;
分包过程中可能发现大文件会转移,比如一开始可能是chunk-nodesInitial.js很大,增加缓存组来分割以后,发现nodesInitial文件不见了,这时候反而app.xxxxx.js变得很大,这个问题我还没想明白,只看哪个文件大,对它分包就行;
缓存组分包过程中如果发现分不出来,可以加上enforce: true
参数,有点强行拽出来的意思,小编在分块过程中也发现这个情况,比如增加缓存组antDesignVue后,打包是可以独立分离出来的,但是如果再加一个缓存组moment或jeecg,会发现,可能jeecg分出来了,但是ant-design-vue又被拽回去原来的包了,要不就是jeecg分不出来,这时候只要加了enforce: true参数,就能分出来,试过不是权重优先级priority的问题,还要多研究研究,过程中基本是看结果图,看哪个文件大,就分一个缓存组;
本次案例分块的主要逻辑是,先分同步和异步两大块,再从这两大块里找最大的继续分割,同步分出来的就都使用chunks:”initial”,异步分出来的就都是chunks:”async”,一直往下分……
那分到什么程度为止呢,小编的结论是,如果继续分下去,总包变大,那基本就可以停止了,因为总包变大意味着有依赖重复打包了,就不要尝试抽离这个包,换一个包继续尝试,如果大文件里每一个包都分不出来,那就不继续分了,到这个程度基本上分包已到了相对合理的地步,但如果分到总包变大还有大文件,可以尝试换个角度分块,一开始分的两个大包大小最好接近二等分,说明两个包之间的代码耦合度比较低,越低越利于分割,多试试;
本次的分块成果:由原来的192个文件,减少到182个文件,最大文件值由6M缩小到所有文件不超过2M,因为有些单个文件本来就是1M多的,比如formMaking,已经没法再分了,所以暂时优化至此,后续深入了解再做调整;
还有个要提醒的问题,就是使用enforce: true参数后,不知会不会引发使用该组件的页面空白,只是小编猜测,大家可以自己验证下;
本项目小编验证过暂时是没有问题的,分的包可以说非常完美,最后还使用gzip压缩,将原来140M的包,压缩到70多M,对这次的分块压缩非常满意;gzip压缩的文章在这里: