webpack-SplitChunksPlugin学习

news/2024/10/17 3:50:35/

前言

Webpack 默认会将尽可能多的模块代码打包在一起,优点是能减少最终页面的 HTTP 请求数,但缺点也很明显:

1 页面初始代码包过大,影响首屏渲染性能;2 无法有效应用浏览器缓存,特别对于 NPM 包这类变动较少的代码,业务代码哪怕改了一行都会导致 NPM 包缓存失效。

理解chunk

chunks代码块 assets资源 file文件 区别

  • modules: 模块,每个文件就是一个模块
  • chunks: 打包的每个文件属于独立的模块,然后webpack通过入口开始寻找依赖图,每个入口文件及其依赖的模块就是一个chunks
  • assets: 资源,chunks打包后输出资源,内容就是字符串。
  • file: 打包后的资源会写入硬盘,生成main.js文件
chunk详细

Chunk 是 Webpack 内部一个非常重要的底层设计,用于组织、管理、优化最终产物,在构建流程进入生成(Seal)阶段后:

1 Webpack 首先根据 entry 配置创建若干 Chunk 对象;2 遍历构建(Make)阶段找到的所有 Module 对象,同一 Entry 下的模块分配到 Entry 对应的 Chunk 中;(同步chunk)3 遇到异步模块则创建新的 Chunk 对象,并将异步模块放入该 Chunk;(异步chunk)4 分配完毕后,根据 SplitChunksPlugin 的启发式算法进一步对这些 Chunk 执行裁剪、拆分、合并、代码调优,最终调整成运行性能(可能)更优的形态;5 最后,将这些 Chunk 一个个输出成最终的产物(Asset)文件,编译工作到此结束。
Chunk 分包结果的好坏直接影响了最终应用性能,Webpack 默认会将以下三种模块做分包处理:
1 Initial Chunk:entry 模块及相应子模块打包成 Initial Chunk;2 Async Chunk:通过 import('./xx') 等语句导入的异步模块及相应子模块组成的 Async Chunk;3 Runtime Chunk:运行时代码(比如webpack的requre等代码,让其可以在浏览器环境运行)抽离成 Runtime Chunk,可通过 entry.runtime 配置项实现

其中,RunTimeChunk规则比较简单,而Initial Chunk和Async Chunk这种略显粗暴的规则则会带来两个明显问题。

  • 1 模版重复打包。假如多个 Chunk 同时依赖同一个 Module,那么这个 Module 会被不受限制地重复打包进这些 Chunk
  • 2 资源冗余和低效缓存。Webpack 会将 Entry 模块、异步模块所有代码都打进同一个单独的包
    • 资源冗余:客户端每次都需要请求整个项目代码才能启动正常,但是用户可能只需要访问当下很小一部分内容。
    • 缓存失效:所有的资源打成一个包,所有改动即使是修改了一个字符串,也会导致hash名称改变,从而导致浏览器缓存失效,客户端需要重新下载代码包。

而这两个问题都可以通过科学的分包策略解决,如

  • 1 将多个Chunk依赖的包分离成独立的chunk,防止资源重复。
  • 2 node_modules的资源变动较少,可以抽离成一个独立的包,业务代码的频繁改动不会导致第三方库资源缓存失效。

SplitChunksPlugin简介

splitChunksPlugin可以基于一些更灵活、合理的启发式规则将 Module 编排进不同的 Chunk,最终构建出性能更佳,缓存更友好的应用产物

splitChunksPlugin的主要能力有:

  • SplitChunksPlugin 支持根据 Module 路径、Module 被引用次数、Chunk 大小、Chunk 请求数等决定是否对 Chunk 做进一步拆解,这些决策都可以通过 optimization.splitChunks 相应配置项调整定制,基于这些能力可以实现:
    • 单独打包某些特定路径的内容,例如 node_modules 打包为 vendors
    • 单独打包使用频率较高的文件;
  • SplitChunksPlugin 还提供了 optimization.splitChunks.cacheGroup 概念,用于对不同特点的资源做分组处理,并为这些分组设置更有针对性的分包规则;
  • SplitChunksPlugin 还内置了 defaultdefaultVendors 两个 cacheGroup,提供一些开箱即用的分包特性:
    • node_modules 资源会命中 defaultVendors 规则,并被单独打包;
    • 只有包体超过 20kb 的 Chunk 才会被单独打包;
    • 加载 Async Chunk 所需请求数不得超过 30;
    • 加载 Initial Chunk 所需请求数不得超过 30。

如下:

 optimization: {splitChunks: {// 代码分为两类,第一类是初始化模块,第二个是异步模块。chunks: "all", // initial async all ,默认是aync, asyncb表示splitChunks只对异步的chunks生效,比如import('./)这些,initail表示只对同步chunks生效,比如根据入口和其依赖打包的chunks,all表示都生效。minSize: 30000, //默认值是30kb,分割的代码块最小尺寸。minChunks: 2, //在分割之前被引用的次数要大于2.maxAsyncRequests: 5, // 按需加载的时候的最大并行请求数小于5,一个文件中有多个import(),访问的时候一次性请求5个异步代码最多,剩余的import在下一次才请求maxInitialRequests: 3, //一个入口的最大并行请求数量(首页),就是打包后的代码,首页文件中需要加载的script脚本,bundle.js,打包后的第三方模块算一个(vendor.js),以及公用的模块也算一个(common.js)。比如首页的js文件所在的chunkjs,node_modules打包出来的,以及自己编写的公共模块。一共三个。如设置2,只能分割自己,以及node_Module中的模块,对于我们自己写的公共模块无法打包(common.js无法形成,因为不满足一个入口最大并行请求数量)。而如果chunkjs依赖了verdor.js和公共模块commonjs,那么打包后的代码就有一个循环依赖,只有commonjs 和verdor.js加载好了之后,调用改造好的Push方法,就会触发检查依赖的方法,然后只有判断verdor和common都加载完,才会执行main.js。//name: true, //打包后的名字,默认是chunk名字通过分隔符分开连接在一起。automaticNameDelimiter: "~", //名字分隔符。// 配置分割的文件cacheGroups: {vendors: {chunks: "initial", //分割的是同步的代码块。test: /node_modules/, //属于node_modules的模块,加载同步的并且是同步的就会被分割。priority: -10, // 优先级},commons: {chunks: "initial", //分割的是同步的代码块。minSize: 0,minChunks: 2, //最少被两个引用。priority: -10, // 优先级reuseExistingChunk: true, //如果该chunk引用了已经抽取的chunk,则会被打包。},},},
spligChunks主要有两种类型配置:
  • minChunks/minSize/maxInitialRequest等分包条件,满足这些条件的模块都会被执行分包。
  • cacheGroup:用于为特定资源声明特定分包条件,比如可以为nodeModules包设置更宽松的分包条件。

设置分包范围

optimization: {splitChunks: {// 代码分为两类,第一类是初始化模块,第二个是异步模块。chunks: "all", // initial async all ,默认是aync, asyncb表示splitChunks只对异步的chunks生效,比如import('./)这些,initail表示只对同步chunks生效,比如根据入口和其依赖打包的chunks,all表示都生效。}

根据module使用频率分包

SplitChunksPlugin 支持按 Module 被 Chunk 引用的次数决定是否分包,通过设置splitChunks.minChunks

optimization: {splitChunks: {minChunks: 2 //设定引用次数超过2的模块进行分包}
}

这里被chunks引用次数不直接等价于被import的次数,而是取决于上游调用者是否被视为同步chunk或者异步chunk处理(可以理解成被多少个chunks引用),如:

// common.js
export default "common chunk";// async-module.js
import common from './common'// a.js
import common from './common'
import('./async-module')// b.js
import common from './common'

a和b分别被视为initial chunk处理,async-modale被a以异步的方式引入,因此视为async chunk处理。

对于common来说。分别被三个不同的chunk引入,所以引用次数为3。

如果此时的配置为

module.exports = {entry: {entry1: './src/a.js',entry2: './src/b.js'},// ...optimization: {splitChunks: {  minChunks: 2,//...}}
};

那么common模块会命中optimization.splitChunks.minChunks=2的规则,因此该模块可能被单独分饱,产物:

  • a.js
  • b.js
  • async-module.js
  • common.js

这里这是可能,1因为minChunks并不是唯一的条件,此外还需要满足如minSize,maxInitialRequest等配置的要求才会真正执行分包。

注意这里的引用次数算法

如果这里

a->common.js

b->common.js

如果a和b是单独的entry,即表示a和b会被设为不同的chunks,那么common的引用次数就是2。

而如果a引用了b,那么a和b就属于同一个chunk,那么common的引用次数就被设为1,并不是2。

限制分包数量

在minChunks的基础上,为了防止最终产物文件过多导致http网络请求数量剧增,反而降低应用性能,wbepack提供了maxInitialRequessts/maxAsyncRequests配置项,用于限制分包数量。

  • maxInitialRequests:用于设置initial Chunk最大并行请求数。
  • maxAsyncRequests: 用于设置Async Chunk最大并行请求数。

要注意这里所谓的请求数,不是说首次加载需要请求的http数量,而是加载一个chunks时所需要加载的分包数量。

以上述案例为例子:

  • 对于a.js,加载这个chunks的时候,不仅要加载a.js,还需要加载common.js,那么commonjs对于a来说就是分包,所以此时chunkA的并行请求数就是自身+分包数=1+1=2。这里的async-module并不是initail Chunk,故不算进initial Chunk并行请求数。

再看一个demo

设minChunks=2,maxInitialRequests=2

a.js->common.jsb.js->common1.jsc.js->common.js+common1.js

如上,a,b,c被视为三个initital chunkm,common1和common都满足minChunks的要求,但是在加载c的chunk的时候,他的分包为common和common1,此时并行数量=1+2=3,并不满足maxInitailRequests=2的需求,此时,SplitChunksPlugin放弃 common-1common-2 中体积较小的分包 ,也就是将体积大的包拆分出来,体积小的文件不拆分。maxAsyncRequest 逻辑与此类似

所以并行请求数量的关键逻辑总结如下:
  • Initial Chunk 本身算一个请求;
  • async chunk不算并行请求
  • 通过runtimeChunk拆分出来的runTime不算并行请求。
  • 如果同时有两个chunk满足拆分规则,但是maxInitailrequests只能允许再拆分一个模块,那么体积更大的模块会优先被拆解。

限制分包体积

除了minChunks-模块被引用次数以及maxXXXRequests-包数量,webpack还提供了与chunk大小有关的分包判定规则,包体积过小的时候直接取消分包,包体积过大的时候尝试对chunk再做拆解,防止单个Chunk过大。

相关的配置项:

  • minSize: 超过这个尺寸的 Chunk 才会正式被分包;
  • maxSize: 超过这个尺寸的 Chunk 会尝试进一步拆分出更小的 Chunk;
  • maxAsyncSize: 与 maxSize 功能类似,但只对异步引入的模块生效;
  • maxInitialSize: 与 maxSize 类似,但只对 entry 配置的入口模块生效;
  • enforceSizeThreshold: 超过这个尺寸的 Chunk 会被强制分包,忽略上述其它 Size 限制。

结合前面的两种规则,splitChunksPlugin的主体流程如:

  • 1 尝试命中minChunks的module统一抽到一个额外的chunk对象。
  • 2 判断该cunk是否满足maxXXXRequests阈值,若满足则进行下一步判断。
  • 3 判断该chunk的资源体积是否大于上述配置项minSize声明的阈值下限,有几种情况:
    • 如果该chunk的体积小于minSize,则取消这次分包。对应的module依然会合并到原来的chunk
    • 如果chunk体积大于minSize,判断是否超过maxSize,maxAsyncSize/maxInitailSize声明的阈值上限,如果超过,会尝试将该chunk分割成更小的部分。
    • 如果chunk的体积命中enforeSizeThreshold,那么会跳过上述的条件判断,直接进行分包。

缓存组 cacheGroups

上述的minChunks,maxInitailRequest,minSize都属于分包条件,决定什么情况下对那些满足条件的module做分包处理,此外,cacheGroups配置项用于为不同的文件组设置不同的规则。满足的module会优先按cacheGroup设置的条件进行分包。如

module.exports = {//...optimization: {splitChunks: {cacheGroups: {vendors: {test: /[\\/]node_modules[\\/]/,minChunks: 1,minSize: 0}},},},
};

上述设置了vendors缓存组,满足test正则匹配的模块都会被归为vendors分组,优先使用该分组下的minCunks,minSize等分包配置。

cacheGroups支持上述,minSize/minChunks/maxInitailRequest等条件配置,此外还支持*

  • test:接受正则表达式、函数及字符串,所有符合 test 判断的 Module 或 Chunk 都会被分到该组;
  • type:接受正则表达式、函数及字符串,与 test 类似均用于筛选分组命中的模块,区别是它判断的依据是文件类型而不是文件名,例如 type = 'json' 会命中所有 JSON 文件;
  • idHint:字符串型,用于设置 Chunk ID,它还会被追加到最终产物文件名中,例如 idHint = 'vendors' 时,输出产物文件名形如 vendors-xxx-xxx.js
  • priority:数字型,用于设置该分组的优先级,若模块命中多个缓存组,则优先被分到 priority 更大的组。

缓存组的作用在于能为不同类型的资源设置更具适用性的分包规则,一个典型的场景就是将所有nodue_modules下的模块统一打包到vendors产物,从而实现第三方库与业务代码的分离,cacheGroups的默认配置是:

module.exports = {//...optimization: {splitChunks: {cacheGroups: {default: {idHint: "",reuseExistingChunk: true,minChunks: 2,priority: -20},defaultVendors: {idHint: "vendors",reuseExistingChunk: true,test: /[\\/]node_modules[\\/]/i,priority: -10,enforce: true//不受splitChunks.minSize等影响,强制打包}},},},
};

这两个配置组可以帮助我们:

  • 将所有的mode_modules中的资源单独打包到venfors-xx-xx.js中
  • 对引用次数大于等于 2 的模块 —— 也就是被多个 Chunk 引用的模块,单独打包(也受限splitChunks.minSize,maxInitialRequests影响,不一定会打包。没设置enforce)

配置总结

  • minChunks:用于设置引用阈值,被引用次数超过该阈值的 Module 才会进行分包处理;
  • maxInitialRequest/maxAsyncRequests:用于限制 Initial Chunk(或 Async Chunk) 最大并行请求数,本质上是在限制最终产生的分包数量;
  • minSize: 超过这个尺寸的 Chunk 才会正式被分包;
  • maxSize: 超过这个尺寸的 Chunk 会尝试继续做分包;
  • maxAsyncSize: 与 maxSize 功能类似,但只对异步引入的模块生效;
  • maxInitialSize: 与 maxSize 类似,但只对 entry 配置的入口模块生效;
  • enforceSizeThreshold: 超过这个尺寸的 Chunk 会被强制分包,忽略上述其它 size 限制;
  • cacheGroups:用于设置缓存组规则,为不同类型的资源设置更有针对性的分包策略。

最佳实践

  • 针对nodeModules:
    • 可以将node_mdodules模块打包成单独文件,通过cacheGroup实现,防止业务带啊吗的变更影响npm包缓存,同时通过maxSize设定阈值,防止vendor包体积过大。
    • 更激进的,如果生产环境已经部署 HTTP2/3 一类高性能网络协议,甚至可以考虑将每一个 NPM 包都打包成单独文件。
  • 针对业务代码:
    • 设置common分组,通过minChunks配置项,将使用率较高的资源合并为common资源。
    • 首评用不上的代码,比如除了主页外其他的页面,尽量使用异步导入。
    • 设置optimization.runTimeChunk为true,将运行时代码拆分为独立资源。

总结

  • Chunk 是 Webpack 实现模块打包的关键设计,Webpack 会首先为 Entry 模块(initail chunk)、异步模块(import(‘/’))、Runtime 模块(取决于配置) 创建 Chunk 容器,之后按照 splitChunks 配置进一步优化、裁剪分包内容。
  • splitChunks的规则大致可以分为:
    • 规则类,如统一的minSize, minChunks等
    • cacheGroup: 针对特定资源的规则集合。被命中的模块会优先使用cacheGroup的规则进行分包。
      学习文章:掘金:《Webpack5 核心原理与应用实践》

http://www.ppmy.cn/news/123301.html

相关文章

vr报价单_七里河区vr全景展示报价表

七里河区vr全景展示报价表 VR现实创新实验室综合解决方案是一款为学校、校外教育基地设计开发的软硬件一体化产品,通过现实技术,满足青少年自主探索、安全实验的vr现实实验创新教学的需求。 该方案依托国际先进的桌面级便携式一体化现实设备,…

中国移动苏州研发中心暑期实习随行录-1

2019年7月8号 今天是实习报到的第一天,早上从宿舍乘坐班车去公司,宿舍是苏小研租的高博软件学院的一个学生宿舍。宿舍是六人间,但是只住四人,宿舍有空调,热水器,我们都是住在一楼,所以相对来说有…

服务器柜机位置摆放电子图,柜式空调摆放位置有什么要注意的吗

答:根据你的情况,可以放在门后面,斜着往里面(45度角),因为门口是太阳西下,里面应该不是很热,斜着往里面不仅里面保持凉快,门口处也可以当冷气帘,挡住从外面进来的热气。我的回答希望…

空调大1.5匹、正1.5匹、小1.5匹的区别

空调调大1.5匹、正1.5匹、小1.5匹有什么区别,很明显的就能看出来,其在功率大小上是有关系的。小1.5匹的空调制冷量是3200W左右,也叫1.25匹空调,适合16平米左右的房间使用;把制冷量为3500W的空调叫做正1.5匹&#xff0c…

健康消费升级提速,“智慧”+“健康”TCL空调能否构筑市场新格局?

文|曾响铃 来源|科技向令说(xiangling0815) 五月初夏,终于迎来了空调市场的第一波热潮,苏宁数据显示,五一期间线上空调销售量同比增长99.7%,几近翻倍。 然而,今年的情况又与往年不太一样&…

保障移动安全的十大诀窍

随着移动设备的普及度不断攀升,新型商务及娱乐应用也开始在市场上大规模出现。如今人们已经可以在移动设备上玩游戏、购物、支付账单并通过社交网络分享观点。 “对于消费者而言,网络犯罪似乎还是一种仅仅威胁大型企业、无暇侵犯个人权益的隐患。然而在线…

移动商城第一篇【搭建项目环境+数据模型】

前言 本次该项目使用的技术如下: 这里写图片描述 搭建Oracle数据库环境 本次我们用Oracle作为我们的服务器,我们一般开发并不是把数据表放在我们练习的scott用户下的。 需要我们自己创建用户、创建默认的表空间 使用超级管理员账号登陆Oracle 这里写图片…

2022年制冷与空调设备运行操作考试练习题及模拟考试

题库来源:安全生产模拟考试一点通公众号小程序 2022年制冷与空调设备运行操作培训试题是制冷与空调设备运行操作模拟考试题库理论知识考试题库!2022年制冷与空调设备运行操作考试练习题及模拟考试依据制冷与空调设备运行操作新版教材大纲编写。制冷与空…