NestJS 源码解析:模块系统
深入 @Module 装饰器和 DependenciesScanner,揭秘模块的扫描与加载。
@Module 装饰器
模块是 NestJS 组织代码的基本单元:
@Module({
imports: [DatabaseModule],
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
装饰器实现
// packages/common/decorators/modules/module.decorator.ts
export function Module(metadata: ModuleMetadata): ClassDecorator {
const propsKeys = Object.keys(metadata);
validateModuleKeys(propsKeys);
return (target: Function) => {
for (const property in metadata) {
if (Object.hasOwnProperty.call(metadata, property)) {
Reflect.defineMetadata(property, (metadata as any)[property], target);
}
}
};
}
本质就是将配置存储到类的元数据中:
Reflect.defineMetadata('imports', [DatabaseModule], CatsModule);
Reflect.defineMetadata('controllers', [CatsController], CatsModule);
Reflect.defineMetadata('providers', [CatsService], CatsModule);
Reflect.defineMetadata('exports', [CatsService], CatsModule);
模块扫描
DependenciesScanner
// packages/core/scanner.ts
export class DependenciesScanner {
public async scan(module: ModuleDefinition, options?: { overrides?: ModuleOverride[] }) {
// 1. 注册核心模块
await this.registerCoreModule(options?.overrides);
// 2. 递归扫描模块
await this.scanForModules({
moduleDefinition: module,
overrides: options?.overrides,
});
// 3. 扫描模块内的依赖
await this.scanModulesForDependencies();
// 4. 计算模块距离
this.calculateModulesDistance();
}
}
递归扫描模块
public async scanForModules({
moduleDefinition,
scope = [],
ctxRegistry = [],
overrides = [],
lazy,
}: ModulesScanParameters): Promise<Module[]> {
// 添加模块到容器
const { moduleRef, inserted } = (await this.container.addModule(
moduleDefinition,
scope,
))!;
if (!inserted) {
return [moduleRef];
}
// 获取 imports
const importedModules = this.reflectImports(moduleDefinition);
// 递归扫描 imports
const modules = await this.scanModulesRecursively(
importedModules,
[].concat(scope, moduleDefinition),
ctxRegistry,
overrides,
);
return [moduleRef].concat(modules);
}
// 反射获取 imports
public reflectImports(module: Type<any>): Type<any>[] {
return Reflect.getMetadata(MODULE_METADATA.IMPORTS, module) || [];
}
扫描模块依赖
public async scanModulesForDependencies(
modules: Map<string, Module> = this.container.getModules(),
) {
for (const [token, moduleRef] of modules) {
// 扫描 imports
await this.reflectImports(moduleRef.metatype).forEach(imported =>
this.insertImport(imported, token),
);
// 扫描 providers
this.reflectProviders(moduleRef.metatype).forEach(provider =>
this.insertProvider(provider, token),
);
// 扫描 controllers
this.reflectControllers(moduleRef.metatype).forEach(controller =>
this.insertController(controller, token),
);
// 扫描 exports
this.reflectExports(moduleRef.metatype).forEach(exported =>
this.insertExportedProvider(exported, token),
);
}
}
模块类型
静态模块
@Module({
providers: [CatsService],
})
export class CatsModule {}
动态模块
@Module({})
export class DatabaseModule {
static forRoot(options: DatabaseOptions): DynamicModule {
return {
module: DatabaseModule,
providers: [
{
provide: 'DATABASE_OPTIONS',
useValue: options,
},
DatabaseService,
],
exports: [DatabaseService],
};
}
}
// 使用
@Module({
imports: [DatabaseModule.forRoot({ host: 'localhost' })],
})
export class AppModule {}
动态模块处理:
// packages/core/injector/compiler.ts
export class ModuleCompiler {
public async compile(metatype: ModuleMetatype): Promise<ModuleFactory> {
// 处理 Promise
const unwrappedMetatype = await this.extractMetadata(metatype);
// 处理动态模块
const { type, dynamicMetadata } = this.isDynamicModule(unwrappedMetatype)
? this.extractDynamicMetadata(unwrappedMetatype)
: { type: unwrappedMetatype, dynamicMetadata: undefined };
// 生成唯一 token
const token = this.moduleOpaqueKeyFactory.create(type, dynamicMetadata);
return { type, dynamicMetadata, token };
}
}
全局模块
@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
全局模块的 exports 对所有模块可见:
// packages/core/injector/container.ts
public addGlobalModule(module: Module) {
this.globalModules.add(module);
}
// packages/core/injector/injector.ts
public async lookupComponentInGlobalModules(
dependency: InjectorDependency,
contextId: ContextId,
wrapper: InstanceWrapper,
) {
for (const globalModule of this.container.getGlobalModules()) {
const provider = globalModule.providers.get(dependency);
if (provider) {
return provider;
}
}
return null;
}
模块导入导出
imports
导入其他模块,使其 exports 可用:
@Module({
imports: [DatabaseModule],
})
export class CatsModule {}
exports
导出 providers,使其对导入方可用:
@Module({
providers: [CatsService, DogsService],
exports: [CatsService], // 只导出 CatsService
})
export class PetsModule {}
导出处理:
// packages/core/injector/module.ts
public addExportedProvider(provider: InjectionToken) {
this._exports.add(provider);
}
// 查找导出的 provider
public getProviderByKey<T = any>(name: InjectionToken): InstanceWrapper<T> {
// 先在自己的 providers 中找
if (this._providers.has(name)) {
return this._providers.get(name) as InstanceWrapper<T>;
}
// 再在 imports 的模块中找
for (const importedModule of this._imports) {
if (importedModule.exports.has(name)) {
return importedModule.getProviderByKey(name);
}
}
return null;
}
模块距离计算
用于确定依赖解析顺序,源码使用 TopologyTree 实现:
// packages/core/scanner.ts
public calculateModulesDistance() {
const modulesGenerator = this.container.getModules().values();
// 跳过 InternalCoreModule
modulesGenerator.next();
const rootModule = modulesGenerator.next().value!;
if (!rootModule) {
return;
}
// 使用拓扑树计算模块距离
const tree = new TopologyTree(rootModule);
tree.walk((moduleRef, depth) => {
if (moduleRef.isGlobal) {
return; // 全局模块距离为 MAX_VALUE
}
moduleRef.distance = depth;
});
}
模块距离影响:
- 中间件注册顺序:距离小的模块先注册
- 全局模块:距离为
Number.MAX_VALUE,最后处理
## 模块生命周期
```typescript
export interface NestModule {
configure(consumer: MiddlewareConsumer): void;
}
@Module({})
export class CatsModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(CatsController);
}
}
configure 方法在模块初始化时调用:
// packages/core/middleware/middleware-module.ts
public async register(
middlewareContainer: MiddlewareContainer,
container: NestContainer,
config: ApplicationConfig,
injector: Injector,
httpAdapter: HttpServer,
graphInspector: GraphInspector,
) {
const modules = container.getModules();
for (const [token, moduleRef] of modules) {
const instance = moduleRef.instance;
// 调用 configure 方法
if (instance && isFunction(instance.configure)) {
const middlewareBuilder = new MiddlewareBuilder(
this.routesMapper,
httpAdapter,
this.routeInfoPathExtractor,
);
await instance.configure(middlewareBuilder);
// 收集中间件配置
const middlewareConfigs = middlewareBuilder.build();
middlewareContainer.insertConfig(middlewareConfigs, token);
}
}
}
模块重新导出
可以重新导出导入的模块:
@Module({
imports: [DatabaseModule],
exports: [DatabaseModule], // 重新导出
})
export class SharedModule {}
总结
NestJS 模块系统的核心:
- @Module 装饰器:将配置存储到元数据
- DependenciesScanner:递归扫描模块依赖树
- 动态模块:运行时配置模块
- 全局模块:exports 对所有模块可见
- imports/exports:控制依赖可见性
- 模块距离:确定依赖解析顺序
下一篇我们将分析装饰器的实现原理。
📦 源码位置:
packages/core/scanner.ts、packages/common/decorators/modules/下一篇:NestJS 装饰器原理