模块系统

26 阅读3分钟

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 模块系统的核心:

  1. @Module 装饰器:将配置存储到元数据
  2. DependenciesScanner:递归扫描模块依赖树
  3. 动态模块:运行时配置模块
  4. 全局模块:exports 对所有模块可见
  5. imports/exports:控制依赖可见性
  6. 模块距离:确定依赖解析顺序

下一篇我们将分析装饰器的实现原理。


📦 源码位置:packages/core/scanner.tspackages/common/decorators/modules/

下一篇:NestJS 装饰器原理