热重载:fiber.update 与 HMR

专栏:Cordis 插件框架 · 第 11 / 12 篇
Cordis热重载HMR

:::info 学习目标 完成本篇后你能够:使用 fiber.update() 热更新插件配置;解释 internal/update waterfall 的否决机制;说清 dsh 配置热重载的完整链路。 前置:第 9 篇完成。预计时长:45 分钟。 :::

“改配置不重启”依赖三个角色:文件监听(发现变更)→ fiber.update(应用变更)→ internal/update waterfall(给插件否决/善后的机会)。

fiber.update:变更的入口

// vendor/cordis/src/fiber.ts:726 附近(节选,结构还原)
async update(config: any, noSave = false) {
  // 走 internal/update waterfall:任何监听器都可以否决(不调 next)
  // 或替换本次重启;全部放行后:
  this._config = config
  return this.restart()          // = dispose 后立即 _reload
}

restart()(fiber.ts

)= dispose() 紧跟 _refresh()——旧轮次的全部 effect 逆序清理,然后带着新配置重新执行 apply。对插件来说这就是”卸载即重载”:定时器重设、监听器重注册、服务重提供,全程无感。

internal/update:给插件善后的机会

变更并不盲目生效。internal/update 是一个 waterfall(events.ts

有一个全局前置的自举监听器收集回调),任何监听器都可以:

  • 否决:不调用 next(),本次更新被拒绝——dsh 的 live profile 靠它实现”被拒绝的编辑让最后一个可用配置继续运行”;
  • 善后:在 next() 前后做清理/预热(比如先停后台任务再允许重启);
  • 替换:改写传入的 config。

dsh 的插件用这个钩子实现”配置变更时先优雅停止再重启”的语义。

dsh 的完整链路

把第 2 篇(patch 组装)与本篇串起来,配置热重载的端到端路径是:

图表(cordis-hmr.md)

三个工程细节:组合时 bundle 层垫底、overlay 垫顶、用户层全量重读并深拷贝(防止 insert 行按引用污染 bundle 默认值);web 这类 live profile 启动时若无 HMR 服务会自动补一个 watch-only 的(第 2 篇 dsh 专栏);startup 型 profile(headless/sdk)刻意不做热重载——一次性应用拥有工作之后替换依赖会破坏生命周期。

与其他模块的联系

  • fiber 生命周期(第 9 篇):update = dispose + _reload 的组合,internal/update 的否决发生在 dispose 之前;
  • Loader(dsh 专栏第 11 篇方向):配置树的每行条目 id 稳定,diff 后只更新变化的条目;
  • dsh base 的 hmr 插件:模块级热替换的执行者(默认 disabled,按 profile 开启)——与配置热重载是两件事。

常见踩坑

  1. 以为改了 yml 立即生效但用的是 startup profile——headless/sdk/acp 只在启动时应用配置;
  2. update 的 config 没过插件 Config 校验就自嗨——走 fiber.update 会正常校验,绕过框架直接改 fiber.config 则不会;
  3. 在 internal/update 里做耗时操作——它是 waterfall 的同步路径,会阻塞所有 fiber 的更新。

随堂练习(带验收标准)

  1. fiber.update(newConfig) 更新一个插件,验证 dispose→apply 的完整序列(打印日志观察顺序);
  2. 写一个 internal/update 监听器否决”包含 forbidden 字样”的配置。验收:非法配置被拒、旧配置继续生效;
  3. 在 dsh 里跑 web profile,编辑用户 patch 文件加一个无害的 console.log 插件。验收:无需重启即生效。

← 返回文章列表