DEBUG.WIKI
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →
腾讯云;让你的想法跑起来;轻量服务器 · 建站 / Docker / API;查看产品 →
阿里云;从建站到云上业务;云服务器 · 数据库 · 对象存储;查看产品 →

Vite 插件 reloadModule 后仍返回旧虚拟模块

Vite 8.3.5 在转换尚未结束时 reloadModule,可能复用并缓存旧结果。插件更新虚拟源后,先对同一环境的模块显式失效,再通知热更新。

插件已经把虚拟模块更新成新版本,也调用了 reloadModule(),浏览器后续请求却仍拿到旧代码,直到下一次额外失效才恢复。若虚拟模块的上一次转换还在等待异步工作,应检查两次操作是否发生了交叉。

Vite 8.3.5 有这条具体路径:v1 的慢转换未结束时,源数据改成 v2 并触发重载,旧转换仍可能被复用、写入缓存。单纯等待 HMR 消息发出,不代表原先的转换结果已不能再进入缓存。

HMR 时间戳和转换失效时间戳不同

正式实现中,待处理请求能否复用,以及转换结果能否写入缓存,会比较 lastInvalidationTimestamp。HMR 失效则使用另一项 lastHMRTimestamp。只沿热更新路径更新后者,不一定让旧转换在前面的比较中失效。

先保留插件实际输出的版本标记、转换开始与完成时刻、源数据更新和 reload 调用顺序。浏览器自身缓存、插件自己缓存了旧数据,以及访问到另一个开发服务,都要分开排除。

更新源数据后先让模块转换失效

插件掌握自己的虚拟源更新入口时,可先显式失效同一环境里的模块,再通知重载。下面是已有 configureServer 逻辑中的局部形状,不是完整插件:

const environment = server.environments.client;
const module = environment.moduleGraph.getModuleById(resolvedVirtualId);

if (module) {
  environment.moduleGraph.invalidateModule(module);
  await environment.reloadModule(module);
}

server 是当前 Vite 开发服务,resolvedVirtualId 是插件实际解析后的虚拟模块 ID,例如插件内部使用的带空字符前缀形式。代码应放在源数据已经更新之后;如果 load/transform 仍读取另一份旧快照,失效也无法生成新内容。

还没有进入模块图的虚拟模块可能取不到 module,此时不能伪造一个节点;它首次被请求时应从新的源状态生成。SSR 或其他 environment 也要在各自的图里处理,不能把 client 的模块对象交给另一个环境重载。

验证旧请求之后的新请求

显式失效不会让已经发给客户端的旧响应倒流改变,也不能取消插件在转换中自行启动的外部任务。插件有外部副作用时,需要按其原有取消或版本检查机制处理。

验收应让一次旧转换处于等待中,再更新源并执行失效和重载,最后检查旧转换结束后的新请求拿到最新代码。还要验证随后没有更多变更时,缓存不会继续固定在 v1;只看一次 HMR 通知或一次立即响应,没有覆盖旧结果晚到的窗口。

不要用固定 sleep 等待“转换应该结束”,也不要给所有虚拟模块随机加查询参数来制造无限新身份。保留原配置和变更前的插件代码,以便回退这次局部处理。前置失效针对的是这条并发路径,不能替代插件自己对外部任务和版本快照的管理。