
webpack 异步公共 chunk 自动抽取全解析以 extra-async-chunk 示例吃透 splitChunks 公共代码优化【免费下载链接】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本文围绕当前仓库 examples/extra-async-chunk 目录下的官方示例展开系统讲解 webpack 在多个异步代码块async chunk之间共享公共模块时的自动抽取automatic commons/split chunk机制。通过真实源码、配置文件与构建产物逐层拆解你将掌握require([...])/require.ensure触发的异步代码分割、optimization.splitChunks中minSize与默认 cache group 的取舍以及抽取结果如何在 SPA 路由懒加载场景中大放异彩。读完本文即可在真实工程里复现并定制这套优化。一、示例解决的核心问题多个异步 chunk 的公共模块去哪了在大型单页应用中路由级懒加载通常会产生多个彼此独立的异步 chunk。若这些 chunk 恰好引用了同一批公共模块例如工具函数、状态管理库最朴素的打包方式会把这批模块重复打包进每一个 chunk造成体积浪费与缓存失效。示例 README 描述的场景精确对应这一痛点入口 chunk 通过两次异步加载引用两个 chunk记为 X 与 Y其中 chunk X 包含模块a、b、cchunk Y 包含模块a、b、d入口 chunk异步引用 → chunk X、异步引用 → chunk Ychunk X模块abcchunk Y模块abdchunk X 与 chunk Y 共享模块a和b。webpack 的代码分割优化会把它们提取到一个新的 chunk Z 中从而形成每个公共模块只存在一份的产物结构入口 chunk异步引用 → chunk X Z、异步引用 → chunk Y Zchunk X仅剩模块cchunk Y仅剩模块dchunk Z包含公共模块abREADME 特意点明这种结构 Pretty useful for a router in a SPA对 SPA 的路由器非常有用——这正是路由懒加载 公共依赖抽取的标准形态。二、示例源码精读两种异步加载语法触发公共代码分割示例全部源码文件都位于 examples/extra-async-chunk 目录example.js示例入口只做一件事——发起两次异步模块加载a.js、b.js、c.js、d.js四个超简单的 CommonJS 模块内容分别为module.exports a / b / c / d是观察 chunk 归属最干净的对象。入口 example.js 完整内容如下// a chunks with a, b, c require([./a, ./b, ./c]); // a chunk with a, b, d require.ensure([./a], function(require) { require(./b); require(./d); });它同时使用了 webpack 的两种经典异步加载写法AMD 风格的require([deps], callback)静态列出依赖./a、./b、./cwebpack 解析后生成一个包含这三者的异步 chunkrequire.ensure(deps, callback)保证./a就绪后执行回调回调内又同步require(./b)、require(./d)组合成第二个异步 chunk。两个 chunk 恰好交集出模块a与b为公共代码抽取提供了最小但完整的验证标本。产物统计信息见下文构建输出解读显示两次加载分别以./example.js 2:0-30与./example.js 5:0-8:2标注正好对应源码第 2 行与第 5–8 行的异步调用。三、配置解密为什么这个示例要显式关闭minSize示例的构建配置在 webpack.config.jsuse strict; /** type {import(webpack).Configuration} */ const config { // mode: development || production, optimization: { splitChunks: { minSize: 0 // This example is too small }, chunkIds: named // To keep filename consistent between different modes (for example building only) } }; module.exports config;配置只做了两件事其中藏着两个关键知识点1.optimization.splitChunks.minSize: 0README 明确说明The optimization compares the size of chunk Z to some minimum value, but this is disabled from this example. In practice, there is no configuration needed for this.——即 splitChunks 优化在决定是否真的抽出公共 chunk 时会先把这个候选公共 chunk的体积与一个最小体积阈值比较只有达标才落地抽取本示例中该阈值被显式设成 0 以强制展示抽取行为而真实工程里无需为它做任何配置。为什么默认配置下这个迷你示例可能不会被抽取看 lib/config/defaults.js 中的默认值推导逻辑可知minSize并非固定值而是随构建模式变化minSize: () (production ? 20000 : 10000)——生产模式默认 20 KB、非生产模式默认 10 KB此外还有enforceSizeThreshold生产 50000 / 非生产 30000、maxAsyncRequests与maxInitialRequests生产默认 30非生产Infinity、minChunks顶层默认 1等一系列约束。本示例中a.js、b.js各自只有 21 bytes抽出的 chunk Z 远达不到 10 KB/20 KB 阈值因此必须用minSize: 0显式放行才能直观看到公共 chunk 的完整生成链路。该参数到运行时由 lib/optimize/SplitChunksPlugin.js 中的checkMinSize函数按 source type如javascript、unknown逐类校验任何一类尺寸不达标即判定为不满足最小体积条件。2.optimization.chunkIds: named注释说明它用于让不同 mode 下产出文件名保持一致示例构建脚本会同时跑开发与生产模式。由 lib/config/defaults.js 可见chunkIds的默认规则是 production 用deterministic、development 用named、其余为natural本示例显式固定为named于是产物中出现a_js-b_js.output.js、c_js.output.js、d_js.output.js这类由模块来源推导的可读 chunk 名./a.js→a_js多个模块以-连接方便两种模式下 diff 对比输出体积。四、公共 chunk 从哪来默认 cache group 的抽取规则细心的读者会问配置里完全没有声明任何 cache groupchunk Z产物中即a_js-b_js.output.js凭什么被创建答案在 lib/config/defaults.js当用户未提供自定义 cache group 时webpack 会为splitChunks.cacheGroups补上两个内置组F(cacheGroups, default, () ({ // minChunks: 2, ... })); F(cacheGroups, defaultVendors, () ({ // test: /[\\/]node_modules[\\/]/i, ... }));同时顶层默认chunks: async见 defaults.js即 splitChunks 默认只针对异步 chunk做切分。落到本示例模块a、b被两个不同的异步 chunkchunk X、Y共同引用命中默认 cache groupdefault要求的最小引用次数minChunks: 2它们满足chunks: async的筛选范围minSize: 0又放行了体积校验于是 SplitChunksPlugin 把a、b归入默认的default分组抽出独立 chunk。这一结论可从构建统计直接验证——产物信息中明确标注该文件是split chunk (cache group: default)而非用户自定义分组。真正执行分组、合并与校验的复杂逻辑集中在 lib/optimize/SplitChunksPlugin.js例如将minSize与 cache group 配置合并、计算哪些 chunk 组合适合抽离的_validateSize、按minChunks过滤候选模块等过程都在该文件实现。五、构建产物逐文件剖析入口、公共块与独立块的最终形态示例的 README 连同构建产物一起被提交在 examples/extra-async-chunk/dist由 template.md 模板渲染进 README最终产物共四个文件output.js入口 chunkname: main包含 webpack 引导bootstrap运行时 入口模块 example.js 的编译代码a_js-b_js.output.js公共 chunk仅含模块a、b即上文所说被提取出来的 chunk Zc_js.output.js仅含模块cd_js.output.js仅含模块d。入口 chunk 的运行时骨架README 中以折叠块收纳的 dist/output.js 运行时代码展示了 webpack 加载异步 chunk 所依赖的完整运行时基础设施大致包括__webpack_require__模块加载器与其模块缓存__webpack_module_cache____webpack_require__.m模块表、__webpack_require__.ohasOwnProperty 简写__webpack_require__.u异步 chunk 文件名生成器(chunkId) (chunkId .output.js)呼应output.filename中[name].output.js的模式__webpack_require__.eensure chunk 入口对__webpack_require__.f中所有加载器逐个请求目标 chunk 并Promise.all__webpack_require__.f.jJSONP chunk 加载实现配合installedChunks状态机undefined 未加载、[resolve, reject, Promise] 加载中、0 已加载__webpack_require__.l基于script标签的脚本加载器含超时与错误处理超时默认 120000 ms__webpack_require__.ppublicPathdist/webpackJsonpCallback与全局self[webpackChunk]回调机制JSONP 加载回调。入口代码编译后的真正形态example.js 中两段异步声明被分别编译成两个 JSONP 请求序列见 README 的编译结果// a chunks with a, b, cAMD require Promise.all([__webpack_require__.e(a_js-b_js), __webpack_require__.e(c_js)]) .then(function() { [__webpack_require__(1), __webpack_require__(2), __webpack_require__(3)]; })catch; // a chunk with a, b, drequire.ensure Promise.all([__webpack_require__.e(a_js-b_js), __webpack_require__.e(d_js)]) .then((function(require) { __webpack_require__(2); // ./b __webpack_require__(4); // ./d }).bind(null, __webpack_require__))catch;注意一个决定性细节两条异步路径都优先加载a_js-b_js公共 chunk Z——这正是 splitChunks 重构 chunk 图后的直接结果。而c、d两个互不共享的模块则被隔离在各自独立的小 chunk 中保证任何一个路由被访问时只会按需拉取自己真正需要的代码。异步 chunk 的 JSONP 载荷每个独立 chunk 文件则是一段自注册的 JSONP 片段例如 dist/a_js-b_js.output.js(self[webpackChunk] self[webpackChunk] || []).push([[a_js-b_js],[ /* 1 */ module.exports a; // 来自 ./a.js /* 2 */ module.exports b; // 来自 ./b.js ]]);其数据包是[chunkIds, moreModules]二元组入口运行时收到后会把moreModules并入模块表__webpack_require__.m再把对应chunkId标记为已加载并 resolve 等待中的 Promise。构建统计里也能看到这些模块的依赖来源标注例如./a.js同时被amd require ./a ./example.js 2:0-30第一次异步加载与require.ensure item ./a ./example.js 5:0-8:2第二次异步加载引用——两个入口点共同引用了a正是它被选入公共 chunk 的原因。六、构建输出解读Unoptimized 与 Production 两种模式的差异README 在末尾的 Info 章节记录了两种模式下真实的 webpack 统计输出可用作判断抽取是否生效的可复现基准。Unoptimized开发模式未压缩asset output.js 8.84 KiB [emitted] (name: main) asset a_js-b_js.output.js 618 bytes [emitted] asset c_js.output.js 329 bytes [emitted] asset d_js.output.js 329 bytes [emitted] chunk (runtime: main) a_js-b_js.output.js 42 bytes [rendered] split chunk (cache group: default) ./a ./b ./c ./example.js 2:0-30 ./example.js 5:0-8:2 chunk (runtime: main) c_js.output.js 21 bytes [rendered] chunk (runtime: main) d_js.output.js 21 bytes [rendered] chunk (runtime: main) output.js (main) 164 bytes (javascript) 4.83 KiB (runtime) [entry] [rendered]统计信息以前缀标出每个 chunk 的上游入口引用点。a_js-b_js.output.js同时被两个入口点引用2:0-30的 AMD 加载与5:0-8:2的 ensure 加载并被明确标记为split chunk (cache group: default)入口output.js里 4.83 KiB 是 6 个运行时模块runtime modules这是开发模式未压缩下的完整运行时代码。Production mode生产模式压缩asset output.js 1.86 KiB [emitted] [minimized] (name: main) asset a_js-b_js.output.js 110 bytes [emitted] [minimized] asset c_js.output.js 83 bytes [emitted] [minimized] asset d_js.output.js 83 bytes [emitted] [minimized]同一份代码在生产模式下大幅缩水入口从 8.84 KiB 降到 1.86 KiBa_js-b_js公共块从 618 bytes 压缩到 110 bytesc_js/d_js各从 329 bytes 压到 83 bytes。差异主要来自压缩minimization、更精简的运行时与依赖图优化[used exports unknown]变为[no exports used]等标注而 chunk 划分结构完全一致——这正是示例配置把chunkIds固定为named想保证的一致性。需要说明示例统计中的webpack X.X.X是模板渲染时的版本占位符实际输出会随当前仓库构建所用 webpack 版本显示具体版本号。七、复现运行与进一步探索本地构建示例按 examples/README.md 中 Building an Example 一节的说明可先在仓库根目录完成依赖安装yarn # 安装依赖 yarn setup # 初始化构建工具链 yarn add --dev webpack-cli再进入单个示例目录执行构建每个示例目录都带有独立的 build.js 构建脚本本质是通过 webpack API 以 development 与 production 两种 mode 各编译一次并输出 stdout 统计cd examples/extra-async-chunk node build.js也可以一次构建全部示例npm run build:examples构建后会按 template.md 的模板结构把产物与统计渲染进 README方便你改动源码或配置后对比验证。进阶练习方向改小/去掉minSize: 0回到默认配置重跑构建观察公共 chunka_js-b_js是否还出现直观体会默认 10 KB/20 KB 阈值的过滤效果显式声明 cache group参考 SplitChunksPlugin.js 的cacheGroups结构增加一个test过滤 name指定的自定义分组把公共模块改名成有业务语义的 chunk例如common.js对照更复杂变体同目录下的 examples/extra-async-chunk-advanced 展示了更多模块与更多异步层级下的抽取行为可作为理解边界条件的延伸阅读。八、小结通过 examples/extra-async-chunk 这个最小示例我们完整还原了 webpack 的一条核心优化链路多个异步 chunk 共享的公共模块会被splitChunks默认作用于异步 chunk 的defaultcache group自动抽取为独立公共 chunk运行时通过 JSONP 在真正需要时才加载它。示例还精确演示了两个工程上极具代表性的配置权衡体积阈值minSize决定抽取是否值得真实项目中通常保持默认生产 20000 bytes只有示例级微小代码才需设 0 强制展示chunkIds: named保证不同构建模式下产物命名的确定性便于对比与缓存策略设计。理解了这两点再结合 lib/optimize/SplitChunksPlugin.js 的源码与 lib/config/defaults.js 的默认值推导就足以应对绝大多数 SPA 路由懒加载场景下的公共依赖切分配置做到知其然亦知其所以然。【免费下载链接】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),仅供参考