使用ConfigurableModuleBuilder的NestJS动态模块外部导入依赖报错原因
问题描述
类库模块实现
我在类库中创建了一个需配置参数的模块,遵循NestJS文档使用ConfigurableModuleBuilder生成register和registerAsync方法:
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<RepositoriesModuleOptions>().build(); @Module({ providers: [ { provide: ConnectionService, useFactory: (options: RepositoriesModuleOptions) => new ConnectionService(options.connectionUri, options.mongoClientOptions), inject: [MODULE_OPTIONS_TOKEN], }, RepositoryMethodsBuilder, ...MethodBuilderProviders(METHOD_BUILDERS), ...FilterModifierProviders(FILTER_MODIFIERS) ], exports: [ ConnectionService, RepositoryMethodsBuilder ] }) export class RepositoriesModule extends ConfigurableModuleClass {}
内部e2e测试正常运行
在同一项目中导入该模块编写e2e测试,两种注册方法均正常工作:
@Module({ imports: [ RepositoriesModule.registerAsync({ useFactory: () => { Logger.log("Registering RepositoriesModule"); return { connectionUri: TEST_URI }; } }) //RepositoriesModule.register({ // connectionUri: TEST_URI //}) ], providers: [ repositoryFactoryProvider(TestRepo), repositoryFactoryProvider(TestRepo2), repositoryFactoryProvider(ExtendedEntityRepository) ] }) export class TestModule {}
外部项目使用报错
但在另一个NestJS项目本地导入该类库,以相同方式使用RepositoriesModule时(类库此前作为动态模块可正常运行):
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), UserModule, RepositoriesModule.register({ connectionUri: TEST_URI }) ], controllers: [AppController], providers: [AppService] }) export class AppModule {}
运行应用时,两种注册方法均触发如下错误:
Error: Nest can't resolve dependencies of the ConnectionService (?). Please make sure that the argument "CONFIGURABLE_MODULE_OPTIONS[97243f5614591d9255421]" at index [0] is available in the RepositoriesModule context. Potential solutions: - Is RepositoriesModule a valid NestJS module? - If "CONFIGURABLE_MODULE_OPTIONS[97243f5614591d9255421]" is a provider, is it part of the current RepositoriesModule? - If "CONFIGURABLE_MODULE_OPTIONS[97243f5614591d9255421]" is exported from a separate @Module, is that module imported within RepositoriesModule? @Module({ imports: [ /* the Module containing "CONFIGURABLE_MODULE_OPTIONS[97243f5614591d9255421]" */ ] }) at Injector.lookupComponentInParentModules (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:254:19) at processTicksAndRejections (node:internal/process/task_queues:96:5) at async Injector.resolveComponentInstance (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:207:33) at async resolveParam (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:128:38) at async Promise.all (index 0) at async Injector.resolveConstructorParams (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:143:27) at async Injector.loadInstance (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:70:13) at async Injector.loadProvider (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:97:9) at async Injector.lookupComponentInImports (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:289:17) at async Injector.lookupComponentInParentModules (\full-stack-poc\node_modules\@nestjs\core\injector\injector.js:252:33)
核心疑问
为何该问题仅出现在外部项目中,而内部e2e测试无异常?
解决方案分析
这种问题通常由以下几个原因导致,按优先级排查:
1. 类库打包时的重复依赖问题
当类库和外部项目都安装了@nestjs/core等核心依赖,且版本不一致,或者类库打包时没有将NestJS核心依赖标记为外部依赖,会导致运行时出现两个不同的NestJS实例,此时MODULE_OPTIONS_TOKEN在两个实例中是不同的引用,依赖注入无法匹配。
- 排查方式:检查类库的
package.json,确认@nestjs/core、@nestjs/common等依赖是否在peerDependencies中,而不是dependencies。 - 修复方式:将NestJS核心依赖移到
peerDependencies,并在打包配置(如tsconfig、webpack、rollup)中标记这些依赖为外部,避免打包到类库中。
2. 本地类库链接的缓存问题
如果是通过npm link或yarn link引入本地类库,Node.js的模块缓存可能导致类库中的MODULE_OPTIONS_TOKEN和外部项目中的NestJS实例不兼容。
- 排查方式:检查外部项目的
node_modules中是否存在重复的@nestjs/core包,或者尝试删除类库和外部项目的node_modules及锁文件,重新安装。 - 修复方式:使用
npm pack生成类库的tar包,在外部项目中本地安装该包,替代链接方式;或者清理Node.js模块缓存(npm cache clean --force)。
3. 模块构建时的作用域问题
ConfigurableModuleBuilder生成的令牌是基于模块类的唯一标识,当类库被打包后,模块类的引用可能在外部项目中发生变化,导致令牌不匹配。
- 排查方式:在类库中显式指定模块的令牌名称,避免自动生成的动态令牌出现不匹配:
const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<RepositoriesModuleOptions>() .setClassMethodName('register') .setModuleOptionsToken('REPOSITORIES_MODULE_OPTIONS') .build(); - 修复方式:使用
setModuleOptionsToken手动指定固定的令牌字符串,确保类库和外部项目中使用的令牌一致。
4. 全局模块的干扰
外部项目中启用了ConfigModule.forRoot({ isGlobal: true }),可能导致依赖注入上下文冲突,特别是如果类库中也使用了全局配置或存在重复的注入令牌。
- 排查方式:暂时移除全局ConfigModule,测试是否还会报错。
- 修复方式:如果必须使用全局ConfigModule,确保类库中的配置参数获取方式兼容,或者在注册
RepositoriesModule时明确指定依赖的配置来源。
内容的提问来源于stack exchange,提问作者francadaval
相关产品推荐
相关产品推荐

