09 CORDIS / CONFIGURATION IN MOTION

一次改动,四条运行路径

沿着四条实现路径,看原始配置、运行值和插件资源怎样变化。切换输入,播放步骤,再检查完成后的结果。切换路径会保留步骤,重播可从头观察。

01 / 选择改动

同一个 Fiber,重新执行一次插件

fixed 从 A 变成 B。默认 internal/update 末端选择 restart,已登记资源先退出,再按新配置执行。

真实 Loader 实验 03–06

保留插件资源,只替换引用中的快照

仅 live、count 改变,普通有效值一致。Loader 完整校验候选后,先提交所有引用,再通知拥有它们的 Fiber。

真实 Loader 实验 07–12

候选没有通过验证,旧运行值继续可用

live 声明为字符串,却收到数字 42。这个分支保留原始输入,记录警告,不替换正在运行的引用。

真实 Loader 实验 08

先验证模块能导入,再替换运行实例

dsh HMR 在统一队列内分析依赖和缓存。以下是正常替换顺序;失败恢复与权限边界见正文。

固定源码分析 · 本次未运行 HMR

配置变化的四条时序路径时间从上向下。选择普通字段、volatile、无效候选或模块代码,查看配置、运行引用和插件实例之间的处理顺序。六个观察步骤包含处理动作与终点确认;间距不表示耗时。提交新配置01保存新原始值01保留新原始输入01备份并清理缓存01解析并更新02比较原始改动02进入候选解析02重新导入入口02开始释放资源03解析并验证候选03候选验证失败03删除旧 runtime03等待旧资源退出04检查普通有效值04记录校验警告04创建替换实例04按新配置 apply05提交全部快照05跳过快照提交05等待新实例稳定05生命周期稳定06通知 owner06确认运行状态06提交命名空间记录06配置树raw optionsEntry更新入口Fiber同一 uid插件资源重新登记原始配置live / countLoader比较并校验候选与引用schema / get()owner Fiber局部通知新输入live: 42Entry保留 raw校验与日志验证失败运行引用live: oneHMR 队列同一时刻一项模块缓存ESM / CJS旧实例保存的 Fiber新实例替换 Fiber
配置树EntryFiber插件资源
原始配置Loader候选与引用owner Fiber
新输入Entry校验与日志运行引用
HMR 队列模块缓存旧实例新实例

02 / 顺序与等待点

  1. 01
    提交新配置配置树 → Entry

    Entry 接收 fixed: B,并保留新的原始 options。

    保存新原始值原始配置 → Loader

    Entry 与 Fiber 保留新的原始配置,以供后续激活使用。

    保留新原始输入新输入 → Entry

    Entry.options.config 与 Fiber._config 已保留 live: 42。

    备份并清理缓存HMR 队列 → 模块缓存

    选定受影响模块;保存旧 Fiber、entry 与当前原始配置。

  2. 02
    解析并更新Entry → Fiber

    候选配置通过钩子与 schema 后,进入 internal/update。

    比较原始改动Loader → 候选与引用

    只有 config 变化、Fiber ACTIVE、父 Context 未变,才考虑快速路径。

    进入候选解析Entry → 校验与日志

    原始改动符合快速路径条件,开始解析并验证候选。

    重新导入入口模块缓存 → HMR 队列

    所有替换入口先导入;此时尚未删除旧 runtime。

  3. 03
    开始释放资源Fiber → 插件资源

    默认 restart 先发起卸载,已登记资源进入清理。

    解析并验证候选Loader → 候选与引用

    执行真实 internal/config 与 schema,得到新的候选运行配置。

    候选验证失败校验与日志 → Entry

    字符串字段收到数字,验证抛出错误,进入 catch 分支。

    删除旧 runtimeHMR 队列 → 旧实例

    等待保存下来的旧 Fiber 完成清理。

  4. 04
    等待旧资源退出插件资源 → Fiber

    异步清理完成后,才继续本轮装载。

    检查普通有效值候选与引用 → Loader

    普通有效值必须保持相同;否则回到普通更新。

    记录校验警告Entry → 校验与日志

    Loader 记录失败;原始输入不会在这里自动还原。

    创建替换实例HMR 队列 → 新实例

    按原命名空间选择实现,恢复对应 entry 关联。

  5. 05
    按新配置 applyFiber → 插件资源

    插件再次执行,按 fixed: B 登记新的资源。

    提交全部快照Loader → 候选与引用

    live 与 count 的引用身份保持不变,当前值一起更新。

    跳过快照提交Entry → 运行引用

    候选没有提交,运行引用依然返回 one。

    等待新实例稳定新实例 → HMR 队列

    等待新 Fiber 与 Loader;稳定并不一律等于 ACTIVE。

  6. 06
    生命周期稳定插件资源 → Fiber

    Fiber 对象与 uid 保持不变,实验轨迹以新 apply 完成为终点。

    通知 ownerLoader → owner Fiber

    事件带有字段路径;通知前所有引用已提交,其他实例不接收。

    确认运行状态运行引用 → Entry

    实验终点:Fiber 仍 ACTIVE,没有 volatile 更新事件。

    提交命名空间记录HMR 队列 → 模块缓存

    成功后更新 entry.moduleNamespace,再发 hmr/reload。

03 / 完成后快照

固定示例结果,不是实时应用状态
原始配置fixed: B
实例身份同一 Fiber / uid
已登记资源清理 A → 登记 B
// 前后都由 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 / 完成后快照

固定示例结果,不是实时应用状态
同一 live 引用one → two
资源与 apply不清理 / 不重做
事件观察值two 与 2 已提交
// 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 / 完成后快照

固定示例结果,不是实时应用状态
原始输入live: 42
当前 get()仍为 one
将来重新激活原始值仍需校验
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 / 完成后快照

固定示例结果,不是实时应用状态
旧实例清理已等待
替换实例新建 Fiber
成功通知hmr/reload
替换前保存:
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 回滚不撤销任意外部副作用。完整条件与失败路径见正文。