一次改动,四条运行路径
沿着四条实现路径,看原始配置、运行值和插件资源怎样变化。切换输入,播放步骤,再检查完成后的结果。切换路径会保留步骤,重播可从头观察。
01 / 选择改动
同一个 Fiber,重新执行一次插件
fixed 从 A 变成 B。默认 internal/update 末端选择 restart,已登记资源先退出,再按新配置执行。
真实 Loader 实验 03–06保留插件资源,只替换引用中的快照
仅 live、count 改变,普通有效值一致。Loader 完整校验候选后,先提交所有引用,再通知拥有它们的 Fiber。
真实 Loader 实验 07–12候选没有通过验证,旧运行值继续可用
live 声明为字符串,却收到数字 42。这个分支保留原始输入,记录警告,不替换正在运行的引用。
真实 Loader 实验 08先验证模块能导入,再替换运行实例
dsh HMR 在统一队列内分析依赖和缓存。以下是正常替换顺序;失败恢复与权限边界见正文。
固定源码分析 · 本次未运行 HMR02 / 顺序与等待点
- 01提交新配置配置树 → Entry
Entry 接收 fixed: B,并保留新的原始 options。
保存新原始值原始配置 → LoaderEntry 与 Fiber 保留新的原始配置,以供后续激活使用。
保留新原始输入新输入 → EntryEntry.options.config 与 Fiber._config 已保留 live: 42。
备份并清理缓存HMR 队列 → 模块缓存选定受影响模块;保存旧 Fiber、entry 与当前原始配置。
- 02解析并更新Entry → Fiber
候选配置通过钩子与 schema 后,进入 internal/update。
比较原始改动Loader → 候选与引用只有 config 变化、Fiber ACTIVE、父 Context 未变,才考虑快速路径。
进入候选解析Entry → 校验与日志原始改动符合快速路径条件,开始解析并验证候选。
重新导入入口模块缓存 → HMR 队列所有替换入口先导入;此时尚未删除旧 runtime。
- 03开始释放资源Fiber → 插件资源
默认 restart 先发起卸载,已登记资源进入清理。
解析并验证候选Loader → 候选与引用执行真实 internal/config 与 schema,得到新的候选运行配置。
候选验证失败校验与日志 → Entry字符串字段收到数字,验证抛出错误,进入 catch 分支。
删除旧 runtimeHMR 队列 → 旧实例等待保存下来的旧 Fiber 完成清理。
- 04等待旧资源退出插件资源 → Fiber
异步清理完成后,才继续本轮装载。
检查普通有效值候选与引用 → Loader普通有效值必须保持相同;否则回到普通更新。
记录校验警告Entry → 校验与日志Loader 记录失败;原始输入不会在这里自动还原。
创建替换实例HMR 队列 → 新实例按原命名空间选择实现,恢复对应 entry 关联。
- 05按新配置 applyFiber → 插件资源
插件再次执行,按 fixed: B 登记新的资源。
提交全部快照Loader → 候选与引用live 与 count 的引用身份保持不变,当前值一起更新。
跳过快照提交Entry → 运行引用候选没有提交,运行引用依然返回 one。
等待新实例稳定新实例 → HMR 队列等待新 Fiber 与 Loader;稳定并不一律等于 ACTIVE。
- 06生命周期稳定插件资源 → Fiber
Fiber 对象与 uid 保持不变,实验轨迹以新 apply 完成为终点。
通知 ownerLoader → owner Fiber事件带有字段路径;通知前所有引用已提交,其他实例不接收。
确认运行状态运行引用 → Entry实验终点:Fiber 仍 ACTIVE,没有 volatile 更新事件。
提交命名空间记录HMR 队列 → 模块缓存成功后更新 entry.moduleNamespace,再发 hmr/reload。
03 / 完成后快照
固定示例结果,不是实时应用状态// 前后都由 Entry 保存原始输入
之前 { fixed: "A", live: "one" }
之后 { fixed: "B", live: "one" }apply(A) dispose(A) 完成 apply(B) Fiber 与 uid 保持不变;运行配置重新解析。
internal/config → schema internal/update → next() restart → 清理 → apply 省略 next() 的插件可以消费更新。
查看实现依据与边界
vendor/loader/src/config/entry.ts:117–157 · vendor/cordis/src/fiber.ts:736–752
真实 Loader 实验 03–06。普通更新可被 internal/update 拦截;HMR 回滚不撤销任意外部副作用。完整条件与失败路径见正文。
03 / 完成后快照
固定示例结果,不是实时应用状态// schema 中 live、count 标为 volatile
之前 { live: "one", count: 1 }
之后 { live: "two", count: 2 }
fixed 的有效值保持 A。const reference = config.live reference.get() // "two" config.count.get() // 2 config 和 reference 仍是原对象。
loader/volatile-update paths: [["live"], ["count"]] owner 回调读到 two / 2。 同插件的另一个实例仍读到 one / 1。
查看实现依据与边界
vendor/loader/src/config/entry.ts:141–197 · vendor/cosmokit/src/volatile.ts
真实 Loader 实验 07–12。普通更新可被 internal/update 拦截;HMR 回滚不撤销任意外部副作用。完整条件与失败路径见正文。
03 / 完成后快照
固定示例结果,不是实时应用状态Entry.options.config.live // 42 Fiber._config.live // 42 原始输入已变化。
config.live.get() // "one" Fiber.state // ACTIVE 原引用没有替换。 下次激活仍可能因原始输入失败。
候选解析抛出验证错误 Loader 记录 warning 无 loader/volatile-update 这不是原始配置的自动回滚。
查看实现依据与边界
vendor/loader/src/config/entry.ts:149–175
真实 Loader 实验 08。普通更新可被 internal/update 拦截;HMR 回滚不撤销任意外部副作用。完整条件与失败路径见正文。
03 / 完成后快照
固定示例结果,不是实时应用状态替换前保存: entry.options.config entry.moduleNamespace 旧 runtime 的 Fiber 列表 不重新按包名猜旧模块的位置。
旧 Fiber 清理完成 → 新 Fiber 注册并等待 → Loader 稳定 失败时尝试恢复缓存与旧实现, 不承诺外部副作用被撤销。
runExclusive → partialReload → 等待旧 / 新实例 → 更新 entry 命名空间 → hmr/reload
查看实现依据与边界
packages/boot/hmr/src/index.ts:603–729
固定源码分析 · 本次未运行 HMR。普通更新可被 internal/update 拦截;HMR 回滚不撤销任意外部副作用。完整条件与失败路径见正文。