Skip to content

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 实现构造函数参数的自动注入:

typescript
@injectable()
class A {
  // 不需要 @inject(B),inversify 通过 design:paramtypes 自动识别参数类型
  public constructor(public b: B) {}
}

如果关闭 emitDecoratorMetadata,则必须对每个构造函数参数显式使用 @inject() 装饰器,或者改用属性注入。

不同编译工具链的支持情况

tsc(TypeScript 编译器)

tsc 完整支持 experimentalDecoratorsemitDecoratorMetadata,是 inversify 的标准编译环境。

jsonc
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

esbuild

esbuild 是 vitest/vite 默认使用的 TypeScript 转译器。

特性支持情况
experimentalDecorators支持
emitDecoratorMetadata不支持

esbuild 可以通过配置启用 Stage 1 装饰器:

ts
// 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 完整支持 experimentalDecoratorsemitDecoratorMetadata,可以作为 esbuild 的替代方案。

本项目的 inversify 测试就是通过 unplugin-swc 来运行的:

ts
// 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.getMetadatacontext.metadata(Stage 3 原生)
依赖声明方式构造函数参数 / 属性装饰器仅属性装饰器
esbuild 兼容性部分(不支持 emitDecoratorMetadata)完全兼容

本项目不支持构造函数参数注入,所有依赖都通过属性装饰器声明:

typescript
// @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 无法提供的类型元数据能力。