inversify 使用条件与约束
背景
本项目(@kaokei/di)的 tests/inversify 目录包含了一系列针对 inversify v6.2.2 的测试用例。这些测试用于对比 inversify 和本项目在依赖注入行为上的差异。
由于 inversify 使用的是 Stage 1 装饰器,而本项目使用的是 Stage 3 装饰器,两者在编译环境上有本质区别。本文记录 inversify 的使用条件,以及在不同编译工具链下的约束。
inversify 的核心依赖
inversify 依赖两个关键的 TypeScript 编译器特性:
1. experimentalDecorators
inversify 使用 Stage 1(实验性)装饰器语法,包括:
@injectable()— 类装饰器@inject()— 属性装饰器 / 构造函数参数装饰器@optional()— 属性装饰器@postConstruct()— 方法装饰器@preDestroy()— 方法装饰器
这些装饰器遵循的是 TypeScript 早期实现的装饰器规范,需要开启 experimentalDecorators: true。
2. emitDecoratorMetadata(可选但常用)
emitDecoratorMetadata 让 TypeScript 编译器在装饰器应用时自动生成类型元数据(通过 reflect-metadata),包括:
design:type— 属性的类型design:paramtypes— 构造函数参数的类型列表design:returntype— 方法的返回类型
inversify 利用 design:paramtypes 实现构造函数参数的自动注入:
@injectable()
class A {
// 不需要 @inject(B),inversify 通过 design:paramtypes 自动识别参数类型
public constructor(public b: B) {}
}如果关闭 emitDecoratorMetadata,则必须对每个构造函数参数显式使用 @inject() 装饰器,或者改用属性注入。
不同编译工具链的支持情况
tsc(TypeScript 编译器)
tsc 完整支持 experimentalDecorators 和 emitDecoratorMetadata,是 inversify 的标准编译环境。
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}esbuild
esbuild 是 vitest/vite 默认使用的 TypeScript 转译器。
| 特性 | 支持情况 |
|---|---|
experimentalDecorators | 支持 |
emitDecoratorMetadata | 不支持 |
esbuild 可以通过配置启用 Stage 1 装饰器:
// vite.config.ts
export default defineConfig({
esbuild: {
tsconfigRaw: {
compilerOptions: {
experimentalDecorators: true,
},
},
},
});但 esbuild 作为纯转译器,不做类型分析,因此无法生成 design:paramtypes 等类型元数据。这是 esbuild 的设计决策,不是 bug。
在这种条件下使用 inversify 的约束:
- 不能依赖构造函数参数的自动类型推断注入
- 必须对每个依赖显式使用
@inject(token)装饰器 - 或者改用属性注入方式:
@inject(B) b!: B
SWC
SWC 完整支持 experimentalDecorators 和 emitDecoratorMetadata,可以作为 esbuild 的替代方案。
本项目的 inversify 测试就是通过 unplugin-swc 来运行的:
// vite.config.inversify.ts
import swc from 'unplugin-swc';
export default defineConfig({
plugins: [
swc.vite({
jsc: {
parser: {
syntax: 'typescript',
decorators: true,
},
transform: {
legacyDecorator: true,
decoratorMetadata: true,
},
},
}),
],
});使用 SWC 时,inversify 的所有功能(包括构造函数参数自动注入)都能正常工作。
对比:本项目(@kaokei/di)的条件
本项目使用 Stage 3 装饰器,与 inversify 在编译环境上有根本区别:
| 维度 | inversify | @kaokei/di |
|---|---|---|
| 装饰器规范 | Stage 1(experimentalDecorators) | Stage 3(TC39 标准) |
| 是否需要 experimentalDecorators | 是 | 否(默认就是 Stage 3) |
| 是否依赖 emitDecoratorMetadata | 常用(构造函数参数自动注入) | 否 |
| 是否支持构造函数参数装饰器 | 是 | 否(Stage 3 不支持) |
| 是否依赖 reflect-metadata | 是 | 否 |
| 元数据存储方式 | Reflect.getMetadata | context.metadata(Stage 3 原生) |
| 依赖声明方式 | 构造函数参数 / 属性装饰器 | 仅属性装饰器 |
| esbuild 兼容性 | 部分(不支持 emitDecoratorMetadata) | 完全兼容 |
本项目不支持构造函数参数注入,所有依赖都通过属性装饰器声明:
// @kaokei/di 的方式
@Injectable()
class A {
@Inject(() => B) b!: B;
}因此本项目不需要 emitDecoratorMetadata,也不需要 experimentalDecorators,使用 esbuild 就能完全正常工作。
总结
| 编译工具 | inversify 可用性 | 约束 |
|---|---|---|
| tsc | 完全可用 | 需要 experimentalDecorators + emitDecoratorMetadata |
| esbuild | 部分可用 | 支持装饰器语法,但不支持 emitDecoratorMetadata,需显式 @inject |
| SWC | 完全可用 | 通过 legacyDecorator + decoratorMetadata 配置 |
对于本项目(@kaokei/di),由于使用 Stage 3 装饰器且不依赖 emitDecoratorMetadata,esbuild 就是最简单高效的选择,无需引入额外的编译工具。inversify 测试之所以需要 SWC,是因为 inversify 自身的设计依赖了 esbuild 无法提供的类型元数据能力。