热重载:fiber.update 与 HMR
专栏:Cordis 插件框架 · 第 11 / 12 篇:::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 组装)与本篇串起来,配置热重载的端到端路径是:
三个工程细节:组合时 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 开启)——与配置热重载是两件事。
常见踩坑
- 以为改了 yml 立即生效但用的是 startup profile——headless/sdk/acp 只在启动时应用配置;
- update 的 config 没过插件 Config 校验就自嗨——走
fiber.update会正常校验,绕过框架直接改fiber.config则不会; - 在 internal/update 里做耗时操作——它是 waterfall 的同步路径,会阻塞所有 fiber 的更新。
随堂练习(带验收标准)
- 用
fiber.update(newConfig)更新一个插件,验证 dispose→apply 的完整序列(打印日志观察顺序); - 写一个 internal/update 监听器否决”包含 forbidden 字样”的配置。验收:非法配置被拒、旧配置继续生效;
- 在 dsh 里跑
webprofile,编辑用户 patch 文件加一个无害的console.log插件。验收:无需重启即生效。