webpack之splitChunks
Webpack 默认会将以下三种模块做分包处理:
- Initial Chunk:entry 模块及相应子模块打包成 Initial Chunk,比如文件里的
import...from...
; - Async Chunk:通过
import('./xx')
等语句导入的异步模块及相应子模块组成的 Async Chunk; - Runtime Chunk:运行时代码抽离成 Runtime Chunk,可通过 entry.runtime 配置项实现,entry.runtime 不为空时,会将运行时模块单独组织成一个 Chunk。
但 Initial Chunk 与 Async Chunk 这种略显粗暴的规则会带来两个明显问题:
模块重复打包
假如多个 Chunk 同时依赖同一个 Module,那么这个 Module 会被不受限制地重复打包进这些 Chunk.
示例中 main/index 入口(entry)同时依赖于 c 模块,默认情况下 Webpack 不会对此做任何优化处理,只是单纯地将 c 模块同时打包进 main/index 两个 Chunk:
资源冗余 & 低效缓存
Webpack 会将 Entry 模块、异步模块所有代码都打进同一个单独的包,这在小型项目通常不会有明显的性能问题,但伴随着项目的推进,包体积逐步增长可能会导致应用的响应耗时越来越长。归根结底这种将所有资源打包成一个文件的方式存在两个弊端:
- 资源冗余:客户端必须等待整个应用的代码包都加载完毕才能启动运行,但可能用户当下访问的内容只需要使用其中一部分代码
- 缓存失效(cdn):将所有资源达成一个包后,所有改动 —— 即使只是修改了一个字符,hash变了,资源名称变了,客户端都需要重新下载整个代码包,缓存命中率极低
这两个问题都可以通过更科学的分包策略解决,例如:
- 将被多个 Chunk 依赖的包分离成独立 Chunk,防止资源重复;
- node_modules 中的资源通常变动较少,可以抽成一个独立的包,业务代码的频繁变动不会导致这部分第三方库资源缓存失效,被无意义地重复加载。 为此,Webpack 专门提供了 SplitChunksPlugin 插件,用于实现更灵活、可配置的分包,提升应用性能。
最佳分包策略
- 针对 node_modules 资源:
- 可以将 node_modules 模块打包成单独文件(通过 cacheGroups 实现),防止业务代码的变更影响 NPM 包缓存,同时建议通过 maxSize 设定阈值,防止 vendor 包体过大;
- 更激进的,如果生产环境已经部署 HTTP2/3 一类高性能网络协议,甚至可以考虑将每一个 NPM 包都打包成单独文件。
- 针对业务代码:
- 设置 common 分组,通过 minChunks 配置项将使用率较高的资源合并为 Common 资源;
- 首屏用不上的代码,尽量以异步方式引入;
- 设置 optimization.runtimeChunk 为 true,将运行时代码拆分为独立资源。