Webpack: 三种Chunk产物的打包逻辑

概述

  • 在前文 Webpack: Dependency Graph 管理模块间依赖 中,我们已经详细讲解了「构建」阶段如何从 Entry 开始逐步递归读入、解析模块内容,并最终构建出模块依赖关系图 —— ModuleGraph 对象。本文我们继续往下,讲解在接下来的「封装」阶段,如何根据 ModuleGraph 内容组织 Chunk,并进一步构建出 ChunkGroup、ChunkGraph 依赖关系对象的主流程。

主流程之外,我们还会详细讲解几个比较模糊的概念:

  • Chunk、ChunkGroup、ChunGraph 对象分别是什么?互相之间存在怎样的交互关系?
  • Webpack 默认分包规则,以及规则中存在的问题。

ChunkGraph 构建过程

在 前 Init、Make、Seal》中,我们已经介绍了 Webpack 底层构建逻辑大体上可以划分为:「初始化、构建、封装」三个阶段:

在这里插入图片描述

其中,「构建」阶段负责分析模块间的依赖关系,建立起模块之间的 依赖关系图(ModuleGraph);紧接着,在「封装」阶段根据依赖关系图,将模块分开封装进若干 Chunk 对象中,并将 Chunk 之间的父子依赖关系梳理成 ChunkGraph 与若干 ChunkGroup 对象。

「封装」阶段最重要的目标就是根据「构建」阶段收集到的 ModuleGraph 关系图构建 ChunkGraph 关系图,这个过程的逻辑比较复杂:

请添加图片描述

我们简单分析一下这里面几个重要步骤的实现逻辑。

第一步非常关键: 调用 seal() 函数后,遍历 entry 配置,为每个入口创建一个空的 Chunk 与 EntryPoint 对象(一种特殊的 ChunkGroup),并初步设置好基本的 ChunkGraph 结构关系,为下一步骤做好准备,关键代码:

class Compilation {seal(callback) {// ...const chunkGraphInit = new Map();// 遍历入口模块列表for (const [name, { dependencies, includeDependencies, options }] of this.entries) {// 为每一个 entry 创建对应的 Chunk 对象const chunk = this.addChunk(name);// 为每一个 entry 创建对应的 ChunkGroup 对象const entrypoint = new Entrypoint(options);// 关联 Chunk 与 ChunkGroupconnectChunkGroupAndChunk(entrypoint, chunk);// 遍历 entry Dependency 列表for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {// 为每一个 EntryPoint 关联入口依赖对象,以便下一步从入口依赖开始遍历其它模块entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request);const module = this.moduleGraph.getModule(dep);if (module) {// 在 ChunkGraph 中记录入口模块与 Chunk 关系chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);// ...}}}// 调用 buildChunkGraph 方法,开始构建 ChunkGraphbuildChunkGraph(this, chunkGraphInit);// 触发各种优化钩子// ...}
}

执行完成后,形成如下数据结构:
在这里插入图片描述

其次,若此时配置了 entry.runtime,Webpack 还会在这个阶段为运行时代码 创建 相应的 Chunk 并直接 分配 给 entry 对应的 ChunkGroup对象。一切准备就绪后调用 buildChunkGraph 函数,进入下一步骤。

第二步:buildChunkGraph 函数内 调用 visitModules 函数,遍历 ModuleGraph,将所有 Module 按照依赖关系分配给不同 Chunk 对象;这个过程中若遇到 异步模块,则为该模块 创建新的 ChunkGroupChunk 对象,最终形成如下数据结构:
请添加图片描述

第三步:buildChunkGraph 函数中调用 connectChunkGroups 方法,建立 ChunkGroup 之间、Chunk 之间的依赖关系,生成完整的 ChunkGraph 对象,最终形成如下数据结构:
请添加图片描述

第四步:buildChunkGraph 函数中调用 cleanupUnconnectedGroups 方法,清理无效 ChunkGroup,主要起到性能优化作用。

自上而下经过这四个步骤后,ModuleGraph 中存储的模块将根据模块本身的性质,被分配到 Entry、Async、Runtime 三种不同的 Chunk 对象,并将 Chunk 之间的依赖关系存储到 ChunkGraph 与 ChunkGroup 集合中,后续可在这些对象基础上继续修改分包策略(例如 SplitChunksPlugin),通过重新组织、分配 Module 与 Chunk 对象的归属实现分包优化。

Chunk vs ChunkGroup vs ChunkGraph

上述构建过程涉及 Chunk、ChunkGroup、ChunkGraph 三种关键对象,我们先总结它们的概念与作用,加深理解:

  • Chunk:Module 用于读入模块内容,记录模块间依赖等;而 Chunk 则根据模块依赖关系合并多个 Module,输出成资产文件(合并、输出产物的逻辑,我们放到下一章讲解):

请添加图片描述

  • ChunkGroup:一个 ChunkGroup 内包含一个或多个 Chunk 对象;ChunkGroupChunkGroup 之间形成父子依赖关系:

请添加图片描述

  • ChunkGraph:最后,Webpack 会将 Chunk 之间、ChunkGroup 之间的依赖关系存储到 compilation.chunkGraph 对象中,形成如下类型关系:
    请添加图片描述

默认分包规则

综合上述 ChunkGraph 构建流程最终会将 Module 组织成三种不同类型的 Chunk:

  • Entry Chunk:同一个 entry 下触达到的模块组织成一个 Chunk;
  • Async Chunk:异步模块单独组织为一个 Chunk;
  • Runtime Chunk:entry.runtime 不为空时,会将运行时模块单独组织成一个 Chunk。

这是 Webpack 内置的,在不使用 splitChunks 或其它插件的情况下,模块输入映射到输出的默认规则,是 Webpack 底层关键原理之一,因此有必要展开介绍每一种 Chunk 的具体规则。

Entry Chunk:

先从 Entry Chunk 开始,Webpack 首先会为每一个 entry 创建 Chunk 对象,例如对于如下配置:

module.exports = {entry: {main: "./src/main",home: "./src/home",}
};

遍历 entry 对象属性并创建出 chunk[main]chunk[home] 两个对象,此时两个 Chunk 分别包含 mainhome 模块:
在这里插入图片描述

初始化完毕后,Webpack 会根据 ModuleGraph 的依赖关系数据,将 entry 下所触及的所有 Module 塞入 Chunk (发生在 visitModules 方法),比如对于如下文件依赖:
在这里插入图片描述

main.js 以同步方式直接或间接引用了 a/b/c/d 四个文件,Webpack 会首先为 main.js 模块创建 Chunk 与 EntryPoint 对象,之后将 a/b/c/d 模块逐步添加到 chunk[main] 中,最终形成:
在这里插入图片描述

Async Chunk:

其次,Webpack 会将每一个异步导入语句(import(xxx)require.ensure)处理为一个单独的 Chunk 对象,并将其子模块都加入这个 Chunk 中 —— 我们称之为 Async Chunk。例如对于下面的例子:

// index.js
import './sync-a.js'
import './sync-b.js'import('./async-a.js')// async-a.js
import './sync-c.js'

在入口模块 index.js 中,以同步方式引入 sync-a、sync-b;以异步方式引入 async-a 模块;同时,在 async-a 中以同步方式引入 sync-c 模块,形成如下模块依赖关系图:
请添加图片描述

此时,Webpack 会为入口 index.js、异步模块 async-a.js 分别创建分包,形成如下 Chunk 结构:
在这里插入图片描述

并且 chunk[index]chunk[async-a] 之间形成了单向依赖关系,Webpack 会将这种依赖关系保存在 ChunkGroup._parentsChunkGroup._children 属性中。

Runtime Chunk:

最后,除了 entry、异步模块外,Webpack5 还支持将 Runtime 代码单独抽取为 Chunk。这里说的 Runtime 代码是指一些为了确保打包产物能正常运行,而由 Webpack 注入的一系列基础框架代码,举个例子,常见的 Webpack 打包产物结构如:
请添加图片描述
上图红框圈出来的一大段代码就是 Webpack 动态生成的运行时代码,编译时,Webpack 会根据业务代码,决定输出哪些支撑特性的运行时代码(基于 Dependency 子类),例如:

  • 需要 __webpack_require__.f__webpack_require__.r 等功能实现最起码的模块化支持;
  • 如果用到动态加载特性,则需要写入 __webpack_require__.e 函数;
  • 如果用到 Module Federation 特性,则需要写入 __webpack_require__.o 函数;
  • 等等。

虽然每段运行时代码可能都很小,但随着特性的增加,最终结果会越来越大,特别对于多 entry 应用,在每个入口都重复打包一份相似的运行时显得有点浪费,为此 Webpack5 提供了 entry.runtime 配置项用于声明如何打包运行时代码。用法上只需在 entry 项中增加字符串形式的 runtime 值,例如:

module.exports = {entry: {index: { import: "./src/index", runtime: "solid-runtime" },}
};

compilation.seal 函数中,Webpack 首先为 entry 创建 EntryPoint,之后判断 entry 配置中是否带有 runtime 属性,有则创建以 runtime 值为名的 Chunk,因此,上例配置将生成两个 Chunk:chunk[index.js]chunk[solid-runtime],并据此最终产出两个文件:

  • 入口 index 对应的 index.js 文件;
  • 运行时配置对应的 solid-runtime.js 文件。

在多 entry 场景中,只要为每个 entry 都设定相同的 runtime 值,Webpack 运行时代码就会合并写入到同一个 Runtime Chunk 中,最终达成产物性能优化效果。例如对于如下配置:

module.exports = {entry: {index: { import: "./src/index", runtime: "solid-runtime" },home: { import: "./src/home", runtime: "solid-runtime" },}
};

入口 indexhome 共享相同的 runtime 值,最终生成三个 Chunk,分别为:
请添加图片描述

此时入口 chunk[index]chunk[home] 与运行时 chunk[solid-runtime] 也会形成父子依赖关系。

分包规则的问题

默认分包规则最大的问题是无法解决模块重复,如果多个 Chunk 同时包含同一个 Module,那么这个 Module 会被不受限制地重复打包进这些 Chunk。比如假设我们有两个入口 main/index 同时依赖了同一个模块:
在这里插入图片描述

默认情况下,Webpack 不会对此做额外处理,只是单纯地将 c 模块同时打包进 main/index 两个 Chunk,最终形成:

在这里插入图片描述

可以看到 chunk 间互相孤立,模块 c 被重复打包,对最终产物可能造成不必要的性能损耗!

为了解决这个问题,Webpack 3 引入 CommonChunkPlugin 插件试图将 entry 之间的公共依赖提取成单独的 chunk,但 CommonChunkPlugin 本质上还是基于 Chunk 之间简单的父子关系链实现的,很难推断出提取出的第三个包应该作为 entry 的父 chunk 还是子 chunkCommonChunkPlugin 统一处理为父 chunk,某些情况下反而对性能造成了不小的负面影响。

为此,在 Webpack4 之后才专门引入了更复杂的数据结构 —— ChunkGroup 专门实现关系链管理,配合 SplitChunksPlugin 能够更高效、智能地实现启发式分包。

总结

综上,「构建」阶段负责根据模块的引用关系构建 ModuleGraph;「封装」阶段则负责根据 ModuleGraph 构建一系列 Chunk 对象,并将 Chunk 之间的依赖关系(异步引用、Runtime)组织为 ChunkGraph —— Chunk 依赖关系图对象。与 ModuleGraph 类似,ChunkGraph 结构的引入也能解耦 Chunk 之间依赖关系的管理逻辑,整体架构逻辑更合理更容易扩展。

不过,虽然看着很复杂,但「封装」阶段最重要的目标还是在于:确定有多少个 Chunk,以及每一个 Chunk 中包含哪些 Module —— 这些才是真正影响最终打包结果的关键因素。

针对这一点,我们需要理解 Webpack5 内置的三种分包规则:Entry Chunk、Async Chunk 与 Runtime Chunk,这些是最最原始的分包逻辑,其它插件(例如 splitChunksPlugin)都是在此基础,借助 buildChunkGraph 后触发的各种钩子进一步拆分、合并、优化 Chunk 结构,实现扩展分包效果。

思考 Chunk 一定会且只会生产出一个产物文件吗?为什么?mini-css-extract-pluginfile-loader 这一类能写出额外文件的组件,底层是怎么实现的?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/40252.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【大数据】—美国交通事故分析(2016 年 2 月至 2020 年 12 月)

引言 在当今快速发展的数字时代,大数据已成为我们理解世界、做出决策的重要工具。特别是在交通安全领域,大数据分析能够揭示事故模式、识别风险因素,并帮助制定预防措施,从而挽救生命。本文将深入探讨2016年2月至2020年12月期间&…

24年河南特岗教师招聘流程+报名流程

河南特岗教师报名流程如下 1.登录河南省特岗招聘网 登录河南省特岗招聘网注册账号和密码,账号可以是手机号或者身份证号,密码自己设置 2.注册登录账号 注册完账号重新登录账号,输入身份证号、手机号、密码、验证码 3.浏览考试须知 填写个人信…

Python 编程快速上手——让繁琐工作自动化(第2版)读书笔记01 Python基础快速过关

Python 编程快速上手——让繁琐工作自动化(第2版)读书笔记01 Python基础快速过关 1 python基础概念 Python提供了高效的高级数据结构,还能简单有效地面向对象编程。 python运算符顺序 **——%——//——/——*——-——python中常见的数据…

Real-Time 3D Graphics with WebGL2

WebGL渲染管线 下图是WebGL渲染管线的示意图: Vertex Buffer Objects (VBOs) VBOS中包含了用于描述几何体的信息。如,几何体的顶点坐标,法线坐标,颜色,纹理坐标等。 Index Buffer Objects (IBOs) IBOs中包含了描述顶点关系的信…

C#的多线程UI窗体控件显示方案 - 开源研究系列文章

上次编写了《LUAgent服务器端工具》这个应用,然后里面需要新启动一个线程去对文件进行上传到FTP服务器,但是新线程里无法对应用主线程UI的内容进行更改,所以就需要在线程里设置主UI线程里控件信息的方法,于是就有了此博文。此文记…

Rocky Linux 9 快速安装docker 教程

前述 CentOS 7系统将于2024年06月30日停止维护服务。CentOS官方不再提供CentOS 及后续版本,不再支持新的软件和补丁更新。CentOS用户现有业务随时面临宕机和安全风险,并无法确保及时恢复。由于 CentOS Stream 相对不稳定,刚好在寻找平替系统…

idm 支持断点续传吗 idm 断点续传如何使用 idm断点续传怎么解决 idm下载中断后无法继续下载

断点续传功能,让我再也不会惧怕下载大型文件。在断点续传的帮助下,用户可以随时暂停下载任务,并在空闲时继续之前的下载进程。下载文件不惧网络波动,断点续传让下载过程更稳定。有关 idm 支持断点续传吗,idm 断点续传如…

JavaScript:if-else类型

目录 任务描述 相关知识 if语句 if-else语句 匹配问题 编程要求 任务描述 本关任务:根据成绩判断考试结果。 相关知识 在编程中,我们常常根据变量是否满足某个条件来执行不同的语句。 JavaScript中利用以if关键字开头的条件语句达到以上目的&am…

|从零搭建网络| VisionTransformer网络详解及搭建

🌜|从零搭建网络| VisionTransformer系列网络详解及搭建🌛 文章目录 🌜|从零搭建网络| VisionTransformer系列网络详解及搭建🌛🌜 前言 🌛🌜 VIT模型详解 🌛🌜 VIT模型架…

mybatis、mybatis-plus插件开发,实现数据脱敏功能

首先说一下mybatis中四大组件的作用,下面开发的插件拦截器会使用 四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler Executor: Executor 是 MyBatis 中的执行器,负责 SQL 语句的执行工作。它通过调度 StatementHan…

python基础语法 004-3流程控制- while

1 while while 主要用的场景没有 for 循环多。 while循环&#xff1a;主要运行场景 我不知道什么时候结束。。。不知道运行多少次 1.1 基本用法 # while 4 > 3: #一直执行 # print("hell0")while 4 < 3: #不会打印&#xff0c;什么都没有print("…

IT之旅启航:高考后IT专业预习全攻略

✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的点赞、关注、收藏、评论&#xff0c;是对我最大…

Java知识点大纲

文章目录 第一阶段&#xff1a;JavaSE1、面向对象编程(基础)1)面向过程和面向对象区别2)类和对象的概述3)类的属性和方法4)创建对象内存分析5)构造方法(Construtor)及其重载6)对象类型的参数传递7)this关键字详解8)static关键字详解9)局部代码块、构造代码块和静态代码块10)pac…

2-24 基于图像处理的细胞计数方法

基于图像处理的细胞计数方法。经过初次二值化、中值滤波后二值化、优化后二值化图像、填充背景色的二进制图像、开运算后的图像一系列运算后&#xff0c;进行标签设置&#xff0c;最终得到细胞总数。程序已调通&#xff0c;可直接运行。 2-24 细胞计数方法 中值滤波后二值化 - …

【C++】 解决 C++ 语言报错:Invalid Cast

文章目录 引言 无效类型转换&#xff08;Invalid Cast&#xff09;是 C 编程中常见且严重的错误之一。当程序试图进行不合法或不安全的类型转换时&#xff0c;就会发生无效类型转换错误。这种错误不仅会导致程序崩溃&#xff0c;还可能引发不可预测的行为。本文将深入探讨无效…

图像增强方法汇总OpenCV+python实现【第一部分:常用图像增强方法】

图像增强方法汇总OpenCVpython实现【第一部分】 前言常用的图像增强方法1. 旋转&#xff08;Rotation&#xff09;&#xff1a;2. 平移&#xff08;Translation&#xff09;&#xff1a;3. 缩放&#xff08;Scaling&#xff09;&#xff1a;4. 剪切变换&#xff08;Shear Trans…

UserWarning: IPython History requires SQLite, your history will not be saved

UserWarning: IPython History requires SQLite, your history will not be saved 很久未打开pycharm&#xff0c;控制台出现爆红 解决方法&#xff1a; 重启pycharm&#xff0c;就好啦&#xff01;&#xff01;&#xff01;我猜测可能是上次pycharm没有关闭就电脑关机&…

《企业实战分享 · 内存溢出分析》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;如需交流&#xff…

用PyQt5打造炫酷界面:深入解析pyqt5-custom-widgets

在PyQt5中&#xff0c;使用自定义小部件可以为应用程序增添更多实用性和时尚感。pyqt5-custom-widgets是一个开源项目&#xff0c;提供了一系列有用且时尚的自定义小部件&#xff0c;如开关按钮、动画按钮等。本文将详细介绍pyqt5-custom-widgets的安装和使用方法。 安装 可以…

权限维持Linux---监控功能Strace后门命令自定义Alias后门

免责声明:本文仅做技术交流与学习... 目录 监控功能Strace后门 1、记录 sshd 明文 监控 筛选查看 2、记录sshd私钥 命令自定义Alias后门 1、简单粗鲁实现反弹&#xff1a; 靶机替换命令 攻击机监听上线 2.升级(让命令正常) 将反弹命令进行base64编码 替换alias命令 …