webpack 4: 代码拆分、代码块关系图及优化插件splitChunks

2,653 阅读5分钟

webpack4 对 chunk graph(代码块关系网格) 进行了一些重大改进并用新的优化策略实现了 chunk spliting(对CommonsChunkPlugin的一种改进)。

我们看下老 chunk graph 的一些缺点。

在老的 chunk graph 中,chunks 通过父子关系来连接且 chunks 包含了 moudles 的。

当一个拥有父块的 chunk 被加载时,就可以确定它至少有一个父级已经加载完了。优化策略就是使用了这个原理。即当 chunk 的父块同时拥有同一个 module 时,这个 module 就可以从此 chunk 移除,因为它在任何情况下都已经可用了。

入口点(entry)或异步拆分点(async import)引用了的 chunk 是并行加载的。

这种关系使得实现“分割” chunks 变得困难。例如使用 CommonsChunkPlugin 时就会发生这种情况。一个或多个 chunks 模块被移除并放进一个新的 chunk,这个 chunk 需要被连接到 chunk graph 中。但要怎么做呢?当做老 chunk 的父块?还是子块?CommonsChunkPlugin 把它作为父块,但这在技术上是错误的,会对其他优化产生负面影响(父块信息不准确)。

新的 chunk graph 引入了一个新对象:ChunkGroupChunkGroup 包含了 Chunks

在一个入口点或异步拆分点引用了一个 ChunkGroup,这意味着此组包含的所有 Chunks 都是并行的。一个 Chunk 可以被多个 CHunkGroups 引用。

Chunk 之间不再存在父子关系,而存在于 ChunkGroups 之间的关系中。

现在 Chunks 的“分割”可以实现了。分割出的新 Chunks 被添加到所有包含原 ChunkChunkGroups,这不会对父子关系产生负面影响。


现在解决了这个问题,我们可以开始更多地使用 chunk 分割了。我们可以不冒破坏 chunk graph 的风险地拆分任何 chunk

CommonsChunkPlugin 还有许多问题:

  • 会导致下载多余的无用代码。
  • 在异步 chunks 上效率低。
  • 用起来繁琐。
  • 实现难以理解。

所有新的插件诞生了:SplitChunksPlugin

它使用 module 重复次数和类别(即node_modules)作为依据,实现自动识别应该怎样拆分 chunks

这是一种范式转移。CommonsChunkPlugin 就像是:“快给我加一个新的 chunck 然后把所有匹配 minChunks 规则的模块移进去”。SplitChunksPlugin 则像是:“这里有一份图纸,想办法实现它们”。(命令式 vs 声明式)

SplitChunksPlugin 还有一些别的好特性:

  • 从不下载多余的模块(只要你不通过命名(name)强制(enforce)执行块合并)
  • 异步 chunks 仍然高效
  • 默认情况下,异步 chunks 就是打开的
  • 当有多个第三方类库时,会自行拆分这些 chunks
  • 容易上手
  • chunk graph 不依赖 hacks 手段
  • 更自动化

这里有一些 SpitChunksPlugin 可以为你做些什么的例子。这些例子只展示默认行为。自定义配置会有更多的结果。

注意: 你可以通过 optimiztion.splitChunks 对其进行配置。例子中关于 chunks 的,默认情况下只在异步块(async chunks)中起作用,不过配置 optimiztion.splitChunks.chunks: "all" 可以使直块(initial chunks)也其作用。

注意:我们假设这使用的所有第三方库都超过30kb,因为优化只会大于此大小后发生。

Vendors

chunk-a: react, react-dom, 一些组件 chunk-b: react, react-dom, 一些其他组件 chunk-c: angular, 一些组件 chunk-d: angular, 一些其他组件

webpack会自动添加两个 vendors chunks,像下面这样:

vendors~chunk-a~chunk-b: react,react-dom vendors~chunk-c~chunk-d: angular chunk-achunk-d: 只有组件

交叉 Vendors

chunk-a: react, react-dom, 一些组件

chunk-b: react, react-dom,lodash, 一些其他组件

chunk-c: react,react-dom.lodash 一些组件

同样的,webpack会自动添加两个 vendors chunks,像下面这样:

vendors~chunk-a~chunk-b: react,react-dom

vendors~chunk-c~chunk-d: lodash

chunk-achunk-c: 只有组件

模块共享

chunk-a: vue, 一些组件, 一些共享组件

chunk-b: vue, 一些其他组件, 一些共享组件

chunk-c: vue, 一些组件, 一些共享组件

假设共享组件的大小大于30kb,webpack 会添加一个新 vendors chunk 和 一个 commons chunk, 就像这样:

vendors~chunk-a~chunk-b~chunk-c: vue

commons~chunk-a~chunk-b~chunk-c: 一些共享组件

chunk-achunk-c: 只有组件

当共享组件大小小于30kb,webpack 会特意将chunk-a 中的模块复制到 chunk-b 中,我们认为减少的下载大小不值得为此单独加载模块而发起额外请求。

多个模块共享

chunk-a: react, react-dom, 一些组件,一些共享的react组件

chunk-b: react, react-dom, angular, 一些其他组件

chunk-c: react,react-dom,angular, 一些组件, 一些共享的react组件,一些共享的angular组件

chunk-d: angular, 一些其他组件, 一些共享的angular组件

webpack 添加两个 vendors chunks 和两个 commons chunks

vendors~chunk-a~chunk-b~chunk-c: react,react-dom

vendors~chunk-b~chunk-c~chunk-d: angular

commons~chunk-a~chunk-c: 一些react共享组件

commons~chunk-c~chunk-d: 一些angular共享组件

chunk-achunk-d: 只有组件


注意:由于 chunk 名称由所有 chunk 来源名称组合而成,因此建议在长期缓存的生成环境中,文件名应该含有[name],或者通过 optimization.splitChunks.anme:false 关闭创建名称。否则在添加更多具有相同 vendorschunks 时,先前的文件会失效。

翻译原文地址:webpack 4: Code Splitting, chunk graph and the splitChunks optimization | 作者:Tobias Koppers