[译]webpack 4: Code Splitting和chunks切分优化

5,794 阅读5分钟

原文地址: webpack 4: Code Splitting, chunk graph and the splitChunks optimization
原文作者: Tobias Koppers
译者: arzh
推荐理由: 从webpack3升级到webpack4所做的重要更改

webpack4chunk图进行了一些重大改进,并为chunk拆分添加了一个新的优化(这是对CommonsChunkPlugin的一种改进)

让我们来看看旧版关系图的一些缺点

在旧图中,chunks通过父子关系和chunks包含模块来连接其他的chunks

当一个chunk有多个父节点的话,我们可以认为当这个chunk被加载的时候,至少有一个父节点已经被加载了.这个信息可以被用来优化,例如,当一个chunk的模块,在所有父节点都可用时,它可以从chunk中被移除,因为它一定已经被成功加载了。

在入口点或异步拆分点处引用chunk列表时,这些chunks会并行加载。

这种类型的关系图使得分离chunks变得十分困难,例如使用CommonsChunkPlugin会有这样的问题,当你删除一个或多个chunks模块,并将它们放在一个新的chunk中,这个新的chunk需要被重新连接到关系图中,但是如何连接呢?作为旧的chunk的父级,还是子级?CommonsChunkPlugin将其添加为父级,但这在技术上是错误的,同时也会对其他优化产生负面影响(父节点信息是不准确的)

新的chunk图中引入了一个新对象: ChunkGroup,一个ChunkGroup包含多个chunk

在入口点或异步拆分点处引用单个ChunkGroup,这意味着所有被ChunkGroup包含的chunk都是并行加载的。一个chunk可以被引用在多个ChunkGroup中(但不会被加载多次)

现在就不再使用父子关系来关联chunk,而是通过ChunkGroup来进行关联

现在chunks的分割可以被理解了,新增加的chunks会被添加到所有包含原始chunkChunkGroups 中,这并不会对父层级关系产生负面影响。

现在这个问题被修复了,我们就可以开始更多的使用chunk分离了,我们可以任意拆分chunk而不用担心chunk图会被破坏

CommonsChunkPlugin有许多的问题:

  1. 会下载一些我们所不需要的代码
  2. 在异步chunks下是低效率的
  3. 会比较难使用
  4. 实践起来比较难以理解

所以新的插件诞生了:SplitChunksPlugin

它能通过heuristics自动识别应该被分块的模块,使用模块重复计数和模块类别(如node_modules),来分割chunks

这里有一种两者的比喻。CommonsChunkPlugin就像:"创建一个chunk并将匹配minChunks的所有模块移动到新块中",SplitChunksPlugin就像:"这是heuristics,确保你满足他们"(Here are the heuristics, make sure you fullfil them)(命令式与声明式的区别)

SplitChunksPlugin也有一些很棒的属性:

  1. 它永远不会下载不需要的模块(只要你不通过名称强制执行chunk合并)
  2. 在异步chunks下也是高效的
  3. 默认为异步chunks模式分割
  4. 更加容易使用
  5. 不依赖于chunk
  6. 大多是自动化配置的

以下是SplitChunksPlugin为你列举的几个例子。这些示例仅显示默认行为,附加配置有更多个性化的选择

注意:你可以通过optimization.splitChunks进行配置。这些示例说明了一些关于chunk的内容,默认情况下它只适用于异步chunk,但是使用optimization.splitChunks.chunks:"all"来配置,适用于所有类型的文件(同步、异步chunk

注意:我们假设此处使用的每个外部库都大于30kb,因为代码优化仅在超出此大小之后才能生效。

Vendors

chunk-a: react, react-dom, some components
chunk-b: react, react-dom, some other components
chunk-c: angular, some components
chunk-d: angular, some other components

webpack将会自动创建两个vendors chunks,结果如下:

vendors~chunk-a~chunk-b: react, react-dom
vendors~chunk-c~chunk-d: angular
chunk-a to chunk-d: Only the components

Vendors重叠

chunk-a: react, react-dom, some components
chunk-b: react, react-dom, lodash, some other components
chunk-c: react, react-dom, lodash, some components

webpack也会创建两个vendors chunks,结果如下:

vendors~chunk-a~chunk-b~chunk-c: react, react-dom
vendors~chunk-b~chunk-c:lodash
chunk-a to chunk-c: Only the components

共享模块

chunk-a: vue, some components, some shared components
chunk-b: vue, some other components, some shared components
chunk-c: vue, some more components, some shared components

假设共享组件的大小大于30kbwebpack将创建一个vendors chunk和一个commons chunk,结果如下:

vendors~chunk-a~chunk-b~chunk-c: vue
commons~chunk-a~chunk-b~chunk-c: some shared components
chunk-a to chunk-c: Only the components

当这些shared components体积小于30kb时,webpack会特意将该模块复制到chunk-a chunk-b chunk-c三个文件中。他们认为进行分离所减小的加载体积的整体效果并不如一次额外的加载请求的消耗。

多个共享模块

chunk-a: react, react-dom, some components, some shared react components
chunk-b: react,react-dom, angular, some other components
chunk-c: react,react-dom, angular, some components, some shared react components, some shared angular components
chunk-d: angular, some other components, some shared angular components

webpack将创建两个vendors chunk和两个commons chunk,结果如下:

vendors~chunk-a~chunk-b~chunk-c: react, react-dom
vendors~chunk-b~chunk-c~chunk-d: angular
commons~chunk-a~chunk-c: some shared react components
commons~chunk-c~chunk-d: some shared angular components
chunk-a to chunk-d: Only the components

注意:由于chunk名称包括所有原始chunk名称,因此建议使用长期缓存的生产版本不包括文件名中的[name],或通过optimization.splitChunks.name:false关闭名称生成.这样在后续开发中添加了新的引用,也不会变更文件名。

对本文有任何优化建议,可扫描下述二维码一起讨论,同时也希望大家多多关注,会不定期发送一些原创文章