如何对 Vue 3 组件实现局部热更新
解读
面试官抛出“局部热更新”这一关键词,核心想验证两点:
- 你是否理解 HMR(Hot Module Replacement) 在前端工程中的定位——不刷新浏览器、只替换变更模块,同时保留组件状态与 DOM 位置;
- 在 Grunt 主导的构建体系 里,如何低成本、可落地地接入 Vue3 官方 HMR 能力,而不是“切到 Vite/Webpack 就完事”。
国内很多老项目仍用 Grunt,直接说“换构建工具”会被判为逃避问题;必须给出在 Grunt 生命周期内可运行的方案,并讲清与 Vue3 模板编译、ESM 运行时、WebSocket 推送三者的配合关系。
知识点
- Vue3 的 HMR 客户端:
@vue/runtime-core暴露的__VUE_HMR_RUNTIME__API,只要运行时环境注入import.meta.hot或module.hot,就能触发rerender/reload/dispose三种行为。 - Grunt 生态没有原生 HMR,但可以通过 grunt-contrib-watch + grunt-contrib-connect + WebSocket 手写桥接层 模拟“文件变更 → 浏览器接受 diff → Vue3 执行 HMR”。
- 浏览器端需要一段 <5KB 的 inline 脚本,用来:
- 建立 WebSocket 连接;
- 收到变更信号后,对
*.vue文件执行fetch + compile + eval; - 调用
__VUE_HMR_RUNTIME__.createRecord()与reload()完成局部替换。
- 编译环节:用 @vue/compiler-sfc 把单文件组件拆成
renderFn+style+hmrId,在内存中完成,不落盘,避免 Grunt 的 IO 瓶颈。 - 状态保持:借助 Vue3 的
keep-alive与script setup顶层const state = reactive({...})写法,只要组件类型签名不变,状态即可复用。 - 国内落地注意:
- 内网常禁用 8000+ 随机端口,WebSocket 端口必须写死 35729 并在 nginx 侧代理;
- 安全审计要求不允许 eval,可把编译结果生成 Blob URL,通过
import(/* @vite-ignore */ blobUrl)动态加载,绕过 CSP。
答案
“在 Grunt 体系里给 Vue3 做局部热更新,我分四步落地:
第一步,在 Gruntfile 中新增一个 watch 子任务,只监听 src/**/*.vue,一旦变更,触发自定义任务 vue-hmr。
第二步,实现 vue-hmr 任务:用 @vue/compiler-sfc 把改动文件编译成 { render, styles, hmrId } 三元组,通过 WebSocket 推送到浏览器,不落地磁盘,保证毫秒级响应。
第三步,浏览器端注入一段 3KB 的 hmr-client.js:
- 连接
ws://localhost:35729/grunt-hmr; - 收到推送后,用
new Function()执行 render 代码,拿到最新renderFn; - 调用
window.__VUE_HMR_RUNTIME__.reload('${hmrId}', newRender),Vue3 内部会做diff+patch,DOM 节点原地替换,状态不丢。
第四步,在入口 HTML 里加一段条件注释:
<!--[if development]><script src="/@grunt/hmr-client"></script><![endif]-->,
保证生产构建不会多 1byte。
整个流程零外部依赖、不碰 Webpack/Vite,在国有银行、运营商内网项目里已稳定跑两年,单次热更新 <300ms,完全满足开发效率要求。”
拓展思考
- 如果团队后续要迁移到 Vite,可以把这段 WebSocket 桥接逻辑抽象成独立 npm 包
grunt-plugin-vue3-hmr,暴露同 vite 的 import.meta.hot 接口,将来切构建工具时业务代码零改动,实现“先享受 HMR,再渐进式替换构建”的国内主流演进路线。 - 当项目组件超过 1000 个,全量编译会拖慢 watch;可以在
vue-hmr任务里加依赖图缓存,利用 Vue3 的<script setup>编译后的hmrId做哈希索引,只重新编译受影响的单文件,把热更新时间从 300ms 压到 80ms 以内。 - 若需支持 Vue3 + TSX,把
@vue/compiler-sfc换成esbuild-service,在内存里把 tsx 转译成 esm 再喂给 hmr-runtime,整体思路不变,但要把 sourcemap 也一起打过去,否则控制台行号对不上,会被测试同事投诉。