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

news2024/10/5 21:18:29

概述

  • 在前文 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 与 ChunkGroup
      connectChunkGroupAndChunk(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 方法,开始构建 ChunkGraph
    buildChunkGraph(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.coloradmin.cn/o/1891606.html

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

相关文章

全面解析自然语言处理(NLP):基础、挑战及应用前景

自然语言处理 (NLP) 简介与应用前景 自然语言处理(NLP)是人工智能和计算语言学的一个分支,致力于使计算机能够理解、解释和生成人类语言。这篇博文将深入探讨自然语言处理的基础知识、挑战、典型任务及其广泛的应用前景。 一、自然语言处理的…

路由的基本使用

1.安装 npm i vue-router3 2.引入 import VueRouter from vue-router 3.使用 Vue.use(VueRouter) 4.在src目录下创建router 5.创建两个组件 5.1创建About组件 <template><div> <h1>我是About的内容</h1></div> </template><script> …

计算机操作系统部分选填及大题整理

并发和&#xff08; 共享 &#xff09; 是操作系统的两个最基本的特征,&#xff08; 虚拟 &#xff09;和&#xff08; 异步 &#xff09; 是操作系统的重要特征&#xff0c;并发执行的程序失去可再现性现代操作系统的两个基本特征是&#xff08;程序的并发执行&#xff09;和资…

AC7801时钟配置流程

一 默认配置 在启动文件中&#xff0c;已经对时钟进行了初始化&#xff0c;默认按外部8M晶振&#xff0c;配置系统时钟为48MHZ&#xff0c;APB为系统时钟的2分频&#xff0c;为24MHZ。在system_ac780x.c文件中&#xff0c;可以找到下面这个系统初始化函数&#xff0c;里面有Se…

layui-表格

1.使用方法 加上table标签 加上classlayui-table colgroup是列属性 tr是行td是列 thead是表头&#xff0c;后面一一对应 2.基础属性 加lay-even逐行换色 加lay-skin 设置边框风格

windows上安装Frida环境

python安装 下载地址 Python Release Python 3.12.4 | Python.org python安装好后&#xff0c;使用如下命令安装frida客户端 pip install frida-tools 使用frida --version 查看frida版本 安装手机模拟器&#xff08;雷电模拟器&#xff09; 我的版本是4.0.61 查看CPU架构 adb …

昇思25天学习打卡营第15天 | Vision Transformer图像分类

内容介绍&#xff1a; 近些年&#xff0c;随着基于自注意&#xff08;Self-Attention&#xff09;结构的模型的发展&#xff0c;特别是Transformer模型的提出&#xff0c;极大地促进了自然语言处理模型的发展。由于Transformers的计算效率和可扩展性&#xff0c;它已经能够训练…

Cmake静态库与动态库的构建与使用

项目目录 各个文件 myhell.h // // Created by glt on 2024/7/3. //#ifndef MY_PRO_HELLO_H #define MY_PRO_HELLO_H#include <iostream>void HelloFunc();#endif //MY_PRO_HELLO_Hhello.cpp // // Created by glt on 2024/7/3. // #include "myhello.h"voi…

postman请求访问:认证失败,无法访问系统资源

1、使用postman时&#xff0c;没有传入相应的token&#xff0c;就会出现这种情况&#xff0c;此时需要把token放进去 发现问题: { "msg": "请求访问&#xff1a;/getInfo&#xff0c;认证失败&#xff0c;无法访问系统资源", "code": 401 } 1…

#数据结构 笔记一

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构是带有结构特性的数据元素的集合&#xff0c;它研究的是数据的逻辑结构和物理结构以及它们之间的相互关系&#xff0c;并对这种结构定义相适应的运算&#xff0c;…

Harbor简易安装

1.下载tgz 2.解压 3.修改配置文件 配置文件内容如下&#xff1a;简单粘个自己去改 4.harbor中执行安装 5.命令 启动&#xff1a; docker compose -f docker-compose.yml up -d关闭&#xff1a; docker compose -f docker-compose.yml stop 6.访问harbor 访问地址&#xff1a…

C语言 | Leetcode C语言题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; char* shortestPalindrome(char* s) {int n strlen(s);int fail[n 1];memset(fail, -1, sizeof(fail));for (int i 1; i < n; i) {int j fail[i - 1];while (j ! -1 && s[j 1] ! s[i]) {j fail[j];}if (s[j 1] s[i]) {f…

农村生活污水处理监测系统解决方案

一、概述 随着国民经济的发展和农村生活水平的提高&#xff0c;农村生活用水量越来越大&#xff0c;随之而来的污水产量也越来越大&#xff0c;农村生活污染对环境的压力越来越明显。环境保护意识的逐渐增强&#xff0c;使得人们对青山绿水的希望更为迫切&#xff0c;为满足人民…

nginx修改网站默认根目录及发布(linux、centos、ubuntu)openEuler软件源repo站点

目录 安装nginx配置nginx其它权限配置 安装nginx dnf install -y nginx配置nginx whereis nginxcd /etc/nginx llcd conf.d touch vhost.conf vim vhost.conf 命令模式下输入:set nu或:set number可以显示行号 复制如下内容&#xff1a; server {listen 80;server_name…

Python | 基于支持向量机(SVM)的图像分类案例

支持向量机&#xff08;SVM&#xff09;是一种监督机器学习算法&#xff0c;可用于分类和回归任务。在本文中&#xff0c;我们将重点关注使用SVM进行图像分类。 当计算机处理图像时&#xff0c;它将其视为二维像素阵列。数组的大小对应于图像的分辨率&#xff0c;例如&#xf…

Java项目:基于SSM框架实现的游戏攻略网站系统分前后台【ssm+B/S架构+源码+数据库+毕业论文+任务书】

一、项目简介 本项目是一套基于SSM框架实现的游戏攻略网站系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…

网络基础:OSPF 协议

OSPF&#xff08;Open Shortest Path First&#xff09;是一种广泛使用的链路状态路由协议&#xff0c;用于IP网络中的内部网关协议&#xff08;IGP&#xff09;。OSPF通过在网络中的所有路由器之间交换路由信息&#xff0c;选择从源到目的地的最优路径。OSPF工作在OSI模型的第…

HTTP入门

入门HTTP协议 1. 原理介绍 爬虫就是用程序模拟浏览器的行为&#xff0c;发送请求给服务器&#xff0c;获取网页的内容&#xff0c;解析网页数据。 要学会爬虫&#xff0c;先要了解浏览器是如何和服务器交流的。浏览器通过HTTP协议和服务器交流。 2. HTTP协议简介 2.1…

js三元图的画法图标是圆点

<div id"Plot" style"width:80%; margin-left:14%; height: calc(100% - 80px);"></div>var data {title: [甲苯, 乙苯, 苯],data: [{name: 工业过程/溶剂使用,mode: markers,a: [0.1, 0.2, 0.4, 0.6, 0.8],b: [0.6, 0.3, 0.4, 0.2, 0.1],c: …

线程池概念的详解

前言&#x1f440;~ 上一章我们介绍了什么是定时器以及如何去实现一个定时器&#xff0c;今天我们来讲解在多线程中同样很重要的一个内容线程池 线程池的出现 线程池概念 标准库中的线程池 工厂模式 newCacheThreadPool方法 newFixedThreadPool方法 ThreadPoolExecutor…