Dynamic import of module and loading of a dynamic module ends in "metatype is not a constructor" error

See original GitHub issue

Bug Report

As stated in #3277 I’m actually trying to get a plugin architecture working. For this I used the dynamic modules of NestJS and a simple import(), but in fact, that is not working as expected.

I have two modules:

  • App-Module (for the core app)
  • Plugin-Module (which loads all available plugins)

The plugins are your libraries which I have built before. But If i want to dynamically load the plugin-modulewith all child-modules (plugins) the compiler says:

[Nest] 14908 - 2019-11-01 10:12:32 AM [loadPlugins] Loading plugins from C:\Users\mfranke\Documents\GitHub\cre8ive\cre8ive-server\plugins [Nest] 14908 - 2019-11-01 10:12:48 AM [NestFactory] Starting Nest application… +15595ms All modules resolved: 1 plugins [Nest] 14908 - 2019-11-01 10:12:48 AM [ExceptionHandler] metatype is not a constructor +40ms TypeError: metatype is not a constructor at Injector.instantiateClass (C:\Users\mfranke\Documents\GitHub\cre8ive\cre8ive-server\node_modules@nestjs\core\injector\injector.js:276:19) at callback (C:\Users\mfranke\Documents\GitHub\cre8ive\cre8ive-server\node_modules@nestjs\core\injector\injector.js:74:41) at process._tickCallback (internal/process/next_tick.js:68:7) at Function.Module.runMain (internal/modules/cjs/loader.js:834:11) at startup (internal/bootstrap/node.js:283:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3) Waiting for the debugger to disconnect…

Input Code

Code of my app-module:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PluginModule } from './plugin/plugin.module';

@Module({
  imports: [PluginModule.registerPluginsAsync()],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

Code of plugin-module:

import { PluginAModule } from '@c8-plugin/plugin-a';
import { DynamicModule, Logger, Module } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import { PluginController } from './plugin/plugin.controller';

export const PLUGIN_PATH = path.normalize(path.join(process.cwd(), 'plugins'));

@Module({
  controllers: [PluginController],
})
export class PluginModule {

  public static async registerPluginsAsync(): Promise<DynamicModule> {
    return this.loadPlugins();
  }

  private static loadPlugins(): Promise<DynamicModule> {
    Logger.log(`Loading plugins from ${PLUGIN_PATH}`, 'loadPlugins');

    const loadedPlugins: Array<Promise<DynamicModule>> = new Array();
    this.searchPluginsInFolder(PLUGIN_PATH).forEach(filePath => {
      loadedPlugins.push(
        this.loadPlugin(filePath).then(module => {
          if (module) { return module; }
        }),
      );
    });

    return Promise.all(loadedPlugins).then((allPlugins: DynamicModule[]) => {
      console.log('All modules resolved: ', allPlugins.length, 'plugins');

      return {
        module: PluginModule,
        imports: [...allPlugins],
      } as Dynamic

    });
  }

  private static loadPlugin(path: string): Promise<DynamicModule> {
    const modulePlugin = import(path);
    return modulePlugin;
  }

  private static searchPluginsInFolder(folder: string): string[] {
    return this.recFindByExt(folder, 'mjs');
  }

  private static recFindByExt(base: string, ext: string, files?, result?): any[] {
    files = files || fs.readdirSync(base);
    result = result || [];

    files.forEach((file) => {
      const newbase = path.join(base, file);
      if (fs.statSync(newbase).isDirectory()) {
        result = this.recFindByExt(newbase, ext, fs.readdirSync(newbase), result);
      } else {
        if (file.substr(-1 * (ext.length + 1)) === '.' + ext) {
          result.push(newbase);
        }
      }
    },
    );
    return result;
  }
}


If I change the return of the DynamicModule from

  return {
        module: PluginModule,
        imports: [...allPlugins],
      } as Dynamic

to

  return {
        module: PluginModule,
        imports: [PluginAModule],
      } as Dynamic

The plugin is a simple library created from the cli. It has only one controller which onyl returns a simple string, to test if the module injection works.

it works flawlessly.

Expected behavior

I want to load modules at start of the application from a given folder, which are libs of NestJS.

Environment

Nest version: 6.10.4 For Tooling issues:

  • Node version: v10.16.3
  • Platform: Windows

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:9 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
jmcdo29commented, Nov 3, 2019

I tried messaging you on discord, but haven’t heard back from you. I need a little help getting the set up done correctly

1reaction
Disane87commented, Nov 1, 2019

Yes, there is a repo with this @jmcdo29 https://github.com/Disane87/nestjs-dynamic-plugin/tree/dev

Just clone it and execute it with vscode or somthing else. In plugins/ you find a plugin which is build with a lib of nestjs (found in libs/)

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to deal with NestJS circular dependency when using ...
I'm trying to make a NestJS Library for an API I want to access. I split it off into its own library where...
Read more >
expected the result of a dynamic import() call. instead received
According to you the heavyModule is not a React component. I have a very heavy raw file parsed as string (around 10 mb)...
Read more >
Pipes | NestJS - A progressive Node.js framework
Pipes. A pipe is a class annotated with the @Injectable() decorator, which implements the PipeTransform interface. Pipes have two typical use cases:.
Read more >
API with NestJS #61. Dealing with circular dependencies
A circular dependency between Node.js modules happens when two files import each other. Let's look into this straightforward example: ...
Read more >
Metaprogramming
Notice how, if we find a method to invoke, we then dynamically register a ... that you don't load the same module in...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found