06 · 组合元数据与热替换(HMR)

目标:把 cordis.yml 当应用来管理——条目元数据(id/disabled/组/isolate)与保存即生效的热替换。 对应快速入门项目:npm run lesson:07(常驻进程,Ctrl-C 退出)

条目元数据

配置项不只有 name 和 config:

- id: greeter          # 稳定标识:让 loader 区分「修改」与「先删再加」
  name: './greeter.ts'
- id: consumer
  name: './consumer.ts'
  disabled: true       # 保留条目但卸载插件
  • id:稳定标识。HMR/loader 按 id diff,只动变化的条目;没写 id 的条目每次读取都获得新 id,任何 yml 编辑都会导致它被删了重挂
  • disabled: true:卸载插件但保留条目;改回后插件及所有因依赖其服务而 PENDING 的插件再次加载
  • 组:嵌套一份配置项子列表,作为整体单元加载/卸载
  • isolate:给组内某服务名提供独立实例——两组可以各自看到配置不同的 shell 提供方,互不影响

实例:HMR 保存即热替换

原理一句话:卸载释放 effect(第 02 课)+ 依赖驱动加载(第 03 课),所以热替换 = 先卸载再加载。

# cordis.yml —— HMR 依赖两个基础设施插件(体现「无特权核心」)
- id: logger
  name: '@cordisjs/plugin-logger-console'   # hmr 通过 logger 服务记日志,没它看不到 hmr 消息
- id: timer
  name: '@cordisjs/plugin-timer'            # hmr inject timer 做去抖,缺了永远 PENDING 且无提示
- id: hmr
  name: '@cordisjs/plugin-hmr'
  config:
    root: ['.']
- id: hello
  name: './hello.ts'

运行(npm run lesson:07 已内置 --expose-internals——HMR 需读取 Node loader 内部结构,缺了直接报 --expose-internals is required for HMR service):

npm run lesson:07

保持进程运行,编辑 hello.ts 的日志文案并保存:

hello from my first cordis plugin
2026-07-22 15:44:36 [I] hmr watching [ '.' ]
2026-07-22 15:44:39 [I] hmr reload plugin at hello.ts
hello from my EDITED plugin

旧实例先卸载(其全部 effect 回卷),新代码随后加载,apply 再次运行。

编辑 cordis.yml 本身也触发更新:按 id diff,只挂载/卸载/重配变化的部分——这就是上面条目都显式带 id 的原因。

两个隐藏依赖的教训

HMR 插件自身的依赖正好是本课两个知识点的实战:

  1. 没有 logger-console → hmr 消息打不出来(logger 是服务,也是插件)
  2. 没有 timer → hmr 永远 PENDING 且毫无提示——用第 05 课的诊断器可以查出来

动手练习

  1. 把 hello 条目的 id 删掉,运行中随便在 yml 加一行注释保存,观察 hello 是否被重挂(每次都会——没有稳定 id)
  2. 给 hello 条目加 disabled: true 保存,观察卸载日志;删掉该字段保存,观察复活
  3. 把 logger-console 注释掉重启,体会「hmr 明明在工作却看不到任何日志」的静默失败

上一课:05-config-diagnose | 下一课:07-into-harness