首页 > 其他分享 >webpack性能优化(2):splitChunks用法详解

webpack性能优化(2):splitChunks用法详解

时间:2023-03-18 19:04:14浏览次数:52  
标签:splitChunks 模块 bundle webpack 详解 拆分 minChunks

之前写的《​​webpack性能优化(0):webpack性能优化概况-优化构建速度​​​》、《​​webpack性能优化(1):分隔/分包/异步加载+组件与路由懒加载​​》

如果使用vue-cli,默认生成的vendor.js文件会非常大。这个时候需要进行拆包。其实打包输出后,都可以用如下工具瞧瞧包依赖情况。

  • ​webpack-chart​​:用于webpack统计的交互式饼图。
  • ​webpack-visualizer​​:可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
  • ​webpack-bundle-analyzer​​:一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。

第二是通过 Chrome 的 Instrument converge 功能查看 js,css 的资源使用率:​​https://developers.google.com/web/tools/chrome-devtools/coverage​

webpack性能优化(2):splitChunks用法详解_webpack

优化输出与分包,原来熟悉的配方 new webpack.optimize.CommonsChunkPlugin({}),现在可直接配置optimization项目

在研究splitChunks之前,我们必须先弄明白 module、chunk和bundle 这三个名词是什么意思:

  • module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码。
  • chunk: chunk是webpack根据功能拆分出来的,包含三种情况:
  • 你的项目入口(entry)
  • 通过import()动态引入的代码
  • 通过splitChunks拆分出来的代码
    chunk包含着module,可能是一对多也可能是一对一。
  • bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。

代码分离 | Code Splitting

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

有三种常用的代码分离方法:

  • 入口点:使用entry配置手动分割代码。
    这种方法存在一些缺陷
  • 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
  • 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。
  • 防止复制:使用 CommonsChunkPlugin 去重和分离 chunk。将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
    一些对于代码分离很有帮助的插件和 loaders:
  • ExtractTextPlugin:用于将CSS从主应用程序中分离出来。

  • bundle-loader:用于分离代码和延迟加载生成的 bundle。

  • promise-loader:类似于 bundle-loader ,但是使用的是 promises。

  • 动态导入:通过模块的内联函数调用来分离代码。所谓的延迟加载|按需加载|懒加载

  • 先选择的方式是,使用符合 ECMAScript 提案 的 import() 语法。

  • 使用 webpack 特定的 require.ensure

其实我们一帮需要做的是​​optimization.splitChunks​​。以下是生产环境默认配置(webpack版本 4.29.5)

splitChunks: {
hidePathInfo: true,
chunks: 'all',
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
automaticNameDelimiter: '~',
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
reuseExistingChunk: true,
minChunks: 2,
priority: 20
},
vendors: {
automaticNamePrefix: 'vendors',
test: /[\\\/]node_modules[\\\/]/,
priority: -10
}
}}

splitChunks就算你什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性。

我们需要在整个基础上进行优化,所以需要熟悉里面的配置

splitChunks配置解说

配置项也是蛮多,这里抽取一些重点说明,更加详细还是看文档。

splitChunks.maxAnyscRequests 按需加载并发最大请求数

maxInitialRequests是splitChunks里面比较难以理解的点之一,它表示允许入口并行加载的最大请求数,之所以有这个配置也是为了对拆分数量进行限制,不至于拆分出太多模块导致请求数量过多而得不偿失

这里需要注意几点:

  • 入口文件本身算一个请求
  • 如果入口里面有动态加载得模块这个不算在内
  • 通过runtimeChunk拆分出的runtime不算在内
  • 只算js文件的请求,css不算在内
  • 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来

具体参考《​​理解webpack4.splitChunks之maxInitialRequests​​》

splitChunks.maxAsyncRequests

maxAsyncRequests和maxInitialRequests有相似之处,它俩都是用来限制拆分数量的。

  • maxInitialRequests是用来限制入口的拆分数量
  • maxAsyncRequests是用来限制异步模块内部的并行最大请求数的,说白了你可以理解为是每个import()它里面的最大并行请求数量。

这其中要注意以下几点:

  1. import()文件本身算一个请求
  2. 并不算js以外的公共资源请求比如css
  3. 如果同时有两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来

具体参看《​​理解webpack4.splitChunks之maxAsyncRequests​​》

splitChunks.minChunks

这个配置表示 split 前单个非按需导入的 module 的并行数的最低下限

注:只对 import 或 require 直接引入的 module 有效。

简单来讲,假如 minChunks 设置为 n,那么某个 module 想要被拆分出去,那么它的共享次数(或者说并行请求次数必须 >= n):

minChunks设置为n

  • 假设有m个入口点,这m个入口点都直接引入了某个模块module(通过import或require直接或间接地引入了模块),也就是共享次数为m
  • 当m至少等于n时,module才会被单独拆分成一个bundle

但是,有个特例

minChunks设置成1

  • 有一个入口点,入口点中import了一个模块,并打印了某些字符串,我们就叫它module
  • module被单独拆分成一个bundle,并且这个bundle文件中也包含了打印字符串的部分

我们注意到拆分出来的那个 bundle 包含了打印字符串的部分,那么如果入口点中仅仅包含了打印字符串的部分,没有引入 module,结果是怎样呢,结果就是打印的那部分代码被单独拆分出来了。所以当 minChunks 被设为 1 时,被拆分出来的某个 bundle 一定包含非引入模块代码,如果非引入模块代码存在的话,而当值设为大于 1 的数值时,则不会出现这种情况

最后,还有一个点需要注意,minChunks 不能设为 0,其值为 >= 1 的正整数,不然为报错

minChunks设置成Infinity

搞懂了minChunks的number属性,Infinity属性就很好理解了。Infinity是不会把任何依赖的模块提取出来打包公用

splitChunks.minChunks用法总结

  1. splitChunks.minChunks 表示 split 前单个非按需导入的 module 的并行数的最低下限,即某个模块的引用次数必须大于等于设置的数值,该模块才能被拆分出来;
  2. splitChunks.minChunks 值如果设为 1,会存在被拆分出来的 bundle 包含非引入模块代码的可能,大于 1 则不会有这种情况;
  3. splitChunks.minChunks 值必须大于等于 1;

minSize与maxSize

minSize限制拆分包的最小值(达到这个值,就拆出新包)

maxSize限制每个拆分出来的包的最大文件体积(超过这个大小,再做包拆分

cacheGroups 缓存组

cacheGroups其实是splitChunks里面最核心的配置,一开始我还认为cacheGroups是可有可无的,这是完全错误的,splitChunks就是根据cacheGroups去拆分模块的,包括之前说的chunks属性和之后要介绍的种种属性其实都是对缓存组进行配置的。

缓存组可以继承重写SplitChunks中的任何属性;但是test、priority和ReuseExistingChunk只能在缓存组级别配置 . 也就是说真正起作用的是cacheGroups, SplitChunks中设置属性仅仅是基本配置而已。

CacheGroups之外设置的约束条件比如说默认配置里面的chunks、minSize、minChunks等等都会作用于cacheGroups,除了test, priority and reuseExistingChunk,这三个是只能定义在cacheGroup这一层的。

因为默认的minChunks=1,这个属性会作用于所有的cacheGroups,但是cacheGroups也可以将上面的所有属性都重新定义,就会覆盖外面的默认属性,比如default这个缓存组就设置了minChunks=2,他会覆盖掉默认值1。

splitChunks默认有两个缓存组:vender(webpack5 默认为defaultVendors)和default,

optimization.runtimeChunk

优化持久化缓存的, runtime 指的是 webpack 的运行环境(具体作用就是模块解析, 加载) 和 模块信息清单

模块信息清单在每次有模块变更(hash 变更)时都会变更, 所以我们想把这部分代码单独打包出来, 配合后端缓存策略, 这样就不会因为某个模块的变更导致包含模块信息的模块(通常会被包含在最后一个 bundle 中)缓存失效. 

optimization.runtimeChunk 就是告诉 webpack 是否要把这部分单独打包出来.

何为运行时代码?

形如import('abc').then(res=>{})这种异步加载的代码,在webpack中即为运行时代码。

设置runtimeChunk是将包含chunks 映射关系的 list单独从 app.js里提取出来,因为每一个 chunk 的 id 基本都是基于内容 hash 出来的,所以每次改动都会影响它,如果不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。设置runtimeChunk之后,webpack就会生成一个个runtime~xxx.js的文件。

然后每次更改所谓的运行时代码文件时,打包构建时app.js的hash值是不会改变的。如果每次项目更新都会更改app.js的hash值,那么用户端浏览器每次都需要重新加载变化的app.js,如果项目大切优化分包没做好的话会导致第一次加载很耗时,导致用户体验变差。

现在设置了runtimeChunk,就解决了这样的问题。所以这样做的目的是避免文件的频繁变更导致浏览器缓存失效,所以其是更好的利用缓存。提升用户体验。

runtimeChunk作用是为了线上更新版本时,充分利用浏览器缓存,使用户感知的影响到最低。

具体参看《​​webpack之optimization.runtimeChunk作用​​》

下面是一个参考配置:

// 当我们运行项目并且打包的时候,会发现chunk-vendors.js这个文件非常大,那是因为webpack将所有的依赖全都压缩到了这个文件里面,这时我们可以将其拆分,将所有的依赖都打包成单独的js。
config.optimization = {
mergeDuplicateChunks: true, // 合并相同的 chunk
// runtimeChunk: 'manifest',
// runtimeChunk: 'single',
splitChunks: {
chunks: 'async',//表示将选择哪些块进行优化。提供字符的有效值为all、async和initial,默认是仅对异步加载的块进行分割。
minSize: 100 * 1024,//模块大于minSize时才会被分割出来。默认100k
maxSize: 0,//生成的块的最大大小,如果超过了这个限制,大块会被拆分成多个小块。
minChunks: 1,//拆分前必须共享模块的最小块数。
maxAsyncRequests: 5,//按需加载时并行请求的最大数目。
maxInitialRequests: 3,//入口点的最大并行请求数
automaticNameDelimiter: '~',//默认情况下,webpack将使用块的来源和名称(例如vendors~main.js)生成名称。此选项允许您指定要用于生成的名称的分隔符。
automaticNameMaxLength: 30,//允许为SplitChunksPlugin生成的块名称的最大长度
name: true,
cacheGroups: {
elementUI: {
priority: 20,
name: 'element-ui',
test: /element-ui/,
reuseExistingChunk: true
},
vendor: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,//控制此缓存组选择的模块。省略它将选择所有模块。它可以匹配绝对模块资源路径或块名称。匹配块名称时,将选择块中的所有模块。
minChunks: 1,
// maxInitialRequests: 12,
maxAsyncRequests: 5,
minSize: 100 * 1024,
maxSize: 5 * 1000 * 1024,
priority: -10//一个模块可以属于多个缓存组,模块出现在优先级最高的缓存组中
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true//如果当前块包含已经从主包中分离出来的模块,那么该模块将被重用,而不是生成新的模块
}
}
}
}

如果有更好的办法,请赐教。

externals配置

启用CDN,提高缓存效率与打包分析,具体参看《​​webpack性能优化(0):webpack性能优化概况-优化构建速度​​ 》

路由懒加载分组

分组修改方法如下:

const Role = () => import(/* webpackChunkName: "group-role" */'./views/system/Role.vue')
const AddRole = () => import(/* webpackChunkName: "group-role" */'./views/system/AddRole.vue')
const EditRole = () => import(/* webpackChunkName: "group-role" */'./views/system/EditRole.vue')

const User = () => import(/* webpackChunkName: "group-user" */'./views/system/User.vue')
const AddUser = () => import(/* webpackChunkName: "group-user" */'./views/system/AddUser.vue')
const EditUser = () => import(/* webpackChunkName: "group-user" */'./views/system/EditUser.vue')
const UserAllotRole = () => import(/* webpackChunkName: "group-user" */'./views/system/UserAllotRole.vue')

每个功能模块打包后的 js 大概有十几kb,文件数量也大大减少。

performance性能监控

这些限制告诉webpack如何/何时拆分块,它们仅定义了限制值,在限制值以上,警告在控制台中显示,仅此而已。

entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.

performance: {
maxEntrypointSize: 512000,
maxAssetSize: 512000
}

参考文章:

webpack4 之 splitChunks.minChunks ​​https://zhuanlan.zhihu.com/p/110175375​

理解webpack4.splitChunks ​​https://www.cnblogs.com/kwzm/p/10314438.html​

vuecli3项目中webpack4配置(三)代码分割 ​​https://juejin.im/post/6844903879046332424​



转载​​本站​​文章《​​webpack性能优化(2):splitChunks用法详解​​》,
请注明出处:​​https://www.zhoulujun.cn/html/tools/Bundler/webpackTheory/8554.html​

标签:splitChunks,模块,bundle,webpack,详解,拆分,minChunks
From: https://blog.51cto.com/zhoulujun/6129741

相关文章

  • YOLO详解------YOLOV1
    CV小白说YOLOV1题外话:目标检测是什么?它是在图像中对一类或多类感兴趣的目标进行查找和分类,确定它们的类别和位置。由于各类物体有不同的外观、形状和姿态,加上成像时各......
  • YOLO详解------YOLOV1
    CV小白说YOLOV1题外话:目标检测是什么?它是在图像中对一类或多类感兴趣的目标进行查找和分类,确定它们的类别和位置。由于各类物体有不同的外观、形状和姿态,加上成像时各......
  • webpack原理(2):ES6 module在Webpack中如何Tree-shaking构建
    Tree-shaking最早由打包工具Rollup 提出DCE作用于模块内(webpack的DCE通过UglifyJS完成),而Tree-shaking则是在打包的时候通过模块之间的信息打包必须的代码。We......
  • webpack原理(3):Tapable源码分析及钩子函数作用分析
    webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation......
  • JS数组reduce()方法详解及高级技巧
        参考:https://www.cnblogs.com/webSnow/p/15262337.html......
  • webpack原理(1):Webpack热更新实现原理代码分析
    热更新,主要就是把前端工程文件变更,即时编译,然后通知到浏览器端,刷新代码。服务单与客户端通信方式有:ajax轮询,EventSource、websockt。客户端刷新一般分为两种:整体页......
  • Git详解
    Git的详解一.git的全局设置gitconfig--globaluser.name"username"gitconfig--globaluser.email"email"二.Git的基础命令(在需要目录下打开Gitbash窗口)1......
  • Tarjan算法详解
    Tarjan算法介绍Tarjan算法是用于在有向图中求强连通分量的算法这里给出强连通分量的定义:有向图中一个最大的图,使这个图中每个两点都能够互相到达。这个最大的图称为强连......
  • storybook组件属性详解:组件props到strorybook Args
    首先我们查看官方文档:https://storybook.js.org/docs/vue/writing-docs/doc-block-argstable#customizing官方的例子么有看到v-model如何处理,数组、对象等复杂属性定义。......
  • 响应式编程详解,带你熟悉Reactor响应式编程
    文章目录​​一、什么是响应式编程​​​​1、Java的流和响应式流​​​​2、Java中响应式的使用​​​​3、Reactor中响应式流的基本接口​​​​4、Reactor中响应式接口的......