打包时会遇到的问题:
- 打包文件过大,首屏加载时间过长,js阻塞页面渲染导致白屏
- 改动业务代码后,对于第三方库也会一并重新打包到一个出口文件,浏览器无法利用缓存来减少请求和加载的时间
针对以上两个问题,利用splitChunks可以将各个模块进行分割,并提取出相同部分代码,从而缓解一个js过大、不能缓存第三方库的问题。
webpack 从 4.x 开始支持了零配置的特性,同时对块打包也做了优化,CommonsChunkPlugin
已经被移除了,现在是使用optimization.splitChunks
代替。(webpack5 splitChunks 配置和 webpack4 大体上一样)
概念区分 - module、bundle、chunk都是什么?
在说SplitChunks前,先来了解几个概念,module、bundle、chunk都是什么?
module:模块,在webpack中任何文件都可以作为一个模块,借用官网的图片,左侧的这些类型文件,都可以认为是一个模块,只是需要配置不同的loader,将文件转换成webpack可以支持打包的文件。
chunk:编译完成准备输出时,webpack 将module 按特定规则组成的一个个 chunk
bundle:webpack 处理好chunk
文件后,生成运行在浏览器中的代码
SplitChunks 插件是什么呢
简单的来说就是 Webpack 中一个提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,拆分过大的js文件,合并零散的js文件。
提到前端优化,提取公共代码是必不可少的手段。
在 Webpack 出现前,提取公共代码是人为去处理,而 SplitChunks 插件的作用就是通过配置让 Webpack 去帮你提取公共代码。SplitChunksPlugin
默认配置适用大部分场景。
打包默认情况下只会影响按需加载模块,因为对初始块也进行优化打包会影响 HTML 中的 script 标签数,增加请求数。
默认情况(webpack 的默认配置)下 webpack 会根据下述条件自动进行代码块分割:
- 共享模块(至少被引用 2次)或者 node_modules 模块
- 新代码块大于 20kb(min+gziped之前的体积)
- 按需加载块时的最大并行请求数将低于或等于 30
- 初始页面加载时的最大并行请求数将低于或等于 30
SplitChunks插件配置选项
- 同步加载的模块:通过 import xxx 或 require(‘xxx’) 加载的模块。
- 异步加载的模块:通过 import(‘xxx’) 或 require([‘xxx’],() =>{}) 加载的模块。
- chunks: 表示哪些代码需要优化,有三个可选值:initial(初始块)、async(按需加载块)、all(全部块),chunks 选项,决定要提取哪些模块
- 默认是 async :只提取异步加载的模块出来打包到一个文件中。
- initial 模块下只优化初始模块,也就是说 webpack 只会对初始模块做打包优化。如果 xxx 在项目中异步加载了,也同步加载了,那么 xxx 这个模块会被提取两次,分别打包到不同的文件中。
- all 模块下同时对初始模块和异步模块做打包优化,不管异步加载还是同步加载的模块都提取出来,打包到一个文件中。
- minSize: 规定被提取的模块在压缩前的大小最小值,单位为字节。表示在压缩前的最小模块大小,默认为 20000
- minChunks: 表示被引用次数,默认为 1
- maxAsyncRequests: 按需加载时候最大的并行请求数,默认为 30
- maxInitialRequests: 一个入口最大的并行请求数,默认为 30
- enforceSizeThreshold:强制执行拆分的体积阈值
- cacheGroups: 缓存组。缓存组的属性除上面所有属性外,还有 test, priority, reuseExistingChunk
- test: 用于控制哪些模块被这个缓存组匹配到
- priority: 缓存组打包的先后优先级
- reuseExistingChunk: 如果当前代码块包含的模块已经有了,就不在产生一个新的代码块
实际使用情景:
1. 设置 chunks: initial || all 都可以提取出第三方库。但是它是把所有第三库提取出来,所以我们在只提取 react 和 react-dom 的情况下,需要自定义一个 cacheGroup。
// webpack.config.js
optimization: {splitChunks: {chunks: 'all',cacheGroups: {react: {name: 'react',test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,}}}
}
安装 webpack-bundle-analyzer, 可以查看打包体积情况,在package.json 中,npm run build 的地方写一个 --report 就会在打包文件中生成一个 report.html, 打开可以看到打包体积情况。 "build": "vue-cli-service build --report"
2. 比如发现chunk-vendors.js的大小有点大了,有1.91MB,还是项目初始化时需要加载的 js 文件,大小过大会导致首屏加载时间过长。
要优化一下,两种方法:可以用SplitChunks优化。
例如要把element从chunk-vendors.js提取出来,就在cacheGroups 进行对应配置:
chainWebpack(config) {config.when(process.env.NODE_ENV != 'development',config => {config.optimization.splitChunks({chunks: 'all',cacheGroups: {// 第三方组件libs: {// 指定chunks名称name: 'chunk-libs',//符合组的要求就给构建venderstest: /[\\/]node_modules[\\/]/,//priority:优先级:数字越大优先级越高,因为默认值为0,所以自定义的一般是负数形式,决定cacheGroups中相同条件下每个组执行的优先顺序。priority: 10,// 仅限于最初依赖的第三方chunks: 'all'},elementUI: {// 将elementUI拆分为单个包name: 'chunk-elementUI',// 重量需要大于libs和app,否则将打包到libs或app中priority: 20,// 为了适应cnpmtest: /[\\/]node_modules[\\/]_?element-ui(.*)/},//公共组件// commons: {// name: 'chunk-commons',// // can customize your rules// test: resolve('src/components'),// minChunks: 3,// priority: 30,// //这个的作用是当前的chunk如果包含了从main里面分离出来的模块,则重用这个模块,这样的问题是会影响chunk的名称。// reuseExistingChunk: true,// //最大初始化加载次数,一个入口文件可以并行加载的最大文件数量,默认3// maxInitialRequests: 3,// //表示在分离前的最小模块大小,默认为0,最小为30000// minSize: 0// },}})})}
总结:
webpack5 默认情况下只对异步模块做打包优化,额外的打包必须满足 4 个条件(webpack 的默认配置,可修改)。根据需求可调整为只对初始模块做打包优化,或者初始和异步模块一起优化。同时可配置自己的打包规则。
简而言之:chunks 控制打包作用范围,其他控制打包规则。
webpack5 的 splitChunks 功能是比较强大的,不过推荐还是使用默认模式,或者提取一下第三方库。