HMR 热模块替换原理与实现

💡 什么是 HMR?

模块热替换(HMR)允许你在修改文件后,浏览器不刷新整个页面,而是只替换发生变更的模块代码,保留应用状态(如输入、滚动、Vue/React 状态等)。

🎯 为什么 Vite 的 HMR 更快?

传统构建工具(如 Webpack):

  • 构建一个 bundle
  • 检测到文件变化后 重新构建整个模块图
  • 发给客户端热更新代码块

Vite:

利用原生 ESM:模块级热替换只替换实际变更的模块及其依赖链 ✅ 无需重新打包整个项目

⚙️ 工作原理详解

HMR 步骤示意

你修改了某个文件
  Vite Dev Server 监听到文件变化
    判断该模块是否支持 HMR
 如果支持
        只重新请求该模块(通过 import)
        通知客户端替换模块并触发更新回调
 如果不支持
        全页刷新

Vite 的 HMR 机制图

[文件修改] → Vite Dev Server (WS 通知客户端)

            [客户端 HMR runtime 触发回调]

                import 新模块 执行热更新

🔧 模块是如何注册 HMR 的?

在 React/Vue 应用中,Vite 自动注入如下逻辑:

if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // 自定义热替换逻辑
  })
}

你也可以手动写:

import { state } from './store'
 
if (import.meta.hot) {
  import.meta.hot.accept('./store', (newModule) => {
    console.log('Store updated!', newModule.state)
  })
}

🧩 插件开发者如何接入 HMR?

Vite 插件可以注册 HMR 处理钩子:

handleHotUpdate({ file, server, modules }) {
  if (file.endsWith('.custom.js')) {
    // invalidate the module or send custom event
  }
}

🚧 HMR 支持 vs 不支持的模块类型

模块类型是否支持 HMR备注
.vue 组件内部使用 vue plugin 接管
React 组件配合 @vitejs/plugin-react
CSS/LESS/SASS样式变更会动态注入 style tag
JSON / 静态资源直接全页刷新
后端接口返回非前端模块不受控

❓问答卡片

Q:Vite 的 HMR 是如何实现的?和传统工具的差别在哪? A:Vite 使用原生 ESM 机制,实现模块级热替换,无需重新构建 bundle。修改文件后只替换变更模块,通过 import.meta.hot 接口注册更新回调。如果模块不支持热更新则自动全页刷新。相比 Webpack 更快、粒度更细。

🧠 进阶建议

  • 理解 import.meta.hot.accept() 的应用场景
  • 熟悉插件 handleHotUpdate 钩子写法
  • 会写一个自定义 HMR 模块(如 store、样式切换等)