导读:本期聚焦于小伙伴创作的《Webpack HMR 热更新原理:如何在不刷新页面的情况下替换模块》,敬请观看详情。调试前端项目时,最影响效率的往往不是写代码,而是每次改完样式或逻辑都要整页刷新,状态全丢。Webpack HMR 就是为解决这个痛点设计的机制,它能在应用运行期间把修改过的模块悄悄换掉,页面不跳转、数据不重置。这套能力背后依赖的是开发服务器与浏览器的长连接通信、模块热替换接口以及依赖树比对。理解它运作的方式,能帮你更快定位热更新失效的原因,也能在自定义 loader 或插件时主动支持热替换,让本地开发体验更顺滑。

Webpack HMR(Hot Module Replacement)是 Webpack 提供的一种在运行时更新模块的能力,它允许开发者修改代码后,浏览器不重新加载整个页面,只替换发生变化的模块并保留应用当前状态。这种机制大幅缩短了前端开发中的反馈周期,尤其在处理复杂表单、动画或带有大量交互状态的页面时优势明显。

Webpack HMR 热更新原理:如何在不刷新页面的情况下替换模块

一、HMR 的整体运作流程

HMR 并不是浏览器原生支持的能力,而是 Webpack 生态通过一套约定和工具链共同实现的。在开发模式下,Webpack 会启动一个本地开发服务器(通常是 webpack-dev-server 或 webpack-dev-middleware 配合 express),这个服务器一方面提供打包后的资源,另一方面维护一个基于 WebSocket 或 SSE 的双向通道,用来向浏览器推送编译状态。

当开发者保存文件,Webpack 监听到文件变化并重新编译后,不会生成完整的打包文件丢给浏览器,而是只产出“变更模块”的补丁信息。服务器通过长连接通知浏览器:哪些模块 hash 变了、新代码是什么。浏览器端的 HMR runtime 接收到消息,就会按照依赖关系图,把旧模块卸载、新模块装入,并尝试调用模块或父模块里写的 accept 逻辑来完成局部刷新。

1.1 编译阶段的模块标记

在打包过程中,Webpack 会给每个模块分配唯一的 moduleId 和 chunkId,并在模块代码外层包裹一层 HMR 接口调用。例如一个普通 JS 模块会被注入 module.hot 对象,用来注册“当自身或依赖被替换时该怎么做”。如果模块没有写接受逻辑,热更新就会向上冒泡,直到某个父模块接受,或者最终回退成整页刷新。

这种标记方式意味着 HMR 是否生效,很大程度取决于模块是否在代码里声明了热替换边界。像 React 项目常用的 react-refresh 插件,就是在编译时自动给组件加上 accept 处理,让组件能无感替换;而你自己写的工具函数若没处理,就可能触发页面重载。

二、浏览器端如何不刷新替换模块

核心在于“模块热替换接口”与“状态保留”。传统整页刷新会销毁 window 下的所有 JS 上下文,而 HMR 只动特定的 module 实例。Webpack 的 runtime 维护了一个模块缓存表,热更新时先删除旧模块的缓存引用,再执行新模块代码写入缓存,最后通知依赖方用新引用来继续运行。

对于 React、Vue 这类框架,视图层状态本来由框架自己管理(比如 React 的 Fiber 树、Vue 的响应式系统),只要组件函数被替换且组件没被强制卸载,框架就能用之前的虚拟 DOM 或状态树重新渲染。这就是“不刷新页面却像刷新了一部分”的根本原因:页面 DOM 和 JS 全局环境都在,只是局部逻辑被换掉。

2.1 module.hot.accept 的作用

accept 是 HMR 里最关键的方法。当某个模块调用 module.hot.accept('./dep.js', callback),就表示它愿意在 dep.js 更新时执行 callback 而不是刷新页面。如果 dep.js 自己也能 accept 自身,那连上层都不用动。写 webpack 配置或 loader 时,常常要检查目标模块是否暴露了 hot 接口。

下面用一个简单表格说明不同接受策略的差异:

策略写法示例更新行为
自身接受module.hot.accept()模块自我替换,不通知父级
接受子依赖module.hot.accept('./a', fn)a 变更时执行 fn,父模块不刷新
无接受逻辑未调用 accept冒泡至顶层,通常整页刷新

三、为什么有时 HMR 会失效

实际开发中常遇到改了代码却全页刷新,或者界面没变化的情况。常见原因包括:模块依赖链上没有任何 accept 边界;CSS 没走 style-loader 的 HMR 分支;使用了不支持热替换的自定义 loader 且未注入 hot 代码;以及服务端推送的 hash 与客户端记录不一致导致拒绝更新。

另一个隐蔽问题是状态被模块级变量锁死。比如你在模块顶部写了 const cache = {},热替换后新模块会有自己的 cache,但旧模块实例若被其他地方闭包引用,就可能读到过期数据。因此写可热替换的模块,应尽量避免把可变状态挂在模块作用域,或是在 accept 回调里手动迁移状态。

3.1 如何主动支持 HMR

如果你在写组件库或工具模块,可以在入口判断 if (module.hot) 并注册 accept,同时在回调里把必要的运行时状态从旧模块搬到新模块。对于 Vue 组件,vue-loader 已经内置了处理;对于普通 TS 模块,可以配合 hmr 注释或自定义插件来生成接受代码。

此外,保持 webpack 的 output.hash 稳定、 devServer.hot 设为 true、以及不使用过多手动清缓存的逻辑,都能降低热更新异常的概率。当 HMR 报错时,浏览器控制台通常会打印是哪个模块 reject 了更新,顺着堆栈就能找到缺失的 accept 边界。

四、小结

Webpack HMR 通过编译期注入热接口、开发服务器长连接推送差异、浏览器端按模块替换这三步,实现了不刷新页面换模块。它依赖明确的接受边界和框架层的状态保留能力。弄清原理后,不仅能更快排查热更新失灵,也能在搭建脚手架时把本地开发体验做得更流畅。

Webpack_HMR热更新原理模块替换修改时间:2026-08-11 20:09:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。