Fiber、effect 与确定性清理
让插件知道何时开始,也知道如何结束。
依赖驱动加载,作用域追踪注册,dispose 等待全部清理,HMR 不保留旧实例。
阅读位置完成打包教程后阅读。这里解释此前自动清理、依赖等待和热替换背后的运行时模型。
plugin.ts
export function apply(ctx: Context) {
ctx.on('some-event', handler)
ctx.effect(() => {
const connection = createConnection()
return () => connection.close()
})
}
每个插件实例拥有一个 Fiber
Fiber 是运行时作用域,也是生命周期句柄。状态反映依赖、apply 和清理当前进行到哪里。
PENDING→LOADING→ACTIVE→UNLOADING→DISPOSEDFAILED
PENDING已声明,所需服务尚未就绪。
LOADING / ACTIVEapply 正在运行,或已经完成。
FAILEDapply 或配置校验抛出异常。
UNLOADING / DISPOSEDdisposer 正在运行,或清理已经完成。
inject 决定插件何时能进入 LOADING
插件会等待全部必需服务就绪。如果提供方被替换、服务暂时消失,消费者会自动卸载,并在服务恢复后重新加载。
TypeScript
export const inject = ['tools', 'llm']
export function apply(ctx: Context) {
// ctx.tools and ctx.llm are ready here.
}服务就绪→ACTIVE→服务消失→DISPOSED→恢复后重载
多数注册本身已经是 effect
通过 ctx 建立的注册会附着到当前 Fiber。只有 Cordis 不管理的资源才需要手写 ctx.effect()。
ctx.on(event, handler)卸载时移除监听器ctx.tools.register(tool)撤销工具注册ctx.llm.registerAdapter(...)撤销模型适配器ctx.plugin(child)随父 Fiber 递归卸载ctx.effect(() => cleanup)释放连接、watcher 等外部资源不要假设异步清理串行完成
disposer 按注册顺序的逆序开始调用,但多个异步 disposer 会并发运行。存在顺序依赖时,把全部步骤放在同一个 disposer 中,并在内部依次 await。
子插件继承上下文,但拥有独立生命周期
ctx.plugin() 返回子 Fiber。它可以提前 dispose,也会在父插件卸载时递归清理。
parent Fiber
child Fiber独立状态,同父级卸载
TypeScript
const fiber = ctx.plugin(myPlugin)
// Dispose it manually later.
await fiber.dispose()移除拥有的注册
递归卸载子插件
等待全部异步清理
HMR 是完整卸载再加载
cordis-plugin-hmr 先清理旧实例的所有注册,再加载新代码并执行新的 apply。它不是在旧实例上打补丁。
卸载旧实例→清理全部注册→加载新代码→运行新 apply