
Webpack 热更新的 JSONP 加载流程
要理解 output.hotUpdateGlobal 的价值,必须先看清 Webpack 热更新在浏览器端的工作方式。开发模式下,Webpack-dev-server 或 webpack-dev-middleware 会在编译产出中注入一段 HMR 运行时。当源码发生变化,服务端会生成两个文件:一个清单文件(hot-update.json)和一个或多个更新块文件(hot-update.js)。客户端运行时通过 EventSource 或轮询收到更新通知后,动态创建 <script> 标签去加载这些块文件。
块文件并不是普通的模块代码。它内部并不会直接执行模块替换,而是调用一个早已在全局作用域上定义好的函数,把新模块加入到一个队列中。这个全局函数就是由 output.hotUpdateGlobal 配置名命名的变量承载的。更准确地说,Webpack 生成的 JSONP 回调会查找 window["webpackHotUpdate"] (默认值)并调用它,将新的模块定义和更新数据传入运行时。然后运行时再根据模块 ID 去执行 module.hot.accept 注册的回调,完成局部替换。
这一机制本质上借鉴了 JSONP 跨域加载的思路:用一个全局回调函数名作为服务端和客户端之间的约定,服务端把数据包装成函数调用,客户端预先定义好这个函数即可。因此 hotUpdateGlobal 就是那个约定的名字,改变它,就改变了服务端输出的 JS 代码中调用的函数名。
hotUpdateGlobal 的默认行为与潜在冲突
Webpack 5 默认将 output.hotUpdateGlobal 设置为 "webpackHotUpdate",并且经常会在后面拼接一个取自 output.uniqueName 的字符串,形成类似 webpackHotUpdateMyApp 的变量名。这是为了避免同一个页面上多个 Webpack 实例产生冲突。在微前端场景下,主应用和多个子应用可能各自使用自己的 webpack 构建,如果所有应用都使用完全相同的 hotUpdateGlobal,当一个子应用的更新块文件被下载并执行时,它调用的全局函数可能会被主应用的运行时拦截,导致模块被注入到错误的应用上下文中,引发难以调试的运行时错误。
即使不使用微前端,一个页面也可能同时存在多个入口。例如使用 webpack 的 multi-compiler 模式或配置多组 entry,每组构建产出独立的热更新运行时。如果它们的 hotUpdateGlobal 相同,第一次加载的运行时会把全局函数覆盖成自己的处理逻辑,后续无论哪个入口收到更新,都会走到这个单一的回调里,造成“更新混乱”。因此,在一个页面上运行多个 Webpack 热更新实例时,必须确保每个实例拥有不同的 hotUpdateGlobal。
Webpack 从 5 开始引入了 output.uniqueName 作为避免全局冲突的统一方案,它会自动影响 hotUpdateGlobal 的最终值。但用户仍然可以通过 output.hotUpdateGlobal 显式覆盖这一自动生成的名字,以便与旧项目兼容或满足特殊的命名规则。
自定义 hotUpdateGlobal 的配置方法
在 webpack.config.js 中,我们可以在 output 对象里直接指定 hotUpdateGlobal。例如:
// webpack.config.js
module.exports = {
// ...
output: {
hotUpdateGlobal: 'myCustomHMRGlobal',
},
};
这样配置后,构建出来的 hot-update.js 文件内容大致会从原来的:
webpackHotUpdate("chunkName", {...})
变成:
myCustomHMRGlobal("chunkName", {...})
相应地,浏览器端的 HMR 运行时也会在初始化阶段将 window["myCustomHMRGlobal"] 设置成内部的更新处理函数。需要注意的是,这个配置只影响客户端代码中产生 JSONP 回调名的部分,并不影响服务端向浏览器推送消息的通道名称,因此修改后无需调整 webpack-dev-server 的其他选项。
在实际项目中,更推荐使用 output.uniqueName 来间接控制 hotUpdateGlobal,因为 uniqueName 还会同时影响 output.jsonpFunction 等其他全局标识,避免遗漏。例如:
output: {
uniqueName: 'sub-app-a',
// 此时 hotUpdateGlobal 会自动变成 'webpackHotUpdatesub-app-a'
}
虽然 Webpack 会自动添加前缀,但有时我们需要完全自定义,比如团队成员习惯使用驼峰命名,或者需要与历史代码中的特定名称对齐,这时直接配置 hotUpdateGlobal 就更加直接。不过一定要和 jsonpFunction、chunkLoadingGlobal 等配置保持一致,避免仍然遗留其他冲突点。
与 output.library 等配置的联动注意事项
不少开发者会把 output.hotUpdateGlobal 与 output.library 或 output.jsonpFunction 混淆。前者的作用域仅限于 HMR 更新模块的加载,后两者分别用于库的导出变量名和普通的代码分割 chunk 加载回调。它们虽然都涉及全局变量,但使用场景完全不同。例如 output.jsonpFunction 控制的是按需加载(非 HMR) chunk 时的回调名,在 Webpack 5 中已经被 output.chunkLoadingGlobal 取代。如果一个项目里同时改了 hotUpdateGlobal 而没有调整 chunkLoadingGlobal,可能导致开发环境下 chunk 加载失败,因为运行时内部仍然监听旧的全局回调名。
对于使用 Module Federation 的场景,容器应用和远程应用各自都有自己的 Webpack 运行时。Module Federation 本身通过 __webpack_share_scopes__ 等全局变量进行共享,与 hotUpdateGlobal 互不干涉。但在开发模式下启用热更新时,每个远程应用也会向宿主页面注入自己的 HMR 运行时,此时若两个远程应用的 hotUpdateGlobal 相同,就会出现上述的冲突问题。推荐的最佳实践是为每个 Federation 远程模块配置不同的 uniqueName,由 Webpack 自动生成独一无二的 hotUpdateGlobal。
最后需要注意的是,更改 hotUpdateGlobal 后要确保所有在使用中的 Service Worker 或中间代理不会缓存旧的更新块文件。因为变量名的变化会导致旧名称的全局函数调用变成 undefined,客户端可能静默失败。建议在开发环境禁用 Service Worker 或者使用版本号控制更新文件名。
WebpackhotUpdateGlobal热更新修改时间:2026-08-12 12:25:00