Webpack 默认会将尽可能多的模块代码打包在一起,优点是能减少最终页面的 HTTP 请求数,但缺点也很明显:
1 页面初始代码包过大,影响首屏渲染性能;2 无法有效应用浏览器缓存,特别对于 NPM 包这类变动较少的代码,业务代码哪怕改了一行都会导致 NPM 包缓存失效。
Chunk 是 Webpack 内部一个非常重要的底层设计,用于组织、管理、优化最终产物,在构建流程进入生成(Seal)阶段后:
1 Webpack 首先根据 entry 配置创建若干 Chunk 对象;2 遍历构建(Make)阶段找到的所有 Module 对象,同一 Entry 下的模块分配到 Entry 对应的 Chunk 中;(同步chunk)3 遇到异步模块则创建新的 Chunk 对象,并将异步模块放入该 Chunk;(异步chunk)4 分配完毕后,根据 SplitChunksPlugin 的启发式算法进一步对这些 Chunk 执行裁剪、拆分、合并、代码调优,最终调整成运行性能(可能)更优的形态;5 最后,将这些 Chunk 一个个输出成最终的产物(Asset)文件,编译工作到此结束。
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这种略显粗暴的规则则会带来两个明显问题。
而这两个问题都可以通过科学的分包策略解决,如
splitChunksPlugin可以基于一些更灵活、合理的启发式规则将 Module 编排进不同的 Chunk,最终构建出性能更佳,缓存更友好的应用产物
splitChunksPlugin的主要能力有:
SplitChunksPlugin
支持根据 Module 路径、Module 被引用次数、Chunk 大小、Chunk 请求数等决定是否对 Chunk 做进一步拆解,这些决策都可以通过 optimization.splitChunks
相应配置项调整定制,基于这些能力可以实现: node_modules
打包为 vendors
;SplitChunksPlugin
还提供了 optimization.splitChunks.cacheGroup
概念,用于对不同特点的资源做分组处理,并为这些分组设置更有针对性的分包规则;SplitChunksPlugin
还内置了 default
与 defaultVendors
两个 cacheGroup
,提供一些开箱即用的分包特性: node_modules
资源会命中 defaultVendors
规则,并被单独打包;如下:
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,则会被打包。},},},
optimization: {splitChunks: {// 代码分为两类,第一类是初始化模块,第二个是异步模块。chunks: "all", // initial async all ,默认是aync, asyncb表示splitChunks只对异步的chunks生效,比如import('./)这些,initail表示只对同步chunks生效,比如根据入口和其依赖打包的chunks,all表示都生效。}
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。
如果此时的配置为
ports = {entry: {entry1: './src/a.js',entry2: './src/b.js'},// ...optimization: {splitChunks: { minChunks: 2,//...}}
};
那么common模块会命中optimization.splitChunks.minChunks=2的规则,因此该模块可能被单独分饱,产物:
这里这是可能,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配置项,用于限制分包数量。
要注意这里所谓的请求数,不是说首次加载需要请求的http数量,而是加载一个chunks时所需要加载的分包数量。
以上述案例为例子:
再看一个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-1
、common-2
中体积较小的分包 ,也就是将体积大的包拆分出来,体积小的文件不拆分。maxAsyncRequest
逻辑与此类似
除了minChunks-模块被引用次数以及maxXXXRequests-包数量,webpack还提供了与chunk大小有关的分包判定规则,包体积过小的时候直接取消分包,包体积过大的时候尝试对chunk再做拆解,防止单个Chunk过大。
相关的配置项:
minSize
: 超过这个尺寸的 Chunk 才会正式被分包;maxSize
: 超过这个尺寸的 Chunk 会尝试进一步拆分出更小的 Chunk;maxAsyncSize
: 与 maxSize
功能类似,但只对异步引入的模块生效;maxInitialSize
: 与 maxSize
类似,但只对 entry
配置的入口模块生效;enforceSizeThreshold
: 超过这个尺寸的 Chunk 会被强制分包,忽略上述其它 Size 限制。结合前面的两种规则,splitChunksPlugin的主体流程如:
上述的minChunks,maxInitailRequest,minSize都属于分包条件,决定什么情况下对那些满足条件的module做分包处理,此外,cacheGroups配置项用于为不同的文件组设置不同的规则。满足的module会优先按cacheGroup设置的条件进行分包。如
ports = {//...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的默认配置是:
ports = {//...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等影响,强制打包}},},},
};
这两个配置组可以帮助我们:
minChunks
:用于设置引用阈值,被引用次数超过该阈值的 Module 才会进行分包处理;maxInitialRequest/maxAsyncRequests
:用于限制 Initial Chunk(或 Async Chunk) 最大并行请求数,本质上是在限制最终产生的分包数量;minSize
: 超过这个尺寸的 Chunk 才会正式被分包;maxSize
: 超过这个尺寸的 Chunk 会尝试继续做分包;maxAsyncSize
: 与 maxSize
功能类似,但只对异步引入的模块生效;maxInitialSize
: 与 maxSize
类似,但只对 entry
配置的入口模块生效;enforceSizeThreshold
: 超过这个尺寸的 Chunk 会被强制分包,忽略上述其它 size 限制;cacheGroups
:用于设置缓存组规则,为不同类型的资源设置更有针对性的分包策略。splitChunks
配置进一步优化、裁剪分包内容。本文发布于:2024-01-31 05:54:09,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170665165226018.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |