news 2026/9/7 5:31:39

Vite 插件 HMR Hook 迁移指南:从 handleHotUpdate 走向环境感知的 hotUpdate

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vite 插件 HMR Hook 迁移指南:从 handleHotUpdate 走向环境感知的 hotUpdate

Vite 插件 HMR Hook 迁移指南:从 handleHotUpdate 走向环境感知的 hotUpdate

【免费下载链接】viteNext generation frontend tooling. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vi/vite

本文围绕 Vite 仓库中的变更说明 docs/changes/hotupdate-hook.md 展开,面向 Vite 插件作者讲清楚三件事:为什么要用新的hotUpdateHook 替代handleHotUpdate、两者的接口与行为差异、以及可直接照做的迁移模式;并结合 packages/vite/src/node/server/hmr.ts 与 packages/vite/src/node/plugin.ts 的源码实现,还原 HMR 更新在各 Environment 中的真实执行顺序,帮助你在插件中正确定制 HMR 传播策略。

背景与动机

Vite 的 Environment API 把开发服务器拆分为多个模块执行环境(client、ssr 以及框架自定义的环境)。但旧版的handleHotUpdateHook 对所有环境只调用一次,传入的HmrContext中的模块列表混合了 Client 与 SSR 两个环境的ModuleNode。文档原文给出了这个旧接口的完整签名:

interface HmrContext { file: string timestamp: number modules: Array<ModuleNode> read: () => string | Promise<string> server: ViteDevServer }

随着框架逐步迁移到自定义环境,"一次调用 + 混合模块列表"的模型不再够用。官方计划弃用handleHotUpdate,改用对每个 Environment 各调用一次的hotUpdateHook:

interface HotUpdateOptions { type: 'create' | 'update' | 'delete' file: string timestamp: number modules: Array<EnvironmentModuleNode> read: () => string | Promise<string> server: ViteDevServer }

hotUpdatehandleHotUpdate的工作方式相同,但有三点关键区别:

  1. 按环境调用:Hook 会在每个 Environment 上各执行一次,通过this.environment获取当前正在处理更新的 DevEnvironment。
  2. 模块列表更纯净options.modules只包含当前环境的EnvironmentModuleNode,各环境可以定义完全不同的更新策略。
  3. 覆盖更多 watch 事件:不再只针对'update',还会在文件创建('create')与删除('delete')时触发,用type字段区分。

弃用时间表与试用方式

变更说明中的警告框明确标注了当前状态:

  • hotUpdate最早在v6.0中引入;
  • handleHotUpdate的弃用计划在未来某个主版本(future major)进行;
  • 官方目前并不建议立即放弃handleHotUpdate,插件作者可以继续留在旧 Hook 上;
  • 如果想提前试验并给官方反馈,可以在 vite 配置中使用future.removePluginHookHandleHotUpdate: 'warn'

这个配置项在源码中定义于 config.ts 的FutureOptions接口(第 581 行起),取值仅有'warn'一种;配置future: 'warn'(字符串简写)时会一次性启用所有 future 弃用告警,removePluginHookHandleHotUpdate也在其中:

// vite.config.js export default { future: { removePluginHookHandleHotUpdate: 'warn' } }

启用后,只要还有插件在使用handleHotUpdate,开发服务器就会输出告警。告警的产生逻辑在 deprecations.ts 中:warnFutureDeprecation检查config.future[type] !== 'warn'时静默返回,命中则输出黄字提示 "Plugin hookhandleHotUpdate()is replaced withhotUpdate().",并附上变更说明文档路径(docs/changes/hotupdate-hook,与本文对应的正是同一份文档)和调用栈,帮助插件作者定位到具体是哪一个插件触发了旧 Hook。触发点在 hmr.ts 中,仅当type === 'update'且插件没有定义hotUpdate时才走旧分支。

迁移指南:三种典型模式

官方迁移指南的核心思想是:过滤并收窄受影响的模块列表,让 HMR 更精准。下面三种模式与原文档一一对应,可直接照搬改写。

模式一:过滤模块列表(最简单的迁移)

handleHotUpdate({ modules }) { return modules.filter(condition) } // Migrate to: hotUpdate({ modules }) { return modules.filter(condition) }

两个 Hook 的签名在这个场景下几乎一致,只是modules元素类型从ModuleNode变为EnvironmentModuleNode。注意由于新 Hook 按环境调用,你可以在过滤条件里结合this.environment.name让不同环境执行不同的收窄逻辑。

模式二:返回空数组 + 手动失效模块并全量刷新

handleHotUpdate({ server, modules, timestamp }) { // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { server.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } server.ws.send({ type: 'full-reload' }) return [] } // Migrate to: hotUpdate({ modules, timestamp }) { // Invalidate modules manually const invalidatedModules = new Set() for (const mod of modules) { this.environment.moduleGraph.invalidateModule( mod, invalidatedModules, timestamp, true ) } this.environment.hot.send({ type: 'full-reload' }) return [] }

注意两处替换:server.moduleGraph变为this.environment.moduleGraphserver.ws.send变为this.environment.hot.send。官方 Environment Plugins 文档 中的示例还多了一个防御性写法——先判断this.environment.name !== 'client'return,因为full-reload通常只需要发给客户端环境;如果你的全量刷新确实需要波及 SSR 等环境,去掉该判断即可。

模式三:返回空数组 + 发送自定义事件

handleHotUpdate({ server }) { server.ws.send({ type: 'custom', event: 'special-update', data: {} }) return [] } // Migrate to... hotUpdate() { this.environment.hot.send({ type: 'custom', event: 'special-update', data: {} }) return [] }

客户端代码需要注册对应的处理函数(可通过同一插件的transformHook 注入),见 HMR API 文档:

if (import.meta.hot) { import.meta.hot.on('special-update', (data) => { // perform custom update }) }

补充一点返回值语义(来自 plugin.ts 的 Hook 文档注释):返回过滤后的数组表示收窄更新范围;返回空数组表示"已完全自定义处理";不返回任何值(void)则按 Vite 默认 HMR 流程正常执行——这也是新旧 Hook 共有的三种返回语义。

源码深潜:hotUpdate 的实际执行链路

阅读 handleHMRUpdate 函数 可以还原出一次文件变更后的完整链路,对理解"按环境调用"有直接帮助。

1. 按环境构建 HotUpdateOptions。文件变更(create/delete/update)被监听器捕获后,函数先构造共享的contextMetatypefiletimestampreadserver),然后遍历每个 Environment,用各自的environment.moduleGraph.getModulesByFile(file)取受影响模块,为每个环境生成独立的options存入hotMap(第 480-497 行)。一个值得注意的细节:当type === 'create'时,moduleGraph._hasResolveFailedErrorModules中的模块会被一并加入——也就是"之前解析失败、现在文件终于创建出来了"的模块,这正是旧 Hook 拿不到的能力。

2. 先跑 client 环境的 hotUpdate,再跑其余环境。源码中 client 环境被单独提到循环之前处理(第 516-595 行),其余环境在第二个循环中逐个处理(第 601-622 行)。在 client 循环内:

if (plugin.hotUpdate) { const filteredModules = await getHookHandler(plugin.hotUpdate).call( clientContext, clientHotUpdateOptions, ) // ... } else if (type === 'update') { warnFutureDeprecation(config, 'removePluginHookHandleHotUpdate', ...) // 调用 plugin.handleHotUpdate(旧 Hook) }

即同一个插件只会走新 Hook 或旧 Hook 之一,且每个环境使用的都是该环境自己的插件容器上下文(environment.pluginContainer.minimalContext),这就是this.environment的来历。

3. 新旧模块列表的双向同步。为了让旧 Hook 的语义不丢失,当hotUpdate返回了过滤结果时,源码会把过滤结果反向写回mixedHmrContext.modules(旧 Hook 的上下文):先按 id 过滤掉不在新列表中的模块,再把新列表中混合列表还没有的模块转换为向后兼容的ModuleNode补进去(第 523-545 行);反之旧handleHotUpdate返回的过滤结果也会同步回各环境的HotUpdateOptions.modules(第 559-593 行)。从源码结构看,这是一层过渡期的兼容桥——未来handleHotUpdate移除后,这条桥会随之删除。

4. 插件执行顺序与缓存。参与 HMR 的插件按pre/ normal /post排序,由 getSortedPluginsByHotUpdateHook 完成,排序依据是plugin.hotUpdate ?? plugin.handleHotUpdate,并按 Environment 缓存在WeakMap中(第 401-409 行),避免每次 Hook 调用重复建数组。

5. 各环境的 HMR 传播默认并行。所有插件 Hook 跑完后,真正的模块失效与更新传播由hmr(environment)内部调用updateModules完成,而各环境之间的调度交给可配置的 server.hotUpdateEnvironments:

hotUpdateEnvironments?: ( server: ViteDevServer, hmr: (environment: DevEnvironment) => Promise<void>, ) => Promise<void>

缺省时对所有 Environment 执行Promise.all并行传播(hmr.ts 第 667-678 行)。如果你的 HMR 策略要求环境之间串行或有依赖顺序,可以实现这个回调,例如按server.environments的顺序逐个await hmr(env)。仓库的测试 hmr.spec.ts 就演示了自定义hotUpdateEnvironments的用法,并验证了服务器重启期间不会调度过期的 HMR 任务。

另外两个边界行为值得插件作者知晓:被监听文件命中 config 等敏感路径时服务器会直接重启而不进入 HMR 流程;命中experimental.bundledDev时整个 Hook 链会被跳过(源码中标注了TODO: support handleHotUpdate / hotUpdate),即 bundled dev 模式下这两个 Hook 当前不生效。

插件作者行动建议

  1. 现阶段:继续使用handleHotUpdate是安全的,官方明确不建议现在迁移。
  2. 需要响应 create/delete 事件的插件(例如虚拟模块、按文件存在性切换行为的插件):这些能力旧 Hook 提供不了,应直接采用hotUpdate,并用options.type区分事件。
  3. 想提前排雷:打开future.removePluginHookHandleHotUpdate: 'warn',观察控制台告警与调用栈,确认插件内部及依赖的所有插件都已覆盖。
  4. 多环境策略:迁移后务必用this.environment.name/this.environment.config.consumer分支处理不同环境的差异,而不是沿用旧时代"混合模块列表 + 一个ssr布尔值"的判断方式。

综上,hotUpdate是 Vite 向 Environment API 收敛 HMR 能力的第一步:接口语义与handleHotUpdate一脉相承,迁移成本主要体现在server.ws/server.moduleGraphthis.environment.hot/this.environment.moduleGraph的机械替换上;而"按环境调用 + create/delete 事件 +server.hotUpdateEnvironments调度"才是它真正为多环境开发服务器带来的增量能力。

【免费下载链接】viteNext generation frontend tooling. It's fast!项目地址: https://gitcode.com/GitHub_Trending/vi/vite

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:30:53

OpenMAIC实测:一句话生成AI课堂的部署与生成链路拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:29:55

2.4G私有协议领夹麦方案:JL6976M单芯片一拖二全双工设计实践

简介&#xff1a;这是一份基于杰理JL6976M单芯片方案的2.4G无线麦克风领夹麦一拖二全双工SDK资源包&#xff0c;版本为v1.4.0_2t1&#xff0c;含软件与硬件设计资料。面向无线音频产品开发工程师、方案商及嵌入式学习者&#xff0c;适用于直播领夹麦、访谈麦克风等一对二全双工…

作者头像 李华
网站建设 2026/9/7 5:29:20

爬虫数据落库MySQL实战:编码、去重与批量写入全解析

简介&#xff1a;围绕“Python爬虫MySQL”这一组合&#xff0c;这套zip压缩包面向需要把网页数据抓取并入库的开发者&#xff0c;提供一套可直接运行的参考实现。压缩包共含17个文件&#xff0c;其中6个py脚本分别负责连接数据库、执行SQL查询、批量写入和参数化安全操作&#…

作者头像 李华
网站建设 2026/9/7 5:29:10

腾讯云AI Skills实战:把聊天Agent养成全能执行者

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:28:56

Ant Design Alert 组件设计语言解读:内容、类型与交互变体

Ant Design Alert 组件设计语言解读&#xff1a;内容、类型与交互变体 【免费下载链接】ant-design An enterprise-class UI design language and React UI library 项目地址: https://gitcode.com/GitHub_Trending/an/ant-design 本文基于 Ant Design 官方仓库中 Alert…

作者头像 李华
网站建设 2026/9/7 5:27:55

基于MicroPython的DMA链式触发与Scatter-Gather数据聚合实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华