深入理解webpack的chunkId对线上缓存的思考

   日期:2024-12-29    作者:sk20k 移动:http://mip.riyuangf.com/mobile/quote/80851.html

想必经常使用基于webpack打包工具的框架的同学们,无论是使用React还是Vue在性能优化上使用最多的应该是分包策略(按需加载)。按需加载的方式使我们的每一个bundle变的更小,在每一个单页中只需要引入当前页所使用到的JavaScript代码,从而提高了代码的加载速度。但是,由于webpack目前版本自身的原因分包策略虽然可以提高我们的加载速度,然而在线上缓存方面却给了我们极大的破坏(webpack5中解决了这个问题)。

深入理解webpack的chunkId对线上缓存的思考

本文主要通过以下四个方面,来深入剖析chunkId:

  • chunkId是怎么生成的?
  • chunkId是怎么破坏线上缓存的?
  • 解决chunkId对破坏缓存的方法
  • 远观未来,webpack5完美解决

webpack是一个基于模块化的打包工具,其总体打包流程可分为:

  1. 初始化阶段
  2. 编译阶段
  3. 输出阶段

初始化阶段

webpack初始化阶段主要在webpack.js中完成,有以下方面:

  • webpack-cli启动,获取webpack.config.js配置及合并shell参数
  • 根据cli得到的配置合并默认配置
  • 创建compiler实例
  • 遍历配置中的plugins加载第三方插件
  • 初始化默认插件

编译阶段

初始化完成之后,cli得到compiler实例,执行compiler.run()开始编译,编译的过程主要分为以下步骤:

  • 分析entry,逐一遍历
  • 确定依赖模块,递归解析
  • 分包策略确定每一个chunk所包含的module,合并module生成chunk。
  • 确定模块资源
  • 根据chunk的entry的不同,确定输出template模板。

输出阶段

  • 输出文件

上面简单了解一下打包流程,当然最主要的目的不是为了了解打包流程,而是其中的一个点:chunk是怎么生成的

从编译阶段中可以看出,chunk是由多个module合并生成的,每一个chunk生成的时候都会有一个对应的chunkId,chunkId的生成策略是本节讨论的重点。

chunkId的生成策略可以在官网中找到,主要有五种规则:

  1. false:不适用任何算法,通过插件提供自定义算法。
  2. natural:自然数ID
  3. named:使用name值作为Id,可读性高。
  4. size:数字ID,依据最小的初始下载大小。
  5. total-size:数字ID,依据最小的总下载大小。

不同的生成规则所打包出来的chunkId是不同的。但是,其实内部生成方式是一样的,不同的规则只是对chunks中的chunk排序规则不同(说的什么玩意,什么一会相同,一会不同的)。不要着急,接下来就来看一下这东西到底是怎么生成的。

我们都知道webpack的optimization中有个chunkIds的配置,上面五种值,就是它的可选值。在开发环境下默认值为named,在生产环境下默认值为size。

在webpack初始化阶段会挂载内部插件,我们直接定位到这个文件的第437行。


上面代码中可以看到,在初始化阶段不同的chunkIds的值会加载不同的插件,并且进入这个插件内部你会发现他们都是挂载到这个钩子上。那么疑问来了,这个钩子是在什么时机执行的呢?定位到``compilation.js`的第1334行会得到答案。


在执行流程中可以看出,chunkId在生成前确定生成规则。可能你的疑问又来了,它是怎么根据chunkId的值的不同生成规则呢?其实所有的chunk都存放在一个数组里面(也就是chunks),在中根据规则的不同对chunk进行相应的排序,然后再统一的对进行赋值。眼见为实,我们先来看一下applyChunkIds中是怎么赋值的,定位到compilation.js中的1754行。


这生成过程中判断chunk.id是否为null,如果为null,对id赋值nextFreeChunkId。没错,无论是什么生成规则,都是这样赋值的。明白了所有的生成规则都是使用相同的赋值规则之后,我们现在的疑问应该就是每个规则中是怎么对chunks进行排序的?接下来就来看一下每个规则是怎么做的。

在中我们可以知道,chunkIds值为natural的时候,挂载的是这个插件。


首先,在每一个chunk中都有一个这个属性,它是一个,里面存放的是所有合并当前的module,每个module的id属性表示当前module的。主要做的事就是根据moduleId来最为排序规则进行排序。

named的生成规则比较简单,根据chunk的name取值


named与其他方式的区别在于,named不是在中对chunkId操作,而是在beforeChunkIds阶段。所做的事是遍历所有的chunk,判断chunk的id值是否为null,如果为null,取到chunk的name值赋予id。

当执行的时候,由于当前的id值已经不是null了,所以跳过赋值规则,直接使用已存在的值。

size和total-size规则由于调用的是相同的插件,只是参数的不同,所以我们就一起看一下它是怎么做的。打开文件。

size和total-size调用插件的区别:

  1. size规则:prioritiseInitial为true。
  2. total-size规则:prioritiseInitial为false。

OccurrenceChunkOrderPlugin通过prioritiseInitial区分是size还是total-size:

  • prioritiseInitial为true:根据父模块的数量排序,如果数量相同走total-size的逻辑。
  • prioritiseInitial为false:首先根据chunk的groups的数量排序,如果数量相同,根据chunk所在的索引排序。
  1. 首先我们会发现除了named之外的规则都是生成的number值,并且只是在生成chunkId前,对chunks以不同的规则进行排序。
  2. 通过named规则,我们可以发现,如果在beforeChunkIds中给chunkId赋值,那么就会阻截默认的规则。

说到破坏,我们心中可能又会有疑问,这东西怎么会破坏线上缓存呢?我们来模拟一个场景。

想必业务思想很好的你,有时候也会让业务的快速变更搞的非常烦恼,假设一个blog项目三个功能模块:文章列表页、文章标签页、关于页,并且三个功能模块都是异步的。我们来简写一下代码。

首先入口文件为index.js,三个功能模块代码为articleList.js、articleTag.js、about.js。


在index.js中异步引入这三个功能模块。


我们使用生产环境打包一下,得到dist目录中的文件如下:

很完美,打包成功,结果也肯定和我们想的一样。

假如有一天,需求变了,关于我们页不想要了,让它暂时不存在项目里面了(为了方便文件的diff,我们先把当前的代码做一个备份),我们可以先把About的代码在index.js中的代码注释。


注释之后,重新打包。重新生成的文件和备份的如下

可以你会说,这不小意思吗?我有webpack的魔法注释,不让文件名变不就得了。(此时作者只能呵呵一笑)我们来验证一下。

我们来把index中引入的三个模块都加上魔法注释:


打包结果如下

  1. 按需加载可以使单个js文件的代码量更小、加载更快,但是带来优化的同时也对缓存产生了极大的伤害。
  2. 缓存是性能优化中极为重要的部分,罪魁祸首在chunkId,所以必须盘它。

相信上述问题,早已被社区的同学们发现,笔者在也曾找了一会插件,但都没有如愿,心里不服,干脆自己写一个。

webpack-fixed-chunk-id-plugin 这个插件已经被笔者发布到npm,代码极简,可能会存在不足,还望社区大佬多多提建议,共同成长。

根据上文我们可以得出,万物的罪魁祸首是chunkId,所以必须要固定它,才能让文件内容不会变。那如何固定呢?

第一点:根据上文第一部分分析chunkId生成原理的时候,我们从named这个规则中得出只要在,这个地方给chunkId一个值,在阶段就不会对chunkId执行定义的规则。

第二点:上一点得出在webpack什么阶段来控制chunkId,那么这点就应该讨论控制chunkId要基于什么来控制? 第一个想到的肯定是内容,基于内容来控制chunkId,当内容变chunkId变、内容不变chunkId不变。

基于上面两点,插件代码如下:


通过挂载到beforeChunkIds钩子上,拿到所有的chunk,遍历每一个chunk得到所有合并当前chunk的module的内容,使用node的crypto加密模块,对内容计算hash值,设置。下面我们来测试一下,这个插件好不好用。





  1. 根据打包问题,确定事故发生地点---chunkId。
  2. 根据事故发生时机,找出阻截事故发生方案---beforeChunkIds。
  3. 定制可行方案---基于module内容来生成唯一hash。

chunkId事故问题可谓webpack自身留下的坑,chunkId方便了开发者,同样chunkId也对我们造成了极大的破坏,正所谓:成也chunkId、败也chunkId。

webpack4中遗留的问题,在还未现世的webpack5中得到了完美的解决。

接下来开始尝鲜webpack5。由于webpack5还未发版,我们可以通过一些方法来使用它。


把webpack4中的src下的代码拷贝到webpack5中打包,结果如下:

我们来按照之前的方式验证一下,把about模块注释,并使用Beyond Compare比较一下。

虽然webpack5可以执行以上操作,但是由于目前还未发布,以cli的配合并不完善。目前的版本,只要写webpack.config.js使用cli启动就会报错,如果要使用配置文件的话就只能使用node来启动webpack。

并且如果要使用webpack5完美的chunkId,还需要在webpack配置文件中配置一下内容:



目前的webpack5已经有了很多优秀的特性,包括代码也变的更加简介,总之,拥抱webpack5吧。


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号