You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:28:11