hmr 不是可拔插的独立插件,而是构建工具、开发服务器与运行时三者深度耦合的结果:webpack 编译注入运行时钩子,webpack-dev-server 提供 websocket 通道并生成热更新 chunk,浏览器端需调用 accept 才能响应更新。

Webpack 本身不是插件式构建引擎,它是一个打包器;真正支持「插件式 + HMR」协同工作的,是 webpack-dev-server 配合模块运行时 API(如 import.meta.hot)和用户手动接受更新的逻辑。强行把 HMR 当成可拔插的“功能模块”来设计,反而会破坏其依赖链——HMR 不是独立插件,而是构建工具、开发服务器、运行时三者深度耦合的结果。
为什么不能把 HMR 抽成独立插件?
HMR 的生效前提是:构建工具生成带 HMR 运行时代码的 bundle,开发服务器提供 WebSocket 通道并推送更新,浏览器端有对应接收与替换模块的逻辑。这三者缺一不可:
-
webpack在编译时注入__webpack_require__.hmr和module.hot等运行时钩子,不是靠插件“后期打补丁”加上的 -
webpack-dev-server的hot: true会自动启用HotModuleReplacementPlugin,并启动HotUpdateChunk生成逻辑,同时改写publicPath以支持热更新资源加载 - 浏览器端必须调用
module.hot.accept()或import.meta.hot.accept()才能触发局部更新,否则即使服务推了新模块,客户端也无响应
如何让自定义插件真正参与 HMR 流程?
如果你在写一个类似 vite-plugin-react 或 webpack-plugin-i18n 的插件,并希望它支持 HMR,关键不是“接入 HMR”,而是“适配 HMR 的更新生命周期”:
- 在插件中监听
import.meta.hot?.accept,并在接受到更新时执行清理或重初始化逻辑(例如卸载旧的 i18n 实例,加载新翻译) - 避免在模块顶层直接操作 DOM 或全局状态,否则 HMR 替换后旧引用仍存在,造成内存泄漏或状态错乱
- 对 CSS/JSON/MD 等非 JS 资源,需确保 loader 层已支持 HMR 回调(比如
css-loader默认启用module.hot.accept,但自定义 loader 必须显式实现) - 使用
import.meta.webpackHot(Webpack 5.30+)替代老旧的module.hot,更符合 ESM 规范且类型更安全
Webpack 与 Vite 在 HMR 插件设计上的根本差异
Webpack 的插件系统面向构建阶段(compilation, emit),而 HMR 是运行时行为;Vite 则把 HMR 作为 dev server 的核心能力暴露为 server.hot 对象,插件可通过 handleHotUpdate 钩子直接干预更新内容:
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
export default function myPlugin() {
return {
name: 'my-hmr-plugin',
handleHotUpdate({ file, server }) {
if (file.endsWith('.i18n.json')) {
// 主动触发相关模块的 HMR 更新
server.hot.send({
type: 'custom',
event: 'i18n-update',
data: { lang: 'zh-CN' }
})
}
}
}
}
这种设计让 Vite 插件可以“主动驱动”HMR,而 Webpack 插件只能“被动响应”构建结果。这也是为什么 Vite 的 HMR 更快、更可控——它的构建与开发服务器是一体的,不是两个进程拼起来的。
容易被忽略的边界问题
真实项目中,HMR 失效往往不是因为没开 hot: true,而是卡在这些细节上:
-
React组件默认不支持 HMR,必须用@vitejs/plugin-react或react-refresh-webpack-plugin注入刷新逻辑,否则改 JSX 只会全页刷新 - 使用
eval-source-map时 HMR 正常,但换成cheap-module-source-map可能因 sourcemap 映射不准导致模块无法被正确识别和替换 - 动态
import()加载的模块,如果没在父模块中调用import.meta.hot.accept('./chunk.js'),HMR 推送后不会触发更新 -
process.env.NODE_ENV在构建时被静态替换,HMR 不会重新计算环境变量,所以基于环境的条件逻辑(如if (DEV) {...})在热更新后可能不生效
HMR 不是开关,而是一条需要端到端对齐的链路。设计插件时,优先考虑“它在模块被替换后是否还能干净退出并重新挂载”,而不是“怎么把它塞进 hot 插件列表里”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










