Webpack Chunk 分包规则

webpack 实现中,原始的资源模块以 Module 对象形式存在、流转、解析处理。

而 Chunk 则是输出产物的基本组织单位,在生成阶段 webpack 按规则将 entry 及其它 Module 插入 Chunk 中,之后再由 SplitChunksPlugin 插件根据优化规则与 ChunkGraph 对 Chunk 做一系列的变化、拆解、合并操作,重新组织成一批性能(可能)更高的 Chunks 。运行完毕之后 webpack 继续将 chunk 一一写入物理文件中,完成编译工作。

综上,Module 主要作用在 webpack 编译过程的前半段,解决原始资源“「如何读」”的问题;而 Chunk 对象则主要作用在编译的后半段,解决编译产物“「如何写」”的问题,两者合作搭建起 webpack 搭建主流程。

Chunk 的编排规则非常复杂,涉及 entry、optimization 等诸多配置项,我打算分成两篇文章分别讲解基本分包规则、SplitChunksPlugin 分包优化规则,本文将集中在第一部分,讲解 entry、异步模块、runtime 三条规则的细节与原理。

默认分包规则

在构建(make) 阶段,webpack 从 entry 出发根据模块间的引用关系(require/import) 逐步构建出模块依赖关系图(ModuleDependencyGraph),依赖关系图表达了模块与模块之间互相引用的先后次序,基于这种次序 webpack 就可以推断出模块运行之前需要先执行那些依赖模块,也就可以进一步推断出那些模块应该打包在一起,那些模块可以延后加载(异步执行),关于模块依赖图的更多信息,可以参考我另一篇文章 《有点难的 webpack 知识点:Dependency Graph 深度解析》。

到了生成(seal) 阶段,webpack 会根据模块依赖图的内容组织分包 —— Chunk 对象,默认的分包规则有:

同一个 entry 下触达到的模块组织成一个 chunk
异步模块单独组织为一个 chunk
entry.runtime 单独组织成一个 chunk
默认规则集中在 compilation.seal 函数实现,seal 核心逻辑运行结束后会生成一系列的 Chunk、ChunkGroup、ChunkGraph 对象,后续如 SplitChunksPlugin 插件会在 Chunk 系列对象上做进一步的拆解、优化,最终反映到输出上才会表现出复杂的分包结果。

我们聊聊默认生成规则。

Entry 分包处理
重点:seal 阶段遍历 entry 对象,为每一个 entry 单独生成 chunk,之后再根据模块依赖图将 entry 触达到的所有模块打包进 chunk 中。
在生成阶段,Webpack 首先根据遍历用户提供的 entry 属性值,为每一个 entry 创建 Chunk 对象,比如对于如下配置:

module.exports = {
entry: {
main: “./src/main”,
home: “./src/home”,
}
};
Webpack 遍历 entry 对象属性并创建出 chunk[main] 、chunk[home] 两个对象,此时两个 chunk 分别包含 main 、home 模块:

初始化完毕后,Webpack 会读取 ModuleDependencyGraph 的内容,将 entry 所对应的内容塞入对应的 chunk (发生在 webpack/lib/buildChunkGrap.js 文件)。比如对于如下文件依赖:

main.js 以同步方式直接或间接引用了 a/b/c/d 四个文件,分析 ModuleDependencyGraph 过程会逐步将 a/b/c/d 模块逐步添加到 chunk[main] 中,最终形成:

PS: 基于 entry 生成的 chunk 在 webpack 官方文档中,通常称之为 「Initial chunk」 。

异步模块分包处理
重点:分析 ModuleDependencyGraph 时,每次遇到异步模块都会为之创建单独的 Chunk 对象,单独打包异步模块。
Webpack 4 之后,只需要用异步语句 require.ensure(“./xx.js”) 或 import(“./xx.js”) 方式引入模块,就可以实现模块的动态加载,这种能力本质也是基于 Chunk 实现的。

Webpack 生成阶段中,遇到异步引入语句时会为该模块单独生成一个 chunk 对象,并将其子模块都加入这个 chunk 中。例如对于下面的例子:

// index.js, entry 文件
import ‘sync-a’
import ‘sync-b’

import(‘async-c’)
在 index.js 中,以同步方式引入 sync-a、sync-b;以异步方式引入 async-a 模块;同时,在 · 中以同步方式引入 · 模块。对应的模块依赖如:

此时,webpack 会为入口 index.js、异步模块 async-a.js 分别创建分包,形成如下数据:

这里需要引入一个新的概念 —— Chunk 间的父子关系。由 entry 生成的 Chunk 之间相互孤立,没有必然的前后依赖关系,但异步生成的 Chunk 则不同,引用者(上例 index.js 块)需要在特定场景下使用被引用者(上例 async-a 块),两者间存在单向依赖关系,在 webpack 中称引用者为 parent、被引用者为 child,分别存放在 ChunkGroup._parents 、ChunkGroup._children 属性中。

上述分包方案默认情况下会生成两个文件:

入口 index 对应的 index.js
异步模块 async-a 对应的 src_async-a_js.js
运行时,webpack 在 index.js 中使用 promise 及 webpack_require.e 方法异步载入并运行文件 src_async-a_js.js ,从而实现动态加载。

PS: 基于异步模块的 chunk 在 webpack 官方文档中,通常称之为 「Async chunk」 。

Runtime 分包
重点: Webpack 5 之后还能根据 entry.runtime 配置单独打包运行时代码。
除了 entry、异步模块外,webpack 5之后还支持基于 runtime 的分包规则。除业务代码外,Webpack 编译产物中还需要包含一些用于支持 webpack 模块化、异步加载等特性的支撑性代码,这类代码在 webpack 中被统称为 runtime。举个例子,产物中通常会包含如下代码:

// (() => {
// webpackBootstrap
/
/ var webpack_modules = {}; // The module cache
/******************************************************/
/
/ /
/ var webpack_module_cache = {}; // The require function
/
/

// // function webpack_require(moduleId) {

/******/ /******/ __webpack_modules__[moduleId](module,module.exports,__webpack_require__
); // Return the exports of the module
/******//******/ /******/ return module.exports;
/******/

} // expose the modules object (webpack_modules)
/******/

// // webpack_require.m = webpack_modules; /* webpack/runtime/compat get default export /
/
*****/

// …
})();
编译时,Webpack 会根据业务代码决定输出那些支撑特性的运行时代码(基于 Dependency 子类),例如:

需要 webpack_require.f、webpack_require.r 等功能实现最起码的模块化支持
如果用到动态加载特性,则需要写入 webpack_require.e 函数
如果用到 Module Federation 特性,则需要写入 webpack_require.o 函数
等等
虽然每段运行时代码可能都很小,但随着特性的增加,最终结果会越来越大,特别对于多 entry 应用,在每个入口都重复打包一份相似的运行时代码显得有点浪费,为此 webpack 5 专门提供了 entry.runtime 配置项用于声明如何打包运行时代码。用法上只需在 entry 项中增加字符串形式的 runtime 值,例如:

module.exports = {
entry: {
index: { import: “./src/index”, runtime: “solid-runtime” },
}
};
Webpack 执行完 entry、异步模块分包后,开始遍历 entry 配置判断是否带有 runtime 属性,如果有则创建以 runtime 值为名的 Chunk,因此,上例配置将生成两个chunk:chunk[index.js] 、chunk[solid-runtime],并据此最终产出两个文件:

入口 index 对应的 index.js 文件
运行时配置对应的 solid-runtime.js 文件
在多 entry 场景中,只要为每个 entry 都设定相同的 runtime 值,webpack 运行时代码最终就会集中写入到同一个 chunk,例如对于如下配置:

module.exports = {
entry: {
index: { import: “./src/index”, runtime: “solid-runtime” },
home: { import: “./src/home”, runtime: “solid-runtime” },
}
};
入口 index、home 共享相同的 runtime ,最终生成三个 chunk,分别为:

同时生成三个文件:

入口 index 对应的 index.js
入口 index 对应的 home.js
运行时代码对应的 solid-runtime.js
分包规则的问题
至此,webpack 分包规则的基本逻辑就介绍完毕了,实现上,大部分功能代码都集中在:

webpack/lib/compilation.js 文件的 seal 函数
webpack/lib/buildChunkGraph.js 的 buildChunkGraph 函数
默认分包规则最大的问题是无法解决模块重复,如果多个 chunk 同时包含同一个 module,那么这个 module 会被不受限制地重复打包进这些 chunk。比如假设我们有两个入口 main/index 同时依赖了同一个模块:

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

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

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

在 webpack 4 之后则引入了更负责的设计 —— ChunkGroup 专门实现关系链管理,配合 SplitChunksPlugin 能够更高效、智能地实现「启发式分包」

Webpack Chunk 分包规则相关推荐

  1. 前端工程化之Webpack优化

    你能所学到的知识点 1.Webpack Loader 和 Plugin 的区别 2.Webpack 生命周期 3.Webpack编译阶段提效 1.减少执行编译的模块 2.提升单个模块构建的速度 3.并 ...

  2. webpack原理解析【万字长文】

    开场白 大家好,我是Webpack,AKA打包老炮,我的slogan是:"打天下的包,让Rollup无包可打". 今天我要带来的才艺是:剖析打包的艺术  故事还要从一次npm包工头 ...

  3. webpack 打包(plugin、loader 工作原理)

    模块化工具 由来 ES Modules 存在环境兼容问题 模块文件过多,网络请求频繁 所有的前端资源都需要模块化 概要 Webpack 作为 模块打包器(Module bundler),可以把零散的文 ...

  4. webpack打包 - webpack篇

    @TOC# 什么是 webpack? 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler).当 webpack 处理应用程序时,它会递归地 ...

  5. 从零开始使用webpack 搭建vue项目

    从零开始使用webpack 搭建vue项目 1 创建项目 npm init 生成 package.json 创建 index.html webpack.confug.js project-name|- ...

  6. webpack系列之优化策略

    1.打包速度优化 1.1.速度监控插件-speedMeasureWebpackPlugin speed-measure-webpack-plugin是一款能检测Loader和Plugin耗时的插件,具 ...

  7. webpack系列 —— 打包原理

    为什么要使用webpack? 如今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包.为了简化开发的复杂度,前端社区涌现出了很多好的实践方法: 模块化,让我 ...

  8. 如何征服Webpack 4并构建一个出色的React应用

    This article has been outdated with the new release for babel, kindly check the updated article &quo ...

  9. dex文件结构(三):dex文件差分包分成

    作者:hackest 链接:https://www.jianshu.com/p/5a2e33a61ba2 当程序越来越大之后,出现了一个 dex 包装不下的情况,通过 MultiDex 的方法解决了这 ...

  10. Webpack 打包 Javascript 详细介绍

    本篇我们主要介绍Webpack打包Javascript.当然,除了可以打包Javascript之外,webpack还可以打包html.但是这不是我们本篇的重点.我们可以参考 Webpack HTML ...

最新文章

  1. sqlalchemy.exc.DataError: (pymysql.err.DataError) (1366, “Incorrect string value: ‘\\xE6问题解决
  2. 特征工程(二) :文本数据的展开、过滤和分块
  3. pytorch方法测试——卷积(二维)
  4. linux中yum命令详解
  5. ios键盘done中文_IOS_IOS关闭键盘的方法,首先输入完成后按键盘上的done - phpStudy...
  6. CLOUD信用管理设置
  7. mysql实践小结,【java_mysql】实践小结
  8. 电信用户遭重!iPhone 13全系列不再支持中国电信2G/3G网络
  9. 安卓 camera 调用流程_音视频开发之旅(四)Camera视频采集
  10. 计算机组成原理中wr是什么,计算机组成原理第8章习题指导.pdf
  11. drm android 工作原理,[原创]某DRM解密流程简单分析
  12. LayUI之动态选项卡Tabiframe使用
  13. 20sccm_SCCM安装及配置过程总结
  14. 【自动化】手把手教你一个1秒钟归纳整理海量文件的python小技巧
  15. 数据挖掘与机器学习——离群点检测之孤立森林(isolate forest)
  16. Android音乐播放器开发(3)—注册
  17. 三大方案提升企业安全人才能力 永信至诚将企业网络安全教育推向云端
  18. popen和system函数的区别 以及 popen打开的FILE指针能否用close替代fclose关闭
  19. 《权威指南》笔记 - 8.7 函数属性、方法、构造函数
  20. 北航计算机考研复试时间,北京航空航天大学计算机学院2019年硕士研究生复试安排...

热门文章

  1. 使用Ambari快速部署Hadoop大数据环境
  2. C++核心准则边译边学-不必惊慌!
  3. C语言会员卡计费系统
  4. 我的世界Java版最诡异的种子_我的世界:比666还诡异的故障种子,无限复制结构?官方:还是特性...
  5. 为什么手机有卡却突然显示无服务器,手机突然显示“无服务”,原因是什么?...
  6. Tracy JS 小笔记 - 数据结构 栈,队列,链表,字典,集合,哈希表(散列表)
  7. Element-Ui组件 单选框(Radio) 修改点击激活时的文本颜色,填充色和边框色
  8. openresty-lua代码的语法检查
  9. 公众号第三方平台开发 创建公众号第三方平台
  10. Credential Harvester Attack Method获得用户信息