HMR 原理

1. 面试题

Q: Webpack 的 HMR (Hot Module Replacement) 是怎么工作的?原理是什么?

2. 核心解答

HMR 可以在不需要刷新页面的情况下,把修改后的模块代码替换到浏览器中,并保留应用的当前状态。

(1) 工作流程

  1. 启动 Dev Server (WebSocket):Dev Server 与浏览器建立 WebSocket 长连接。
  2. 监听文件变化 (Watch Mode):当源码发生变化时,Webpack 重新编译。
  3. 生成 HMR Manifest (Json/Js):编译完成后,Dev Server 通过 WebSocket 告诉浏览器:"有新的 Hash 了,快去更新!"。
  4. 下载 HMR Update (Jsonp):浏览器端收到通知,通过 AJAX 请求最新的 Manifest (包含了哪些模块变了),然后请求对应的 Chunk 更新文件 (.hot-update.json.hot-update.js)。
  5. 模块替换 (HMR Runtime):HMR Runtime 拿到新的模块代码,找出依赖它的旧模块,执行这些模块的 Accept 回调。如果该模块没有 Accept 逻辑,则沿着依赖树向上冒泡,直到根节点。如果还没人处理,则触发整页刷新 (Live Reload)

(2) 关键 API

在你的入口文件中,通常会有这样的代码:

if (module.hot) {
  module.hot.accept('./App', () => {
    // 收到更新后的回调
    render(App);
  });
}

如果没有这个 module.hot.accept,HMR 就会失败,退化成全量刷新。好在现代框架 (Vue Loader / React Refresh) 帮我们自动注入了这些代码。

(3) 实现细节

  • Compiler:监听文件变化。
  • Server:提供静态资源服务,并通过 WebSocket 推送 Hash。
  • Client (Client.js):在浏览器端接收 WebSocket 消息。
  • HMR Runtime:在浏览器端处理模块热替换逻辑。

3. 面试加分项

Q: 为什么 CSS 不需要手动配置 HMR?

因为 style-loader 内部实现了 module.hot.accept。当 CSS 变化时,它会移除旧的 <style> 标签,插入新的 <style> 标签,从而实现无刷新样式更新。

Q: HMR 和 Live Reload 有什么区别?

  • Live Reload:只要文件变化,就强制刷浏览器 (F5)。所有状态 (Redux/State) 会丢失。
  • HMR:只替换变更的模块,保留状态 (State Preservation)。开发体验极佳。

4. 总结

  • WebSocket: Server push update signal.
  • Manifest: Hash based update list via AJAX.
  • Runtime: Replace module code via JSONP.
  • Accept: Determine update handling logic (Vue/React handles it).