webpack 代码分割进阶:用 entrypoint dependOn 实现 Vendor 与 App 共享运行时
本篇指南围绕 webpack 官方示例 examples/code-splitting-depend-on-simple 展开,讲解如何通过 entry.<name>.dependOn 选项把多个入口分成"依赖入口(如 react-vendors)+ 依赖它的入口(如 app)",让后者自动共享前者的运行时与模块缓存,从而避免同一份 vendor 代码在多个 bundle 中重复打包。读完本文,你将掌握 dependOn 的完整配置写法、构建产物的结构解读、stats 输出的逐行含义,以及从 EntryOptionPlugin.js 到 Compilation.js 的源码级实现链路,能够把它应用到多页面或"主应用 + 共享 vendor"的真实项目中。
示例目标:一个入口依赖另一个入口
示例目录 examples/code-splitting-depend-on-simple 演示的是 Code Splitting 中 entrypoint dependOn 的用法。核心思想是:
- 把
react、react-dom、prop-types等第三方库单独作为一个入口react-vendors打包; - 让业务入口
app声明dependOn: ["react-vendors"]; - webpack 会保证加载
app前必须先加载react-vendors,并且app.js中不再重复携带这些第三方模块和运行时——它们全部由react-vendors.js提供。
这在多入口项目中尤其常见:如果 appA、appB 两个入口都 import React,但没有 dependOn,webpack 无法确定该把 React 放到哪个 chunk(放 appA 会导致 appB 运行时找不到模块),最终要么复制多份要么报错。dependOn 就是显式告诉 webpack 加载顺序与运行时归属的官方手段。
完整配置:webpack.config.js
示例的完整构建配置如下(与 examples/code-splitting-depend-on-simple/webpack.config.js 一致):
"use strict";
/** @type {import("webpack").Configuration} */
const config = {
entry: {
app: { import: "./app.js", dependOn: ["react-vendors"] },
"react-vendors": ["react", "react-dom", "prop-types"]
},
optimization: {
chunkIds: "named" // To keep filename consistent between different modes (for example building only)
},
stats: {
chunks: true,
chunkRelations: true
}
};
module.exports = config;
逐项说明:
| 配置项 | 取值 | 作用 |
|---|---|---|
entry.app.import |
"./app.js" |
业务入口的源文件 |
entry.app.dependOn |
["react-vendors"] |
声明 app 依赖 react-vendors 入口,加载 app 前必须先加载它;app 因此不再自带运行时 |
entry["react-vendors"] |
["react", "react-dom", "prop-types"] |
vendor 入口,可以是字符串数组形式(多模块入口) |
optimization.chunkIds |
"named" |
保持不同模式(dev/prod、单独构建)下 chunk 文件名一致,方便调试与对比 |
stats.chunks / stats.chunkRelations |
true |
在构建输出中打印 chunk 及 chunk 之间的关系(<{react-vendors}>、>{app}<),便于验证 dependOn 是否生效 |
官方 schema 对 dependOn 的定义(见 schemas/WebpackOptions.json)是:
"The entrypoints that the current entrypoint depend on. They must be loaded when this entrypoint is loaded." (当前入口所依赖的入口,加载当前入口时必须同时加载它们。)
取值为字符串数组,每个元素是已声明的其他入口名,minLength: 1。
入口源码:app.js
examples/code-splitting-depend-on-simple/app.js 非常简洁:
import react from "react";
import reactDOM from "react-dom";
import propTypes from "prop-types";
console.log(react, reactDOM, propTypes);
注意:app.js 里直接 import 了三个第三方库,但配置上这些库由 react-vendors 入口负责打包。得益于 dependOn,app 在运行时直接复用 react-vendors 模块缓存中的实例,而不是各自打包一份。
构建产物分析
构建后生成两个文件:react-vendors.js(vendor 模块 + 完整运行时)与 app.js(仅业务代码,通过 self["webpackChunk"] 注入机制挂载到同一运行时上)。
dist/app.js
app.js 产物中只有 ./app.js 这一个模块,对 react、react-dom、prop-types 的引用全部是 __webpack_require__(0/1/2) 形式的跨 chunk 请求,模块体在 react-vendors.js 中:
"use strict";
(self["webpackChunk"] = self["webpackChunk"] || []).push([["app"],{
/***/ 3
/*!****************!*\
!*** ./app.js ***!
\****************/
/*! namespace exports */
/*! exports [not provided] [no usage info] */
/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.* */
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ 0);
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ 1);
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! prop-types */ 2);
/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_2__);
console.log((react__WEBPACK_IMPORTED_MODULE_0___default()), (react_dom__WEBPACK_IMPORTED_MODULE_1___default()), (prop_types__WEBPACK_IMPORTED_MODULE_2___default()));
/***/ }
},
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(moduleId))
/******/ var __webpack_exports__ = (__webpack_exec__(3));
/******/ }
]);
关键点:
(self["webpackChunk"] = self["webpackChunk"] || []).push([["app"], {...}])是 JSONP chunk 注入——appchunk 依赖react-vendors中已建立的webpackChunk全局数组,把自身模块合并进同一个模块表;- 文件里没有任何
__webpack_modules__缓存声明,说明运行时不在app.js中,印证了"被 dependOn 的入口携带运行时、依赖它的入口只做增量注入"的行为; - 末尾的
webpackRuntimeModules只是执行入口模块3(./app.js),运行时函数__webpack_require__由react-vendors.js提供。
dist/react-vendors.js
react-vendors.js 是真正的 bootstrap 文件,包含三个 vendor 模块、完整运行时和启动代码。模块部分与启动代码如下(完整的运行时折叠代码段可在示例生成的 README.md 中查看):
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ([
/* 0 */
/*!*******************************!*\
!*** ./node_modules/react.js ***!
\*******************************/
/*! unknown exports (runtime-defined) */
/*! runtime requirements: module */
/*! CommonJS bailout: module.exports is used directly at 1:0-14 */
/***/ ((module) => {
module.exports = 'react';
/***/ }),
/* 1 */
/***/ ((module) => {
module.exports = 'react-dom';
/***/ }),
/* 2 */
/***/ ((module) => {
module.exports = 'prop-types';
/***/ })
/******/ ]);
启动段:
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ __webpack_require__(0);
/******/ __webpack_require__(1);
/******/ let __webpack_exports__ = __webpack_require__(2);
/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__);
/******/
/******/ })()
;
运行时的 jsonp chunk loading 部分会初始化 installedChunks = { "react-vendors": 0 } 并建立 self["webpackChunk"] 数组,这正是后续 app.js 能推入模块的前提。从产物结构看:react-vendors 是"运行时宿主 + vendor 模块宿主",app 是"增量 chunk"。页面中只需按顺序引入两个 <script>(先 react-vendors.js,后 app.js)即可。
stats 输出解读(Unoptimized 与 Production)
配置里 stats.chunks 与 stats.chunkRelations 打开后,两种模式下的完整构建输出如下。
Unoptimized 模式
asset react-vendors.js 7.4 KiB [emitted] (name: react-vendors)
asset app.js 1.59 KiB [emitted] (name: app)
chunk (runtime: react-vendors) app.js (app) 139 bytes <{react-vendors}> [initial] [rendered]
> ./app.js app
./app.js 139 bytes [built] [code generated]
[no exports]
[used exports unknown]
entry ./app.js app
chunk (runtime: react-vendors) react-vendors.js (react-vendors) 87 bytes (javascript) 3.25 KiB (runtime) >{app}< [entry] [rendered]
> prop-types react-vendors
> react react-vendors
> react-dom react-vendors
runtime modules 3.25 KiB 6 modules
cacheable modules 87 bytes
./node_modules/prop-types.js 31 bytes [built] [code generated]
[used exports unknown]
from origin ./app.js
harmony side effect evaluation prop-types ./app.js 3:0-35
harmony import specifier prop-types ./app.js 5:29-38
cjs self exports reference ./node_modules/prop-types.js 1:0-14
entry prop-types react-vendors
./node_modules/react-dom.js 30 bytes [built] [code generated]
[used exports unknown]
from origin ./app.js
harmony side effect evaluation react-dom ./app.js 2:0-33
harmony import specifier react-dom ./app.js 5:19-27
cjs self exports reference ./node_modules/react-dom.js 1:0-14
entry react-dom react-vendors
./node_modules/react.js 26 bytes [built] [code generated]
[used exports unknown]
from origin ./app.js
harmony side effect evaluation react ./app.js 1:0-26
harmony import specifier react ./app.js 5:12-17
cjs self exports reference ./node_modules/react.js 1:0-14
entry react react-vendors
webpack X.X.X compiled successfully
Production 模式
asset react-vendors.js 1.2 KiB [emitted] [minimized] (name: react-vendors)
asset app.js 180 bytes [emitted] [minimized] (name: app)
chunk (runtime: react-vendors) app.js (app) 139 bytes <{react-vendors}> [initial] [rendered]
> ./app.js app
./app.js 139 bytes [built] [code generated]
[no exports]
[no exports used]
entry ./app.js app
chunk (runtime: react-vendors) react-vendors.js (react-vendors) 87 bytes (javascript) 3.05 KiB (runtime) >{app}< [entry] [rendered]
> prop-types react-vendors
> react react-vendors
> react-dom react-vendors
runtime modules 3.05 KiB 5 modules
cacheable modules 87 bytes
./node_modules/prop-types.js 31 bytes [built] [code generated]
[used exports unknown]
from origin ./app.js
harmony side effect evaluation prop-types ./app.js 3:0-35
harmony import specifier prop-types ./app.js 5:29-38
cjs self exports reference ./node_modules/prop-types.js 1:0-14
entry prop-types react-vendors
./node_modules/react-dom.js 30 bytes [built] [code generated]
[used exports unknown]
from origin ./app.js
harmony side effect evaluation react-dom ./app.js 2:0-33
harmony import specifier react-dom ./app.js 5:19-27
cjs self exports reference ./node_modules/react-dom.js 1:0-14
entry react-dom react-vendors
./node_modules/react.js 26 bytes [built] [code generated]
[used exports unknown]
from origin ./app.js
harmony side effect evaluation react ./app.js 1:0-26
harmony import specifier react ./app.js 5:12-17
cjs self exports reference ./node_modules/react.js 1:0-14
entry react react-vendors
webpack X.X.X compiled successfully
几个值得注意的细节:
chunk (runtime: react-vendors) app.js (app) ... <{react-vendors}>:<{react-vendors}>表示app在加载关系上位于react-vendors之后(前向依赖),且整个编译的运行时挂在react-vendorschunk 上;>{app}<:react-vendors有子关系指向app,即它是被app依赖的一方;from origin ./app.js与harmony import specifier ... ./app.js 5:12-17:虽然模块归属在react-vendors入口,但使用来源是./app.js,说明依赖收集链路是完整的,只是打包归属被dependOn关系重定向到了 vendor 入口;- Production 模式
runtime modules 3.05 KiB 5 modules(Unoptimized 为3.25 KiB 6 modules),压缩与 tree-shaking 后运行时会略减,两个模式都成功编译。
源码实现链路:dependOn 是如何生效的
从源码结构看,dependOn 从配置到产物的传递经过以下环节:
-
配置归一化:EntryOptionPlugin.js 的
entryDescriptionToOptions会把入口描述对象中的desc.dependOn原样带入EntryOptions,dependOn与其他入口选项(filename、runtime、publicPath等)一起成为入口描述的一部分。 -
入口间建立依赖边:Compilation.js 在处理入口组时,对被依赖的入口执行
entry.addDependOn(dependency),并通过dependency.addChild(entry)/entry.addParent(dependency)建立 parent/child 关系——这正是 stats 中>{app}<、<{react-vendors}>关系的来源。 -
Entrypoint 的数据结构:Entrypoint.js 用
SortableSet保存_dependOn集合,并提供addDependOn(entrypoint)与dependOn(entrypoint)两个方法供 chunk 图遍历查询。 -
运行时归属决策:ChunkGraph.js 在遍历 chunk 的入口组时,会沿
dependOn边向下 BFS(注释中说明用 entrypoints 集合兼作 visited 集合,避免钻石形dependOn依赖被重复遍历),并保证"被别人依赖的入口必须先行加载"。这就是为什么运行时最终落在react-vendorschunk 中、appchunk 只保留 JSONP 注入代码。 -
HTML 场景的延伸:HtmlModulesPlugin.js 中
collectHtmlEntryRequests会按dependOn祖先优先的顺序收集一个 HTML 页面需要加载的资源("itsdependOnancestors first (transitive, deduped — so a diamond loads each once), then the entry's own imports"),保证输出 HTML 时共享/运行时 chunk 的<script>标签先于入口自身的标签注入。
实践建议与适用边界
- 适用场景:多入口项目希望共享 vendor 与运行时(典型如"页面 A + 页面 B 都依赖同一份 React")、或模块联邦/HTML 输出中需要控制脚本加载顺序。
dependOn表达的是加载顺序与运行时归属,而不是简单的代码复用。 - 与
optimization.splitChunks的分工:splitChunks面向"同一入口/单入口下按规则自动拆 chunk";dependOn面向"多个入口之间的显式依赖关系"。多入口共享 vendor 的经典做法正是本示例这种显式声明,而不是依赖splitChunks自动推导。 - 注意顺序:
dependOn形成有向关系,A dependOn B 则 B 必须先行加载;若出现环形依赖,入口关系无法形成合法的加载顺序,配置时应避免。 - 验证方式:沿用本示例的
stats: { chunks: true, chunkRelations: true },通过<{vendor}>/>{app}<标记确认关系建立;通过检查产物中哪个文件携带__webpack_modules__与installedChunks确认运行时归属。 - 文件名一致性:示例中显式设置
optimization.chunkIds: "named"并注释了原因——"To keep filename consistent between different modes (for example building only)",在 dev/prod 切换或部分构建时避免文件名漂移导致引用失效。
小结
dependOn 是 webpack 入口选项中最能体现"入口之间拓扑关系"的机制:一行 dependOn: ["react-vendors"] 就能让 app 入口放弃自带运行时与 vendor 模块,转而挂载到 react-vendors 提供的 JSONP 运行时上。结合 examples/code-splitting-depend-on-simple/README.md 中的完整产物与 stats 输出对照阅读,再参考上文 EntryOptionPlugin.js、Entrypoint.js、ChunkGraph.js 的实现链路,可以完整理解这一机制从配置解析到产物形态的全过程,并将其迁移到自己项目的多入口分包方案中。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00