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
引入了一个新对象:ChunkGroup
。ChunkGroup
包含了 Chunks
。
在一个入口点或异步拆分点引用了一个 ChunkGroup
,这意味着此组包含的所有 Chunks
都是并行的。一个 Chunk
可以被多个 CHunkGroups
引用。
Chunk
之间不再存在父子关系,而存在于 ChunkGroups
之间的关系中。
现在 Chunks
的“分割”可以实现了。分割出的新 Chunks
被添加到所有包含原 Chunk
的 ChunkGroups
,这不会对父子关系产生负面影响。
现在解决了这个问题,我们可以开始更多地使用 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-a
至 chunk-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-a
至 chunk-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-a
至 chunk-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-a
至 chunk-d
: 只有组件
注意:由于 chunk
名称由所有 chunk
来源名称组合而成,因此建议在长期缓存的生成环境中,文件名不应该含有[name],或者通过 optimization.splitChunks.anme:false
关闭创建名称。否则在添加更多具有相同 vendors
的 chunks
时,先前的文件会失效。
翻译原文地址:webpack 4: Code Splitting, chunk graph and the splitChunks optimization | 作者:Tobias Koppers