关于前端:Webpack-系列第四篇Chunk-分包规则详解

30次阅读

共计 5318 个字符,预计需要花费 14 分钟才能阅读完成。

全文 2500 字,浏览时长约 30 分钟。如果感觉文章有用,欢送点赞关注,但写作实属不易,未经作者批准,禁止任何模式转载!!!

背景

在后面系列文章提到,webpack 实现中,原始的资源模块以 Module 对象模式存在、流转、解析解决。

Chunk 则是输入产物的根本组织单位,在生成阶段 webpack 按规定将 entry 及其它 Module 插入 Chunk 中,之后再由 SplitChunksPlugin 插件依据优化规定与 ChunkGraphChunk 做一系列的变动、拆解、合并操作,从新组织成一批性能 (可能) 更高的 Chunks。运行结束之后 webpack 持续将 chunk 一一写入物理文件中,实现编译工作。

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

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

关注公众号【Tecvan】,回复【1】,获取 Webpack 常识体系脑图

默认分包规定

Webpack 4 之后编译过程大抵上能够拆解为四个阶段(参考:[万字总结] 一文吃透 Webpack 外围原理):

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

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

  • 同一个 entry 下触达到的模块组织成一个 chunk
  • 异步模块独自组织为一个 chunk
  • entry.runtime 独自组织成一个 chunk

默认规定集中在 compilation.seal 函数实现,seal 外围逻辑运行完结后会生成一系列的 ChunkChunkGroupChunkGraph 对象,后续如 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 别离蕴含 mainhome 模块:

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

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

PS: 基于动静加载生成的 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-async-b;以异步形式引入 async-a 模块;同时,在 · 中以同步形式引入 · 模块。对应的模块依赖如:

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

这里须要引入一个新的概念 —— Chunk 间的父子关系。由 entry 生成的 Chunk 之间互相孤立,没有必然的前后依赖关系,但异步生成的 Chunk 则不同,援用者 (上例 index.js 块) 须要在特定场景下应用被援用者(上例 async-a 块),两者间存在单向依赖关系,在 webpack 中称援用者为 parent、被援用者为 child,别离寄存在 ChunkGroup._parentsChunkGroup._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.jsbuildChunkGraph 函数

默认分包规定最大的问题是无奈解决模块反复,如果多个 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 可能更高效、智能地实现 启发式分包,这里的内容很简单,我打算拆开来在下一篇文章再讲,感兴趣的同学记得关注。

下节预报

前面我还会持续 focus 在 chunk 相干性能与外围实现原理,内容包含:

  • webpack 4 之后引入 ChunkGroup 的引入解决了什么问题,为什么能极大优化分包性能
  • webpack 5 引入的 ChunkGraph 解决了什么问题
  • Chunk、ChunkGroup、ChunkGraph 别离实现什么能力,相互之间如何合作,为什么要做这样的拆分
  • SplitChunksPlugin 插件做了那些分包优化,以及咱们能够从中学到什么插件开发技巧
  • 站在利用、性能的角度,有那些分包最佳实际

感兴趣的同学肯定要记得点赞关注,您的反馈将是我继续创作的微小能源!

往期文章:

  • [万字总结] 一文吃透 Webpack 外围原理
  • [源码解读] Webpack 插件架构深度解说
  • 十分钟精进 Webpack:module.issuer 属性详解
  • 分享几个 Webpack 实用剖析工具
  • 有点难的 webpack 知识点:Dependency Graph 深度解析

正文完
 0