从 0.1 迁移到 0.2
0.2 是以“显式资源所有权”为核心的重构:Container 与 Proxy 的动态派发被替换为显式的作用域树,所有清理路径可等待。本文列出面向插件与服务作者的破坏性变更与迁移方法。
Container 移除
Container 已从 0.2 移除,作用域(Scope)是唯一的资源管理入口。API 对照:
| Container(0.1) | Scope(0.2) | 说明 |
|---|---|---|
register(name, target) | scope.register(name, target) | 惰性注册,语义一致 |
get(name) | scope.get(name) | 未命中返回 undefined,不再抛错 |
has(name) | scope.has(name) | 沿父链查找 |
list() | scope.list() | 含父链 |
| — | scope.set(name, value) | 纯登记,无实例化 |
| — | scope.provide(name, svc) | 带 start/stop 生命周期管理 |
自行装配框架的脚本需要从:
typescript
const container = new Container();
injectionProvider(container, config, { headless: false });
const app = container.get('app');迁移为:
typescript
const rootScope = createScope();
injectionProvider(rootScope, config, { headless: false });
const app = rootScope.get('app');服务即插件
0.1 的独立服务装载路径(service.files / service.scopes + { name, default: 类或工厂 } 模块)已废弃。服务以插件形态编写,在主体内通过 context.share() 提升到根作用域:
typescript
// 0.1:service.files 装载的 { name, default } 服务模块
export const name = 'my-service';
export default class MyService {
static inject = ['logger'] as const;
constructor(private logger: LoggerService) {}
}
// 0.2:plugin.files 装载的服务插件
export const inject = ['logger'];
export const name = 'my-service';
export default async function (context: Context) {
await context.share(new MyService(context.get('logger')!)); // 缺省服务名 = 插件名
}要点:
- 服务插件经
plugin.files/ 插件依赖扫描装载,saukko plugin enable / disable直接管理; share的服务随插件启用而启动、随禁用而从根作用域摘除、随卸载而清理(详见 服务生命周期);- 依赖该服务的插件在服务消失时自动级联停止,服务恢复后自动重启——不再需要手动处理顺序;
ServiceRegistry声明扩展机制不变,消费方照常inject+context.get()。
插件清理改为可等待的 Lifecycle 钩子
0.1 的内部事件 internal.ready / internal.dispose 已移除。清理逻辑迁移到 context.lifecycle:
typescript
// 0.1
context.on('internal.dispose', async () => { await cleanup(); });
// 0.2
context.lifecycle.onStop(async () => { await cleanup(); });要点:
- 钩子持久保留:
onStop在每次 disable 时执行,onStart在每次 enable 时执行;onDispose是仅卸载时执行一次的终态钩子; onStop可注册多次,按注册逆序执行;onBeforeStop早于onStop;- 插件禁用、卸载、应用停止都会等待清理完成;单个插件清理失败不阻断其余插件;
- 事件监听(
context.on)在卸载时自动移除,无需手动off;disabled 期间监听被门控,重新启用后自动恢复。
install 即执行,enable/disable 是纯开关
插件主体的执行时机从"启用时"提前到"安装时":
| 操作 | 0.1 | 0.2 |
|---|---|---|
| install | 仅登记 | 依赖就绪即执行主体;未就绪则挂起等待 |
| enable | 执行主体 | 仅触发 onStart 钩子;依赖缺失时标记期望启用 |
| disable | 销毁上下文 | 仅触发 onStop 钩子,子作用域保留 |
| uninstall | — | 终态销毁子作用域并移除 |
迁移注意:主体中"只需执行一次"的初始化(注册监听、share 服务)保持在主体内;"每次启用都要做"的逻辑(连接、定时器)移入 onStart,对应清理移入 onStop。
依赖声明的变化
inject中引用其他插件:仅约束启动顺序,不做实例注入;0.1 中此类依赖会被误判为缺失导致插件无法启用,0.2 已修正;- 缺失依赖从"拒绝启用"改为等待语义:install 挂起主体、enable 标记期望启用,依赖补齐后自动执行并启动;循环依赖仍是硬错误;
- 依赖消失(服务插件被禁用/卸载)时依赖方自动级联停止,恢复后自动重启;
- 上下文读取:0.1 的
context.dependencies仍兼容保留(为安装时的快照),推荐改用context.get()——沿父链动态解析,服务替换后读到的始终是最新实例;未声明inject的服务也可读取。
多形态插件入口
程序化 install 新支持函数、类、含 apply 方法的对象三种形态;{ name, default } 模块形态不变,仍是包加载的唯一形态。详见 插件形态。
行为变化速查
App.stop()按启动拓扑的逆序卸载全部插件(0.1 为安装顺序停止);- 插件安装失败(主体抛错)会清理其子作用域且不留记录,可直接重新 install;
- 插件启用失败(
onStart抛错)进入 FAILED 并保持未启用,可直接重试 enable; uninstall清理失败时插件保持已禁用状态,可重试卸载;Context.get未命中返回undefined而非抛错;plugin list会展示每个插件的状态:enabled/disabled/waiting: <依赖清单>。