HMR 需服务端、构建系统和运行时协同,核心是模块主动参与热替换;配置只需 devServer.hot: true 和兼容 loader;其保留状态因不重载页面而只替换模块代码并复用 DOM 实例。

HMR(Hot Module Replacement)在 Webpack 中不是开箱即用的“自动刷新”,它需要服务端、构建系统和模块运行时三方协同。配置本身简单,但真正让“局部刷新不丢状态”生效,关键在于模块是否参与了热替换逻辑。
基础配置只需两步
启用 HMR 的最小必要配置包含服务端开关与插件支持:
- 在 webpack.config.js 的
devServer中设置hot: true(Webpack 5+ 推荐方式),或显式添加new webpack.HotModuleReplacementPlugin() - 确保使用支持 HMR 的 loader,例如:
style-loader(处理 CSS)、vue-loader(Vue 单文件组件)、@pmmmwh/react-refresh-webpack-plugin(React 函数组件) - 无需额外写 HTML 或启动脚本,
webpack-dev-server启动后会自动注入 HMR runtime 脚本到页面中
为什么局部刷新能保留输入框、弹窗等状态
根本原因在于:HMR 不触发 window.location.reload(),而是由浏览器端的 HMR runtime 控制“只换模块,不动 DOM 树上已存在的节点”。具体机制如下:
- 当 JS 模块变更,Webpack 重新编译后生成两个产物:
hash.hot-update.json(描述哪些 chunk 变了)和对应的xxx.hash.hot-update.js(新模块代码) - 通过 WebSocket 主动推送更新通知,浏览器收到后拉取新 chunk,并执行其中的
module.hot.accept回调 - 回调里由开发者或 loader 内置逻辑决定如何“安全替换”——比如
style-loader会替换<style>标签内容;vue-loader会复用已有 Vue 实例,仅更新模板与响应式数据 - 表单输入、滚动位置、Modal 显示状态等都依附于 DOM 或 JS 对象实例,只要这些对象没被销毁重建,状态自然保留
手动控制热替换逻辑(进阶场景)
对于自定义模块(如纯 JS 工具函数、非框架组件),若需精确控制更新行为,需显式编写接受逻辑:
- 在模块顶部检查
module.hot是否存在(生产环境为undefined) - 调用
module.hot.accept('./dep', callback)声明依赖更新时的处理动作 - 在回调中完成清理(如卸载旧事件监听器)、重建(如重新渲染子组件)、状态迁移(如将旧表单值映射到新结构)
- 若未接受更新,HMR 会向父模块冒泡;若冒泡至入口仍无人处理,则退化为整页刷新
常见失效原因与排查方向
即使配置正确,HMR 也可能“看似没反应”或“仍刷新页面”,典型情况包括:
- 修改了入口文件(如
index.js)且未在其中写accept逻辑 → 触发冒泡失败,最终整页刷新 - 使用了不兼容 HMR 的 loader(如原始
raw-loader或未配置react-refresh的老旧 React 项目) - 模块导出方式不满足要求(例如导出一个立即执行函数而非可替换对象)
- 浏览器控制台报错
Cannot apply update. Need to do a full reload!,说明某层模块拒绝或无法处理更新


















