跳转到内容

热更新实现原理

vpt 的微信热更新不是浏览器 HMR 的直接移植。它提供三种开发更新模式:

  • devtools 把原生 JavaScript 补丁写入 dist/wx,借助开发者工具的 Page 热重载执行,再保护原 Taro/React 页面连接;
  • interpreter 通过 Vite 已有 WebSocket 推送源码,由 App 中的 Sval 安装模块实现,不重新注册 Page;
  • rebuild 不交付增量补丁,每次有效源码变化都让 Rolldown 写出完整原生项目并重启 App。

devtoolsinterpreter 共享补丁序号、模块图更新、React Refresh、样式事务、确认和完整构建恢复,只改变补丁如何到达并安装到存活的 App 运行时。一次开发服务器只选择一种模式,更新路径中没有逐补丁模式判断。

使用方法和常见问题参见开发热更新。本页解释内部设计。

微信开发期间会发生三种容易混淆的事情:

变化被替换的内容状态结果
模块热更新App 内存中的部分 JavaScript 模块App 和兼容的 React 组件状态保留
开发者工具替换 Page(仅 devtools当前微信 Page 对象vpt 保留原 React/Taro 页面连接并抑制替换生命周期
完整构建整个 dist/wx 代码基线和 App 运行环境所有运行时状态重置

devtools 的成功更新通常同时包含前两项,页面交接使第二项看起来像没有发生。interpreter 只执行第一项。rebuild 对每次有效变化都直接执行第三项。

在补丁模式中,完整构建是恢复手段。只要局部替换无法证明安全,vpt 就生成新基线并允许开发者工具重启 App。

新代码需要环境允许的执行机制

Section titled “新代码需要环境允许的执行机制”

小程序不能把网络收到的源码交给 eval()Function()devtools 因此先把补丁写入 dist/wx,由微信开发者工具编译为原生 JavaScript。interpreter 则把源码当作数据交给 Sval 的解析器和解释执行器,不调用动态代码生成 API。

解释器只存在于开发运行时。生产构建仍然全部是开发者工具可编译的静态项目文件。

devtools:改了哪个文件,决定重载多大范围

Section titled “devtools:改了哪个文件,决定重载多大范围”

开发者工具根据发生变化的文件及其已有依赖关系决定如何重载:

  • 根入口 app.jsapp.wxss 变化,会重建整个 App 运行环境;
  • Page 已经直接依赖的 JavaScript 文件变化,可以只重新执行 Page;
  • 页面开始重载后才动态发现的新依赖,不能反过来改变这次重载范围。

所以,每个 Page 入口必须从第一次构建起就包含一个字面量依赖:

require('../../hmr/patches.js')

路径按页面位置生成,但所有页面最终指向同一个 hmr/patches.js

React 状态只能存在于没有被销毁的树中

Section titled “React 状态只能存在于没有被销毁的树中”

React Refresh 不会把状态序列化后重建。它只能更新仍然存活的 React 树。vpt 必须避免普通更新重启 App,并把模块运行时放在 App 共享的微信全局环境中,而不是放在会被重新执行的 Page 中。

部分职责
Rolldown 开发引擎监视源码,按所选模式生成模块补丁或完整输出
vpt 开发主机串行处理构建结果、样式、累计补丁、交付和运行时报告
DevTools 交付把累计补丁渲染为 hmr/patches.js
Interpreter 交付通过 Vite WebSocket 推送新发布的累计日志
App 模块运行时保存模块图、缓存、热更新边界和已应用序号
Sval(仅 interpreter解析并解释 Rolldown 模块注册程序与更新后的模块实现
React Refresh判断组件边界是否兼容,并更新现有 React 树
Taro 页面交接(仅 devtools在开发者工具重新注册 Page 时保留原页面连接

微信开发模式复用 Vite 已经解析好的打包开发配置,并创建一个直接写入 dist/wx 的 Rolldown 开发引擎。不会再创建第二个文件监听器或第二张模块图。

第一次完整构建会建立以下约定。

开发文件名不包含内容哈希。App、Page、普通代码块和补丁模式的 HMR 文件都能在后续完整构建中覆盖同一路径,开发者工具也可以持续观察这些文件。

开发模式不会清空并重建整个输出目录。已有路径会保留,再由新构建覆盖需要更新的文件,避免破坏开发者工具对项目目录的监听。

补丁模式中,每次成功并交付给开发者工具的完整构建都有一个新的 buildId。主机把它和已认证的 Vite WebSocket 地址写入:

hmr/info.js
module.exports = {
buildId: '本次完整构建的唯一标识',
endpoint: 'ws://127.0.0.1:<port>/__vpt_hmr__?token=<vite-token>'
}

App 入口在业务模块执行前读取该文件并初始化共享模块运行时。旧构建延迟到达的补丁或报告会因为 buildId 不匹配而被忽略。

初始 hmr/patches.js 不包含更新:

module.exports = undefined

每个 Page 入口仍然会同步读取它:

__rolldown_runtime__.applyPatches(
require('../../hmr/patches.js'),
'pages/example/index'
)

第一次执行什么也不会发生,但开发者工具已经记录了 Page 到补丁文件的直接依赖。以后只需替换这个文件,就能触发所需的 Page 重载。

解释器模式不给 Page 添加补丁依赖,也不生成 hmr/patches.js。App 连接 hmr/info.js 中的 WebSocket,源码发布时主机直接广播累计补丁。

每个 App 运行环境只创建并保留一个原生 SocketTask,不会重建连接,也没有轮询、心跳或插件定时器。构建身份切换和主机关闭通过同一通道通知旧 App 关闭该连接。

点击开发者工具的「编译」会创建新的 App 运行环境,但不会自动重启 Vite 或更新磁盘上的完整代码基线。此时 hmr/patches.js 可能只剩旧 App 尚未确认的后缀,新 App 不能跳过缺失的前缀直接执行它。

Socket 连接建立后,小程序发送 { kind: 'startup', buildId },通知开发服务自己刚刚启动。如果当前构建还没有产生过热更新补丁,就直接继续运行;如果已经发布过补丁,开发服务就根据最新源码重新生成完整代码,并让小程序重新加载。即使旧补丁仍然齐全,也采用完整重建,而不是在启动时逐个重放。遇到不完整的旧补丁时,小程序会跳过它们,等待完整代码生成,不会强行执行。

Socket 打开前跳过 Page 补丁执行,不暂存补丁或 ACK;由启动报告让主机决定是否需要完整构建。启动报告不是应用确认,不会裁剪历史。正常更新若应用失败,共享运行时先发送完整构建请求,再关闭 Socket;后续补丁因没有可用连接而跳过,不保存失败原因或额外的失败标记。正常 HMR 仍在成功应用后报告真实序号。Socket 引用只在 onOpen 后可用于发送,关闭或报错时清除,不维护额外的连接状态或事件队列。

rebuild 不生成 hmr/info.jshmr/patches.js,也不注册 Rolldown 补丁客户端。开发引擎使用 rebuildStrategy: 'always',有效源码变化完成增量分析后会直接产生一次完整输出。主机在完整代码和样式落盘后最后更新带唯一标记的 App 样式入口,保证开发者工具把这一代文件作为 App 级刷新处理。

该模式复用原生模块运行时来执行开发代码块并满足 Vite 生成代码中的 import.meta.hot 接口,但不初始化补丁 Socket、入口依赖或 Page 交接。

模块缓存、模块引用关系、热更新边界和补丁序号都保存在 App 共享的运行时中。devtools 的 Page 重新执行时取得同一个实例;interpreter 的 WebSocket 也由该实例持有。两种补丁模式都把新实现应用到现有模块图,而不是从空状态开始。

开发构建还会使用应用实际的 Taro 模块实例,并在 React 渲染器运行前安装 React Refresh 所需的全局 Hook。这里不能另行导入一份 Taro,否则 DevTools 页面交接和解释器更新都会面对与应用不同的模块实例。

保存源码 ─┬─ rebuild: Rolldown 完整输出 → App 样式构建标记 → App 重启
└─ 补丁 → global.wxss → 累计补丁日志
├─ devtools: patches.js → Page 重载 → 原生工厂
└─ interpreter: Vite WebSocket → Sval 安装源码
共享运行时验证并一次应用整个批次
React Refresh → WebSocket 报告已应用序号

rebuild 在完整输出后结束本次更新。devtools 随后还会完成 Page 生命周期交接;interpreter 的 Page 从未被重新注册。下面主要按两种补丁模式的共享顺序和差异展开。

每次源码变化会得到以下结果之一:

结果含义
无变化没有需要发布的运行时更新
模块补丁包含变化模块的新注册程序,可以尝试局部替换
完整构建变化不能表示为安全的局部更新

语法错误等中间状态不会立刻触发完整构建。vpt 记录错误并继续运行上一份有效代码;下一次保存恢复有效语法后,开发引擎可以继续产生正常补丁。

连续保存时,vpt 会短暂等待这一轮编译结果结束,再合并成一次磁盘通知。这段等待只减少文件写入次数,不会丢弃中间补丁:后一个补丁是基于前一个补丁产生的增量,必须按原顺序全部保留。

一次补丁发布遵守固定顺序:

  1. 根据最新模块图重新生成需要变化的全局 WXSS;
  2. 让所选模式发布累计补丁:devtools 原子替换文件,interpreter 通过 Vite WebSocket 广播源码;
  3. 按补丁顺序通知 Rolldown,这些补丁已经可交付。

所有构建结果、样式、补丁、运行时报告和完整构建切换都经过同一条串行队列。交付成功只推进 Rolldown 的“已发布”前沿;App 通过 WebSocket 成功应用序号后,日志才推进“已应用”前沿。

devtools 文件使用“同目录临时 .txt 文件 + rename”替换。开发者工具忽略临时文件,只会看到完整的最终内容。interpreter 不写 JavaScript 文件,也不复制第二份发布状态;每次广播直接序列化本次累计补丁发布。

3. devtools 补丁文件保留尚未确认的完整序列

Section titled “3. devtools 补丁文件保留尚未确认的完整序列”

hmr/patches.js 不是“最新补丁”,而是“当前 App 尚未确认应用的全部补丁”。例如,App 已应用到序号 3

主机产生 4 → 文件包含 [4]
Page 尚未读取
主机又产生 5 → 文件包含 [4, 5]
Page 这时读取 → 一次得到 [4, 5]
App 报告已应用 5 → 主机删除不大于 5 的历史

这解决了开发者工具合并或错过中间文件事件的问题。即使 Page 没有观察到只含 [4] 的版本,下一版仍携带从它当前状态到最新状态所需的所有注册程序。

补丁文件大致如下:

module.exports = {
buildId: 'current-build',
patches: [
{
seq: 4,
changedIds: ['src/report.tsx'],
factory: () => {
// Rolldown 生成的新模块图和模块实现
}
},
{
seq: 5,
changedIds: ['src/chart.tsx'],
factory: () => {
// 下一次增量
}
}
]
}

补丁文件本身只导出数据,不会擅自修改运行时。Page 入口明确把它交给 App 模块运行时。

4. devtools Page 在业务代码之前应用补丁

Section titled “4. devtools Page 在业务代码之前应用补丁”

开发者工具发现 hmr/patches.js 变化后,会重新执行依赖它的存活 Page。Page 入口先调用 applyPatches(),之后才继续加载页面业务模块。因此本次重新执行看到的是新模块状态。

多个 Page 可能依次读取同一份补丁。App 运行时使用序号跳过已经应用的部分,但仍会为每个被开发者工具替换的路由建立独立的页面交接。

4b. interpreter 接收 WebSocket 源码并安装

Section titled “4b. interpreter 接收 WebSocket 源码并安装”

主机把当前尚未确认的补丁 { buildId, patches: [{ seq, changedIds, code, ... }] } 作为 Vite 自定义事件发送。Sval 在一个 App 级沙箱作用域中解释每个 code;代码只登记模块图和模块工厂,应用模块仍由共享运行时稍后统一执行。

成功应用后,App 通过同一个 WebSocket 报告序号。解释器源码只在新发布时发送。每份源码携带构建身份;构建替换和主机关闭都会向旧 App 发送终止消息。

运行时先确认 buildId 属于当前 App,再按顺序读取补丁。对每一项:

  1. 已应用过的序号直接跳过;
  2. 新序号必须等于当前期待的下一个序号;
  3. 通过检查后,才执行这一项的模块注册程序并继续检查下一项。

补丁是增量,序号不能缺失或重复。若批次中途发现错误,前面连续部分的注册程序可能已经登记了新实现,但应用模块尚未重新执行,序号也不会被确认;运行时会立即请求完整构建,而不是继续使用这份不完整状态。

6. 一次切换到批次中的最新实现

Section titled “6. 一次切换到批次中的最新实现”

通过序号检查后,运行时先执行批次中所有补丁的注册程序。这些程序只登记新模块图和新模块实现,不立即运行应用模块。

随后运行时合并整个批次的变化模块,只做一次模块替换。连续保存 [4, 5, 6] 不会让 React 依次渲染三个中间版本;所有实现登记完成后,页面直接从当前版本切到序号 6 的版本。

对于每个已经执行过的变化模块,运行时沿“谁导入了它”向上查找:

  • 遇到调用过 import.meta.hot.accept() 的模块,就把它作为更新边界;
  • 尚未执行过的变化模块无需立即处理,新实现会在第一次导入时生效;
  • 如果走到顶层仍找不到更新边界,局部替换不安全;
  • 如果传播路径形成循环,运行时也放弃局部替换。

React 项目的更新边界通常由 @vitejs/plugin-react 生成,vpt 不另造组件边界。

找到边界后,运行时:

  1. 确认每个受影响模块都有可执行的新实现;
  2. 保存旧边界注册的接受回调;
  3. 在执行任何新模块之前,一次性清除整个受影响集合的旧缓存;
  4. 用最新实现重新执行边界;
  5. 把最新导出交给旧边界的接受回调。

统一清除缓存可以防止一个新边界读到另一个受影响模块的旧导出。遍历成本与本次实际触及的模块和引用关系成正比,即 O(V + E)

只有模块传播、模块执行和全部接受回调都成功后,运行时才更新“已应用序号”,并发送:

{
kind: 'applied',
buildId: string,
seq: number
}

主机收到后只删除该序号覆盖的补丁前缀。把“模式已经发布”和“App 已经应用”分开,才能在 Page 尚未处理文件事件或解释器尚未收到 WebSocket 消息时继续安全发布后续补丁。

组件签名、组件家族和边界兼容性仍由 @vitejs/plugin-react 与 React Refresh 判断。vpt 只适配它们对浏览器环境的假设:

  1. 为 React Reconciler 添加 Refresh 运行时的静态导入,在渲染器注册前完成初始化;
  2. 在 Refresh 运行时模块末尾调用 injectIntoGlobalHook(globalThis),把 React DevTools Hook 安装到 JavaScript 全局对象;
  3. 通过仅在小程序开发模式启用的 define,把 Refresh 的四个 window.* 协议属性映射到 globalThis.*。运行时与生成的组件边界共享同一个全局对象,不移除边界的前置检查。

前置初始化编译后为:

injectIntoGlobalHook(globalThis);
globalThis.$RefreshReg$ = () => {};
globalThis.$RefreshSig$ = () => (type) => type;

映射仅覆盖 $RefreshReg$$RefreshSig$__registerBeforePerformReactRefresh__getReactRefreshIgnoredExports。VPT 不会创建或整体替换 window;普通 windowtypeof windowwindow.document 以及局部声明的 window 保持原有语义。生产构建和 H5 不启用这些映射。

边界兼容时,React Refresh 在原有 React 树上更新组件,所以 Hook 状态得以保留。组件类型、Hook 顺序或导出形状不兼容时,Refresh 可以重新挂载局部组件;如果边界主动使本次模块更新失效,运行时会请求完整构建。

vpt 不读取或序列化 React 内部的渲染树,不复制 Hook 状态,也不创建第二棵 React 树。

devtools 如何在 Page 原生重新注册时保留页面

Section titled “devtools 如何在 Page 原生重新注册时保留页面”

模块补丁应用后,开发者工具会重新执行原生 Page 注册。这个动作不是普通页面导航,却会额外触发一组卸载、加载和显示生命周期。如果这些回调直接进入 Taro,仍然有效的 React 页面会被卸载,随后又以新的页面身份挂载,组件状态和业务上下文都会丢失。

Page 热更新涉及四层不同的状态,不能混为一个快照:

  1. Taro Page 配置createPageConfig() 创建的静态配置及其生命周期闭包,连接 Taro 页面身份和已挂载页面根;
  2. React 页面树:组件实例、Hook 状态和上下文,由 App 中存活的 React 根持有;
  3. Page 视图数据:原生 Page 的 data,保存 App 投影、普通 Page 节点和每个 CustomWrapper 的初始占位记录;
  4. CustomWrapper 视图数据:每个已挂载原生包装组件自己的 data.i,保存该边界下面的当前渲染快照。

Taro 会把 CustomWrapper 后代的后续更新直接发送给对应包装组件,因此第三层中的嵌套记录不会同步变成第四层的当前值。已经加载完成的懒组件尤其容易暴露这个差异:Page 初始数据仍可能是 SuspenseLoading…,而屏幕和 React 树早已显示真实组件。开发构建把应用图中的真实包装缓存发布到 globalThis 的 Symbol 属性,避免 HMR 启动块导入出第二个空缓存。

React Refresh 负责更新第二层。Page 重新注册既要保护第一层不被卸载,也必须在微信读取初始数据前把第三、四层拼成一个一致的原生快照。微信视图数据不包含 React Hook 状态,也不能用于重建 React 树。

vpt 在原生 Page 注册边界协调一次短暂过程:

  1. 保留原来的 Taro Page 配置和已挂载 React 页面;
  2. 把当前微信视图数据作为本次原生注册的初始数据;
  3. 跳过重新注册触发的卸载,避免 Taro 销毁页面根;
  4. 跳过重新注册触发的加载,避免 Taro 创建第二个页面身份;
  5. 跳过这一次显示回调,避免重复请求、埋点或业务状态初始化;
  6. 随后恢复普通生命周期转发。

初始数据通过注册配置直接提供。vpt 不深拷贝递归数据树,也不调用 setData() 额外发布整页差异,因此这一步不会替代或干扰后续 React Refresh 产生的真实更新。

为什么不绑定重新注册回调中的 Page

Section titled “为什么不绑定重新注册回调中的 Page”

微信调用重新注册生命周期时,会把一个临时 Page 对象绑定为回调中的 this。真实开发者工具行为表明,页面栈中原来挂载的 Page 才会继续显示并接收 React/Taro 更新。把 Taro 当前页面或页面根改绑到这个临时 Page,反而会让之后的更新脱离页面栈中的已挂载 Page。

因此 vpt 不修改 Taro 当前页面,不查找或重绑页面根,也不维护“旧 Page 到新 Page”的接管关系。原有 Taro/React 连接保持不动,React Refresh 继续在同一棵树上工作。

首次加载、正常跳转、返回、隐藏和真实卸载仍使用微信传入的原始 this Page 和参数进入 Taro。真实卸载会结束该 Page 的保留状态;以后再次进入时仍执行完整挂载流程。

每个静态 Page 配置独立管理自己的重新注册过程。当前页和页面栈中的隐藏页可以分别保留,无需路由映射、页面栈扫描或全局交接阶段。

WXSS 内容不通过 JavaScript 模块的更新边界交付。完整构建和增量补丁共用一个 WX 样式插件和同一种样式事务:

  1. Tailwind 扫描器把已有候选文件和编译依赖登记到 Rolldown;相关文件变化时,Rolldown 重新转换对应 Tailwind 入口;
  2. 入口复用自己的增量生成器,得到新的浏览器 CSS 和同一代原始类名集合;编译依赖变化时才重建生成器;
  3. Vite 继续执行 PostCSS、CSS Modules 和预处理器转换,vpt 在内置 vite:css-post 序列化浏览器 HMR 模块前记录最终模块 CSS;
  4. 使用 Rolldown 当前模块图,按 App 和配置页面的顺序选择仍然可达的最终 CSS;
  5. 合并为一份确定的全局级联,再对完整内容执行一次微信 WXSS 转换;
  6. 用步骤 2 的类名集合转换同一事务中的最终 JavaScript;
  7. 内容确实变化时才替换 assets/global.wxss

Tailwind 生成器只负责编译入口,不拥有补丁发布。它在入口存活期间保留增量扫描缓存;候选文件变化由 Rolldown 触发入口转换,新增和删除类名都会更新同一个权威集合。vpt 不在发布事务中重新扫描项目,不重复执行 Vite CSS 预处理,不绕过 Vite 读取物理样式文件,也不从 WXSS 反向解析类名。

增量更新不会改写根目录的 app.wxss。它始终导入 assets/global.wxss,所以只替换后者即可让样式生效,同时保留 App 运行环境。vpt 先写入匹配类名集合的 WXSS,再发布已经用该集合转换过的 JavaScript 补丁。发布前仅把补丁中的 Vite 浏览器 CSS 字符串置空;模块工厂、CSS Modules 导出、changedIds 和补丁序号保持不变。若最终字节没有变化,vpt 不写文件,也不会制造多余的开发者工具事件。

完整构建在最终输出阶段执行同一种图投影,用其中的类名集合处理 JavaScript,并用投影得到的 WXSS 替换 Vite 编译器样式。原生 Page 和组件 WXSS 随后单独输出,始终保持不透明。

rebuild 对每次有效源码变化执行完整构建。两种补丁模式只在以下情况无法继续局部替换时执行:

  • Rolldown 明确返回完整构建;
  • 已执行的变化模块找不到接受边界;
  • HMR 传播路径形成循环;
  • 补丁序号缺失或重复;
  • 受影响模块缺少新实现;
  • 新模块执行或接受回调抛错;
  • 热更新边界调用 invalidate(),包括 React Refresh 判定边界失效的情况。

补丁模式的完整构建完成后,主机按以下顺序建立新基线:

  1. 协调本次完整输出的全局 WXSS;
  2. 生成新的 buildId,停止使用旧 Rolldown 客户端身份;
  3. 通知旧 App 关闭 WebSocket,再旋转补丁日志身份并清空待确认补丁;devtools 同时清空补丁文件;
  4. 写入带有新身份和认证 WebSocket 地址的 hmr/info.js
  5. 最后写入带有新 buildId 标记的 app.wxss

最后一步是有意的 App 级文件变化。开发者工具随后重启 App,新运行时从序号 0 开始读取已经写好的匹配身份。旧 App 延迟发送的报告会被忽略。

rebuild 没有前四项补丁状态,只在完整输出和最终 WXSS 已落盘后写入新的 App 样式构建标记。两条路径都不尝试在完整构建后恢复 React 内部状态;新的磁盘基线和新的 App 运行环境就是恢复边界。

补丁模式都通过同一个 Vite WebSocket 自定义事件发送运行时报告:

type AppliedReport = {
kind: 'applied'
buildId: string
seq: number
}
type RebuildReport = {
kind: 'rebuild'
buildId: string
reason: string
}

applied 允许主机删除已经成功应用的补丁历史;rebuild 请求完整构建。每份报告都与补丁交付和构建切换按顺序处理,不存在单独的 HTTP 报告协议。

interpreter 的源码发布、构建切换和关闭由主机通过 WebSocket 主动推送。

维护这套实现时,最重要的不是某个具体构建钩子,而是以下顺序:

收齐且保留全部 Rolldown 增量
→ 发布新的 global.wxss(如果变化)
→ 通过所选模式发布累计补丁
→ 按序确认补丁已交付给 Rolldown
→ 等待 App 报告实际应用序号
写入完整输出
→ 协调最终 global.wxss
→ 通知旧 App 关闭 WebSocket
→ 创建新 buildId
→ 重置所选交付
→ 写入 info.js
→ 最后更新 app.wxss,让 DevTools 重启 App
写入完整输出
→ 协调最终 global.wxss
→ 最后更新 app.wxss 的唯一构建标记,让 DevTools 重启 App

破坏这些顺序会让页面看到混合构建、让主机过早删除补丁,或让新 App 读取旧身份。

  1. 一次服务器只运行一种开发更新模式,模式选择不进入增量热路径;
  2. rebuild 的有效更新只发布完整输出,不创建补丁文件、日志、客户端身份或运行时连接;
  3. 补丁模式的普通模块更新不能改写 App 入口或其他会重启 App 的根文件;
  4. devtools 的每个 Page 从初始构建起依赖同一个 hmr/patches.js
  5. 每个补丁模式 App 堆只创建并保留一条 Vite WebSocket,不重建连接,也不使用轮询定时器;
  6. 补丁模式的 App 模块运行时必须比 Page 活得更久;
  7. 补丁日志必须保留所有尚未确认应用的连续序号;
  8. 整个受影响模块集合必须在任一新边界执行前统一清除缓存;
  9. devtools 的 Page 重新注册必须保留已挂载页面,且不能绑定到临时 Page;
  10. React Refresh 必须复用存活的 App React 根;
  11. 新构建身份暴露给 App 前,所选交付必须已经重置;
  12. 样式必须先于对应 JavaScript 补丁发布;
  13. 任何无法证明连续且安全的补丁状态都必须回到完整构建。
源码名称本文中的名称含义
DevEngineRolldown 开发引擎生成完整输出和增量模块补丁
WxDevHostvpt 开发主机排队并执行所有磁盘输出、报告和构建切换
PatchJournal补丁日志保存构建身份和尚未确认应用的累计补丁
buildId构建身份区分两次完整构建的运行时和报告
seq / appliedSeq补丁序号 / 已应用序号验证增量连续性并释放已应用历史
HMR boundary更新边界调用 import.meta.hot.accept()、可以接收新导出的模块
MiniHmrMode开发更新模式选择补丁交付能力或完整重建策略,并提供对应运行时、入口改写和插件
Page re-registrationPage 原生重新注册devtools 用已挂载 Page 的数据再次注册配置,同时忽略临时 Page 生命周期
WxStylePluginWX 样式插件从当前模块图和源文件生成同一事务的全局 WXSS 与 JavaScript 类名集合