RFLCT: Bringing Runtime Type Metadata to TypeScript 7
RFLCT: Bringing Runtime Type Metadata to TypeScript 7
RFLCT:为 TypeScript 7 引入运行时类型元数据
If you’ve built large-scale applications in TypeScript, chances are you’ve used a Dependency Injection (DI) container. As the creator of InversifyJS, I’ve spent years thinking deeply about inversion of control, decoupling, and how to make enterprise patterns feel natural in TypeScript. 如果你曾使用 TypeScript 构建过大型应用程序,那么你很可能使用过依赖注入(DI)容器。作为 InversifyJS 的创建者,我多年来一直在深入思考控制反转、解耦,以及如何让企业级模式在 TypeScript 中显得自然。
But for all those years, there has been a glaring elephant in the room: our heavy reliance on experimentalDecorators and emitDecoratorMetadata. These compiler flags have served us well, but they are exactly that—experimental. They tie us to legacy decorator implementations, require specific compiler configurations, and often feel like a magic black box that doesn’t perfectly align with modern build pipelines.
但多年来,一直有一个显而易见的问题被我们忽视:我们对 experimentalDecorators 和 emitDecoratorMetadata 的过度依赖。这些编译器标志虽然服务良好,但它们正如其名——是“实验性”的。它们将我们束缚在旧版的装饰器实现中,需要特定的编译器配置,并且往往感觉像是一个与现代构建流水线不完全兼容的“黑盒”。
I’ve spent a lot of time recently thinking about how we could finally drop these flags entirely while keeping the developer experience pristine. With the release of TypeScript 7, I’m thrilled to introduce the solution: 🪞 RFLCT. 最近我花了很多时间思考,如何在保持完美开发体验的同时彻底抛弃这些标志。随着 TypeScript 7 的发布,我很高兴能介绍这个解决方案:🪞 RFLCT。
What is RFLCT?
什么是 RFLCT?
RFLCT is an ahead-of-time (AOT) reflect metadata injector for TypeScript 7. It injects design:symbols and design:arguments directly at build time. Zero decorators. Zero emitDecoratorMetadata. It integrates seamlessly with virtually any build tool (Vite, Rollup, webpack, esbuild) via unplugin, or you can use the built-in CLI using the TypeScript 7 API for standalone tsgo projects.
RFLCT 是一个针对 TypeScript 7 的预编译(AOT)反射元数据注入器。它在构建时直接注入 design:symbols 和 design:arguments。无需装饰器,无需 emitDecoratorMetadata。它通过 unplugin 与几乎任何构建工具(Vite、Rollup、webpack、esbuild)无缝集成,或者你也可以在独立的 tsgo 项目中使用基于 TypeScript 7 API 的内置 CLI。
The Magic: Before and After
魔法:前后对比
With RFLCT, you annotate the types you want to expose to your runtime metadata using a special Reflect<T> wrapper type.
使用 RFLCT,你可以通过特殊的 Reflect<T> 包装类型来标注你希望暴露给运行时元数据的类型。
What you write: 你的代码:
import { Reflect, resolve } from "rflct";
interface Shape { sides: number; }
class Polygon {
constructor(
public shape: Reflect<Shape>,
public label: Reflect<string, { optional: true }>
) {}
}
// resolve<T>() → the runtime identity of T (Symbol for interfaces, class for classes)
container.bind(resolve<Shape>()).to(Polygon);
What RFLCT compiles it to: RFLCT 编译后的代码:
Notice how the interfaces are safely converted into global Symbols, and metadata is explicitly registered without a single decorator in sight. 请注意接口是如何被安全地转换为全局 Symbols 的,并且元数据被显式注册,整个过程中没有使用任何装饰器。
import "reflect-metadata";
const __RFLCT_Shape = Symbol.for("@acme/shapes@1|src/geo.ts|Shape");
class Polygon {
constructor(shape, label) {}
}
Reflect.defineMetadata("design:arguments", [
{ type: __RFLCT_Shape, metadata: {} },
{ type: String, metadata: { optional: true } }
], Polygon, undefined);
container.bind(__RFLCT_Shape).to(Polygon);
Reflect.defineMetadata("design:symbols", Object.assign(
Reflect.getMetadata("design:symbols", Reflect) ?? {},
{
"@acme/shapes@1|src/geo.ts|Shape": __RFLCT_Shape,
"@acme/shapes@1|src/geo.ts|Polygon": Polygon,
}
), Reflect);
Broader Than Just InversifyJS
不仅仅局限于 InversifyJS
While my primary motivation for building RFLCT was to pave the way for the next generation of InversifyJS, this underlying primitive—a reliable, decorator-free way to emit runtime type metadata—unlocks so much more. 虽然我构建 RFLCT 的主要动机是为下一代 InversifyJS 铺平道路,但这种底层原语——一种可靠的、无需装饰器的运行时类型元数据发射方式——解锁了更多的可能性。
Because RFLCT standardizes how types are mapped to memory at build time, it has massive potential across the ecosystem: 由于 RFLCT 标准化了类型在构建时映射到内存的方式,它在整个生态系统中具有巨大的潜力:
- Custom DI Engines: Build your own lightweight inversion of control containers without metadata boilerplate. 自定义 DI 引擎: 构建你自己的轻量级控制反转容器,无需元数据样板代码。
- Object Mapping & Hydration: Easily map database or API JSON results directly back into instantiated classes. 对象映射与水合(Hydration): 轻松将数据库或 API JSON 结果直接映射回已实例化的类。
- RPC Frameworks: Guarantee type-safe network boundaries by validating incoming arguments against compile-time metadata. RPC 框架: 通过根据编译时元数据验证传入参数,确保类型安全的网络边界。
- Runtime Validation: Perform deep runtime validation by reading exactly what types a constructor or method expects. 运行时验证: 通过精确读取构造函数或方法所期望的类型,执行深度的运行时验证。
How It Works: The Three Transformations
工作原理:三大转换
Under the hood, RFLCT performs three core transformations during your build step: 在底层,RFLCT 在构建步骤中执行三个核心转换:
design:symbols— The Global Type Registry: Every class, interface, and type alias in a file is registered in a process-wide Map on the globalReflectobject. Interfaces & Types become universally unique Symbols. Classes map directly to their constructor.design:symbols— 全局类型注册表: 文件中的每个类、接口和类型别名都会在全局Reflect对象的进程级 Map 中注册。接口和类型成为全局唯一的 Symbols。类直接映射到它们的构造函数。design:arguments— Parameter Type Metadata: Any parameter annotated withReflect<T>tells the compiler to produce aReflect.defineMetadata("design:arguments", [...], target, key)call.design:arguments— 参数类型元数据: 任何使用Reflect<T>标注的参数都会告诉编译器生成一个Reflect.defineMetadata("design:arguments", [...], target, key)调用。resolve<T>()— Compile-Time Type Resolution: Writingresolve<T>()acts as a macro. It is replaced at compile time with the actual runtime identity of T.resolve<T>()— 编译时类型解析: 编写resolve<T>()就像一个宏。它在编译时被替换为 T 的实际运行时标识。
Smart Symbol Qualification
智能 Symbol 限定
A massive headache with metadata in the past has been duplicate dependencies causing symbol collisions. Generated symbols use Symbol.for(qualifiedName) structured as packageName@majorVersion|packageRelativePath|TypeName. This prevents collisions, allows minor/patch versions to share symbols, and ensures that shared library types resolve to the exact same symbol reference in memory.
过去元数据的一个巨大痛点是重复依赖导致 Symbol 冲突。生成的 Symbol 使用 Symbol.for(qualifiedName),结构为 packageName@majorVersion|packageRelativePath|TypeName。这防止了冲突,允许次要/补丁版本共享 Symbol,并确保共享库类型解析为内存中完全相同的 Symbol 引用。
Getting Started (Alpha Preview)
入门(Alpha 预览版)
⚠️ Note: RFLCT is currently in its early 0.0.1-alpha.0 stage and the npm module has not yet been published to the public registry. ⚠️ 注意: RFLCT 目前处于 0.0.1-alpha.0 早期阶段,npm 模块尚未发布到公共注册表。
For now, to try it out, you will need to download the source directly from GitHub and build it locally. 目前,要尝试它,你需要直接从 GitHub 下载源代码并在本地构建。
- Build from Source:
git clone ...,npm install,npm run build,npm link. 从源码构建: 执行git clone ...,npm install,npm run build,npm link。 - Project Setup: Link the local package and import
reflect-metadataat your entry point. 项目设置: 链接本地包,并在入口点导入reflect-metadata。 - Configure Your Build Tool: RFLCT is built on top of
unplugin, so it drops straight into your existing pipeline (Vite, Rollup, esbuild, webpack). 配置构建工具: RFLCT 基于unplugin构建,因此可以直接放入你现有的流水线(Vite、Rollup、esbuild、webpack)中。