尧图精选

webpack 多页面构建中的自动去重与代码拆分:深入解析 `optimization.splitChunks`(以 many-pages 示例为例)

🕒 发布时间:2026/9/8 22:38:52 📁 来源:尧图网络
webpack 多页面构建中的自动去重与代码拆分深入解析optimization.splitChunks以 many-pages 示例为例【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack本篇技术指南围绕 webpack 仓库自带的examples/many-pages示例展开它演示了 webpack 在多页面MPA场景下基于optimization.splitChunks的自动去重automatic deduplication算法当多个页面入口共享相同的 vendor 依赖或应用模块时如何自动把它们抽取为独立 chunk从而避免同一份代码在每个页面产物中被重复打包。读完本文你将理解splitChunks的核心配置项及其默认值、共享模块被合并或故意重复的判定逻辑minSize阈值以及 HTTP/1.1 与 HTTP/2 下请求数与重复度之间的权衡并能直接复现这一示例观察真实产物。示例概览7 个页面 × 2 类模块examples/many-pages是一个刻意简化的多页面应用目录结构如下examples/many-pages/ ├── pages/ # 7 个页面入口 a.js ~ g.js ├── stuff/ # 8 个应用自有模块 s1.js ~ s8.js ├── webpack.config.js └── README.md / template.md每个页面入口通过 ES Moduleimport引入 13 个来自node_modules的 vendor 库本示例中为m1m8以及 03 个来自stuff目录的应用模块。README 明确指出真实应用会比这复杂得多但底层机制完全相同。以 pages/a.js 为例import m1; import m2; import m3; import ../stuff/s2; import ../stuff/s3; import ../stuff/s4;综合 pages/ 下全部入口各页面的依赖矩阵如下页面入口node_modulesvendorstuff应用模块pageAm1, m2, m3s2, s3, s4pageBm1, m2, m4s1, s7, s8pageCm1, m2, m5s4, s5, s6pageDm6, m7, m8s1, s2, s3pageEm6, m7, m8s7pageFm6, m7, m8s1, s2, s3pageGm6s1可以看到m1/m2被 pageA/B/C 共享m6被 pageD/E/F/G 共享m7/m8被 pageD/E/F 共享s1同时出现在 pageB/D/F/Gs2/s3同时出现在 pageA/D/Fs4同时出现在 pageA/C。这种多对多的共享关系正是测试拆分算法的最佳样本。配置文件逐项拆解webpack.config.js 是示例的完整配置use strict; /** type {import(webpack).Configuration} */ const config { // mode: development || production, entry: { pageA: ./pages/a, pageB: ./pages/b, pageC: ./pages/c, pageD: ./pages/d, pageE: ./pages/e, pageF: ./pages/f, pageG: ./pages/g }, optimization: { splitChunks: { chunks: all, maxInitialRequests: 20, // for HTTP2 maxAsyncRequests: 20, // for HTTP2 minSize: 40 // for example only: chosen to match 2 modules // omit minSize in real use case to use the default of 30kb } } }; module.exports config;七个入口全部声明在entry中这构成一个典型的 MPA 入口矩阵。关键的是optimization.splitChunks的三项配置它们共同决定了自动去重如何生效chunks: all显式开启对初始 chunkinitial的自动拆分。README 强调这是opt-in主动选择加入——因为该能力默认只针对异步 chunk 开启。在源码 lib/config/defaults.js 中可以验证D(splitChunks, chunks, async)即默认值只有async。只有把入口代码也纳入拆分范围all页面 A/B/C 共享的m1/m2这类同源 vendor 才可能被抽取成独立文件若保持默认的async同步 import 的模块将不参与候选。maxInitialRequests: 20与maxAsyncRequests: 20允许单个入口加载时并行请求的初始/异步 chunk 数量上限。把它从默认调高到 20等价于开启面向 HTTP/2 的拆分模式浏览器在 HTTP/1.1 下同一域名通常只支持 6 个并行请求而 HTTP/2 支持多路复用允许更多、更细粒度的请求而无需付出连接数代价。minSize: 40以字节为单位的最小拆分阈值。注释特意说明这是仅为示例而设——为了让 2 个约 30 字节的小模块拼起来后能超过阈值见下文554.js分析真实项目应删除该行以使用默认值。版本提示README 中真实应用默认 30kb是对早期版本的经验描述。以当前仓库源码为准minSize的实际默认值由 lib/config/defaults.js 决定生产模式 20000 字节20KB、开发模式 10000 字节10KB由环境决定而非固定值。隐藏在这些开关背后的默认值体系splitChunks本身也有一套完整默认值理解它们才能准确判断哪些配置需要显式给出。以上述 default.js 为例当用户只配置示例中的三项时其余项按以下规则补齐配置项默认值源码位置lib/config/defaults.jschunksasyncL2619minChunks1L2621minSize生产 20000 / 开发 10000L2622enforceSizeThreshold生产 50000 / 开发 30000L2624maxAsyncRequests生产 30 / 开发 InfinityL2625maxInitialRequests生产 30 / 开发 InfinityL2626automaticNameDelimiter-即产物名中页面间分隔符L2627cache groupdefaultidHint: 、minChunks: 2、priority: -20、reuseExistingChunk: trueL2631-L2636cache groupdefaultVendorsidHint: vendors、匹配node_modules、priority: -10、reuseExistingChunk: trueL2637-L2642从中可以读出两层含义内置两个缓存组cache groupdefaultVendors通过test: NODE_MODULES_REGEXP专门收集来自node_modules的模块优先级 -10default组负责其余应用模块优先级 -20。优先级越高越先被考虑所以 vendor 与业务代码天然分流。这正是生产输出中会出现两类共享 chunk一类 id hint 为vendors、一类不带的根本原因。两个组的minChunks不同应用模块组default要求被 ≥2 个 chunk 引用才会进入候选而defaultVendors不设门槛minChunks 取全局默认 1。这解释了为何仅被单个页面使用但体积够大的 vendor 也会被单独拆出见下文122.js。生产模式产物解读一个 chunk 一张表示例文档给出了mode: production下的完整构建输出。先观察结论产物按初始请求数 × 体积阈值被自动划分成入口 chunk、vendor 组合共享 chunk、应用模块组合共享 chunk、单页独占大体积 vendor chunk四类。用表格整理如下产物chunk id归属页面组成模块类型体积pageA.jspageA./pages/a入口1.22 KiBpageB.jspageB./pages/b入口1.29 KiBpageC.jspageC./pages/c入口1.29 KiBpageD.jspageD./pages/d入口1.22 KiBpageE.jspageE./pages/e入口1.2 KiBpageF.jspageF./pages/f入口1.22 KiBpageG.jspageG./pages/g入口1.18 KiB454.jspageA/pageB/pageCm1 m2vendors 共享86 bytes122.jspageAm3单页 vendor超阈值43 bytes778.jspageD/pageE/pageFm7 m8vendors 共享86 bytes301.jspageD/pageE/pageF/pageGm6vendors 共享43 bytes811.jspageBm4单页 vendor超阈值43 bytes876.jspageCm5单页 vendor超阈值43 bytes554.jspageA/pageD/pageFs2 s3应用模块共享default 组62 bytes对应的构建摘要如下节选自示例文档完整输出见 many-pages/README.mdassets by chunk 748 bytes (id hint: vendors) asset 454.js 158 bytes [emitted] [minimized] (id hint: vendors) ... asset pageA.js 1.22 KiB [emitted] [minimized] (name: pageA) ... chunk (runtime: pageA) 122.js (id hint: vendors) 43 bytes [initial] [rendered] split chunk (cache group: defaultVendors) ./pages/a pageA ./node_modules/m3.js 43 bytes [built] [code generated] chunk (runtime: pageA, pageD, pageF) 554.js 62 bytes [initial] [rendered] split chunk (cache group: default) ./stuff/s2.js 31 bytes [built] [code generated] ./stuff/s3.js 31 bytes [built] [code generated] webpack X.X.X compiled successfully怎样解读这些产物入口 chunkpageA.js等 7 个每个入口的正常输出文件包含该页面运行时runtime、入口模块自身以及未被拆分的剩余依赖。454.jsvendors 共享m1、m2被 pageA/B/C 三个入口共同引用两个 43 字节的模块合计 86 字节 ≥minSize: 40因此被defaultVendors组抽成独立文件三页共享。122.js/811.js/876.js单页独占 vendorm3pageA 独用、m4pageB 独用、m5pageC 独用虽然只被一个入口引用但单模块 43 字节已超过阈值故仍被单独拆出——这正是文档所述vendors only used by a single page but larger than the threshold in size。554.js应用模块共享来自stuff的s2、s3同时出现在 pageA/pageD/pageF两个 31 字节模块合计 62 字节超过 40 字节阈值于是被default组抽成独立 chunk。产物中的(cache group: defaultVendors)/(cache group: default)标注印证了上文两组内置缓存组的分工。关于命名有一点需要说明README 的Interpreting the result一节保留了旧版本风格的命名描述如vendors~pageD~pageE~pageF~pageG.js、pageA~pageD~pageF.js其分隔符~正是automaticNameDelimiter的产物。在当前版本的生产构建输出中这些拆分 chunk 使用数值化 chunk id如454.js并附(id hint: vendors)提示其归属本质对应的仍是以页面集合为名的共享组。阈值minSize如何决定要不要新建一个请求示例最值得玩味的设计是某些模块被故意重复./stuff/s4.js同时被 pageA 和 pageC 引用是两页之间唯一的共享模块但其源码仅有一行console.log(some own module)见 stuff/s4.js体积 31 字节 minSize: 40。因此 webpack 判定为它单独新建一个请求不值得——额外请求会带来加载开销、破坏 HTTP/1.1 并行上限并劣化 gzip 压缩效率文件越小压缩去重收益越弱。于是s4被复制进 pageA 和 pageC 各自的产物中。类似的s1pageB/D/F/G 共享、s7pageB/E 共享等也因单模块不达阈值而保持复制而非抽取。这就是minSize的完整语义它用至少省下 X 字节来抵消多一个网络请求的代价是拆分算法在请求数与重复体积之间做经济性取舍的核心旋钮。请求数与重复度是跷跷板示例文档专门补充了一条提示值得所有 MPA 架构者记住调低maxInitialRequests/maxAsyncRequests会进一步增加重复以换取更少的请求数量。重复不影响首屏加载首次进入的页面始终要下载它所需的全部代码它只影响后续在应用各页面间导航时的下载体积——因为被复制到入口里的重复模块无法命中浏览器缓存。换言之对 7 个入口、约 40KB 代码量级的应用把请求上限从 20 降到 6 甚至更低webpack 会把本可共享的 chunk 重新并入多个入口产物用户切页时需要多下载重复部分。当浏览器只能维持 HTTP/1.1 的 6 个并发连接时这是合理的保守选择而当网站启用 HTTP/2 多路复用后就可以像本示例一样调高上限换取更彻底的模块级缓存复用。具体到本示例minSize: 40且上限为 20 时最终 pageA 首屏需要加载pageA.js 454.js 122.js 554.js而 pageD 需要pageD.js 778.js 301.js 554.js——554.js便是两个入口都命中缓存、被复用而非重复下载的共享应用 chunk。如何运行与观察在仓库根目录执行生产构建即可复现上述产物示例文档就是使用mode: production生成的输出node_modules/.bin/webpack --config examples/many-pages/webpack.config.js --mode production构建完成后对比输出中的chunk列表即可看到入口 chunk、两类拆分 chunkcache group 标注与split chunk关键字。开发模式--mode development下由于minSize默认仅 10KB、请求上限为Infinitylib/config/defaults.js小体积模块的拆分行为会与生产模式不同——这正是推荐用生产模式评估产物的原因。若想批量运行仓库内全部示例可参考 examples/buildAll.js每个示例目录内的构建步骤遵循 examples/README.md 中统一的组织方式。深入源码拆分决策发生在哪splitChunks的实际拆分执行位于 lib/optimize/SplitChunksPlugin.js它会在模块图构建完成后计算模块的共享归属并把超过阈值的共享模块从原 chunk 迁出形成新 chunkmaxAsyncRequests/maxInitialRequests作为容量上限参与分组的可行性判定lib/optimize/SplitChunksPlugin.js 附近对两个上限做了比较与记录。该插件的选项定义与模块分组结构均围绕chunks、minSize、minChunks、cacheGroups等字段组织chunks: all会把 initial chunk 中的同步模块也纳入候选集。结合上文 lib/config/defaults.js 的默认值注入逻辑可以确认示例显式配置的chunks: all与minSize: 40恰好覆盖了让 initial chunk 参与拆分 用极低阈值放大拆分效果两个必要条件其余约束默认缓存组、请求上限、命名分隔符都由 webpack 默认补齐。小结从示例到生产 MPA 的迁移建议many-pages示例用 40 字节阈值放大了拆分过程便于肉眼观察算法行为真实项目则应把下述示例做法替换为生产做法chunks: all保持开启——多页面对初始 chunk 的自动去重收益最大minSize不写或用默认值生产 20KB见 lib/config/defaults.js让 webpack 用体积经济性自行裁决避免碎片化请求maxInitialRequests/maxAsyncRequests视传输协议决定仅 HTTP/1.1 时维持浏览器 6 连接约束附近的保守值部署 HTTP/2 后可上调如示例的 20或源码默认的生产 30以换取更高缓存复用理解并善用default与defaultVendors两个内置缓存组的优先级与test规则——业务共享与应用依赖会被自动分到不同产物便于 CDN 长缓存策略设计。对于更深层的手工控制如自定义缓存组、reuseExistingChunk、enforceSizeThreshold等可继续研读 lib/optimize/SplitChunksPlugin.js 与本示例同目录下的其他官方示例如 code-splitting、common-chunk-and-vendor-chunk它们展示了同一套机制在不同应用形态下的典型用法。【免费下载链接】webpackA bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through loaders, modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.项目地址: https://gitcode.com/GitHub_Trending/web/webpack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →