Vite HMR 是如何做到修改代码后几乎秒更新的?
Vite HMR 的核心不是“重新打一个更小的包”,而是利用浏览器原生 ESM 和开发服务器的模块图,只更新真正变化的模块。文件改动后,Vite 服务器通过 WebSocket 通知浏览器,浏览器再按新的模块 URL 拉取更新内容。如果某个模块声明自己能接受更新,页面就不刷新;如果更新无法安全接住,Vite 才会退回整页刷新。
HMR 的基本链路
开发服务器启动后,Vite 会维护一张模块图,记录 URL、文件路径、导入关系和被哪些模块引用。浏览器加载页面时,每个源码模块都以 ESM 方式请求,比如 /src/App.tsx、/src/main.tsx。当你保存文件,文件监听器捕获变化,Vite 找到对应模块,再判断这次更新能不能沿依赖链被某个 HMR 边界接住。
浏览器和服务端之间有一条 WebSocket 连接。服务端发送的不是完整应用代码,而是类似“某个模块更新了,请重新 import 它”的消息。浏览器端 HMR runtime 收到消息后,会给模块 URL 加上时间戳,绕过浏览器缓存并重新加载。
jsif (import.meta.hot) { import.meta.hot.accept((newModule) => { console.log('module updated', newModule) }) import.meta.hot.dispose(() => { console.log('clean side effects') }) }
accept 表示当前模块愿意处理自己的更新,dispose 用来清理定时器、订阅、WebSocket 连接等副作用。很多业务项目不用手写这段,是因为 React、Vue 插件已经替你处理组件级刷新。
为什么 Vite 的 HMR 通常更快
Webpack 这类传统打包工具在开发期也能 HMR,但它的更新建立在打包产物和依赖图补丁之上。项目大了以后,重新编译和生成补丁的成本会变高。Vite 开发期不需要先把源码合成一个大 bundle,改哪个文件通常只转换哪个文件,速度不会随着项目模块数线性变慢。
这并不代表 Vite HMR 永远是几十毫秒。如果改动的是被大量模块共同依赖的基础文件,例如全局样式、路由入口、状态管理单例,更新仍然可能扩大影响范围。快的前提是模块边界清晰,框架插件能正确识别组件更新。
HMR 边界和整页刷新的取舍
HMR 最难的地方不是重新加载代码,而是保证状态不乱。比如 React 组件改了样式或 JSX,保留组件状态通常是好事;但如果改了 Hook 调用顺序、模块顶层副作用或全局单例,强行热替换可能比刷新更危险。Vite 的策略是尽量精确更新,但在无法确认安全时刷新页面。
开发时看到页面刷新,不一定是 Vite 慢,可能是当前模块没有形成可接受的 HMR 边界。常见原因包括:模块既导出组件又导出普通运行时变量、组件外部有不可清理的副作用、插件没有覆盖该文件类型。
ts// vite.config.ts export default { server: { hmr: { protocol: 'ws', host: 'localhost', port: 5173 } } }
在容器、远程开发机或反向代理下,HMR 失败常常不是代码问题,而是 WebSocket 地址不通。页面能打开但保存后没反应时,先看浏览器 Network 里的 WS 连接是否成功。
追问
HMR 和 Live Reload 有什么区别?
Live Reload 通常是文件变化后刷新整个页面,简单可靠,但页面状态会丢。HMR 只替换变化模块,理论上可以保留表单输入、组件状态和当前路由。取舍在于 HMR 需要模块声明更新边界,也需要框架插件配合。遇到状态被错误保留导致调试迷惑时,手动刷新反而更可信。
为什么有些修改会触发整页刷新?
因为 Vite 没找到安全的 HMR 接收方,或者插件判断这次更新不适合局部替换。比如修改入口文件、环境变量、模块顶层副作用,都会让局部更新风险变高。整页刷新是保守策略,不是失败。真正要排查的是为什么某个应该局部更新的组件没有形成边界。
React 或 Vue 项目为什么很少手写 import.meta.hot?
框架插件已经把常见组件更新逻辑封装好了。React Fast Refresh 会判断组件签名,Vue 插件会处理 SFC 的 template、script、style 更新。业务代码手写 HMR 主要出现在自定义状态容器、插件开发、WebSocket 客户端或非组件模块里。边界是:插件只能处理它理解的模式,写法太混合时仍可能退回刷新。
HMR 失效应该从哪里排查?
先看浏览器控制台和 Network,确认 WebSocket 是否连上。再看终端是否捕获到文件变化,容器挂载、网络盘和 WSL 场景都可能让文件监听不稳定。然后检查模块是否有语法错误或循环依赖,因为更新模块加载失败会中断本轮 HMR。不要一开始就改 Vite 配置,先确认链路上的“文件变化、消息发送、浏览器拉取”三步是否成立。
HMR 会影响生产环境吗?
不会,HMR runtime 只在开发环境注入,生产构建不会依赖它。需要注意的是,HMR 让开发反馈很快,但也可能掩盖初始化流程问题,因为页面没有从零加载。涉及登录态初始化、全局缓存、首屏请求顺序时,偶尔手动刷新页面是必要的。生产问题最终还是要用 vite build 和真实构建产物验证。