模块热替换(HMR)是Webpack、Vite等构建工具提供的开发时能力,只更新修改的模块而不刷新页面或丢失状态,依赖运行时模块管理与构建工具协作,并非JS原生特性。

模块热替换(HMR)是什么?它不是刷新页面
模块热替换(HMR)是 Webpack、Vite 等现代构建工具提供的能力:在代码变更时,只更新被修改的模块,不触发整个页面刷新(location.reload()),也不丢失当前组件状态(比如表单输入、滚动位置、React 组件的 useState 值)。它不是语法特性,也不是 JavaScript 语言原生机制,而是构建工具在运行时注入的一套补丁逻辑。
为什么改一行 CSS 或 JSX 就能“局部更新”?
HMR 的核心在于运行时模块管理器与构建工具的协作。当文件变化时:
- 构建工具(如 Webpack Dev Server)检测到
Button.js变更,重新编译该模块,生成新的模块代码和module.hot更新指令 -
浏览器端的 HMR 运行时收到通知,调用
module.hot.accept('./Button.js', callback) - 回调中执行新旧模块的 diff,用新组件实例替换 DOM 中对应节点(React/Vue 等框架需配合
react-refresh插件才能保留状态) - 若未显式
accept,HMR 会向上冒泡,直到根模块或触发 full reload
注意:module.hot 仅在开发环境存在,生产构建中被完全剔除。
HMR 失效的常见原因和修复方式
你改了代码但页面还是整页刷新?大概率是 HMR 没接管成功。典型问题包括:
立即学习“Java免费学习笔记(深入)”;
- 没启用 HMR:Webpack 需配置
devServer.hot: true;Vite 默认开启,但若用了自定义服务器可能被覆盖 - 入口文件没加
module.hot.accept():尤其在非框架项目中,必须手动写,例如:if (module.hot) { module.hot.accept('./utils.js', () => { console.log('utils reloaded'); }); } - 使用了不兼容的打包插件:比如某些旧版
html-webpack-plugin会干扰 HMR 生命周期 - ESM 动态导入(
import())未被正确标记:Webpack 要求异步模块也支持hot,否则 fallback 到 reload
React 项目推荐用 @pmmmwh/react-refresh-webpack-plugin,Vue 项目用 vue-loader 内置支持 —— 别自己手写 accept,容易漏掉副作用清理。
Vite 的 HMR 和 Webpack 有什么实际差异?
两者目标一致,但实现路径不同:
- Webpack HMR 依赖客户端运行时(
webpack/hot/dev-server)+ 服务端事件推送(SSE),启动慢、内存占用高 - Vite 利用原生 ESM,变更时直接向浏览器发送
import请求新模块,无中间打包步骤,响应更快,且默认支持import.meta.hotAPI:if (import.meta.hot) { import.meta.hot.accept('./state.js', (newModule) => { updateState(newModule.default); }); } - 兼容性上:Webpack HMR 对 CommonJS 更友好;Vite 的
import.meta.hot在 Node.js 环境不可用,只适用于浏览器端开发
HMR 不是银弹。它对全局副作用(如 document.title = 'xxx')、未导出的闭包变量、Web Worker 中的模块基本无效 —— 这些地方改了依然要手动刷新验证。











