nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
67.7k stars 7.63k forks source link

How to load modules dynamically based on some condition in @Module imports #450

Closed soundarpandiyan closed 6 years ago

soundarpandiyan commented 6 years ago

we are planning to use nest js framework for middleware application using node js and typescript. i would like to load my modules (module1, module2.. ) dynamically based on the value given in the xml. It will help us to plug-in the feature to the application dynamically. your support and help are much appreciated on this. @Module({ imports: [...object], // object contains all the imported modules dynamically })

BorntraegerMarc commented 6 years ago

This is not a native typescript feature yet but could be supported by the standard of promise imports: http://2ality.com/2017/01/import-operator.html At the moment this is not available on NodeJS yet (as far as I know). So this is the wrong place for this issue. I suggest to close this issue.

kamilmysliwiec commented 6 years ago

@soundarpandiyan https://docs.nestjs.com/modules Dynamic Modules section.

BorntraegerMarc commented 6 years ago

Seems like I misunderstood the question. Sorry 😅

soundarpandiyan commented 6 years ago

Hi kamil, marc. thanks for the response. I'm using DynamicModules section and posted the sample code and the issue. Can you please gives some idea on this

20:49 core module - applicationmodule plugin module - Ecash, AccountManagement, etc.. At the time of loading the core module i want to load the config file and check for the necessary plugin module and load in the ApplicationModule ex. @module { modules : [modules will be retrieved dynamically]} sample code: // code is not accurate just for demo purpose In Helper class, static getmodules () : DynamicModule{

Promise.all([ import('./ecash/ecash.module')]) .then(([myModule]) => { console.log(myModule); var EcashModule = myModule.EcashModule; });

Promise.all([ import('./account.module')]) .then(([myModule]) => { console.log(myModule); var AccountModule = myModule.AccountModule; });

    return {
        module : [],
        modules : [EcashModule, AcountModule]};
}

In ApplicationModule class, @Module{ modules: ...Helper.getmodules().modules}

Issue: ApplicationModule is not waiting until the promise is resolved. In the mean time itself it try to load the Promise and error has been thrown. How to make the ApplicationModule to wait and load the module once promise is resolved

I have posted it in the Gitter and waiting for some good solutions. I'm new to UI so code was bit nasty above.

soundarpandiyan commented 6 years ago

Hi guys, Is it possible to achieve Dynamic loading of modules in nestjs. we are in the discussion on taking decision on using this framework for one our project. It would be great helpful if you could someone confirm on this. Thanks in advance

soundarpandiyan commented 6 years ago

Hi, i have done this way. Does anyone have better suggestions. The only drawback is i'm not able to import dynamically but still achieve the requirement.


In Helper class,
import { Module1 } from '../Module1;
import { Module2} from '../Module2';
import { Module, DynamicModule } from '@nestjs/common';
var fs = require('fs'), xml2js = require('xml2js');
var _ = require('underscore');

export class DynamicModuleLoader{
static ModulesObject = [];
static PushModules(){
DynamicModuleLoader.ModulesObject.push(
{key: 'Module1', value: Module1},
{key: 'Module2', value: Module2}
);
}

static  GetDynamicModules() : DynamicModule  {                
var Plugins = [];
var parser = new xml2js.Parser();    
DynamicModuleLoader.PushModules();    
let fileData  = fs.readFileSync('.\shared.moduleconf.xml', 'ascii');       
parser.parseString(fileData.substring(0, fileData.length), function (err, result) {        
    _.each(result.Modules.Module, (res) => {                    
            let moduleDetails = DynamicModuleLoader.FindModulesByName(res.Name);
            if(moduleDetails && res.PluginStatus == 'true')
            Plugins.push(moduleDetails.value);
    })            
});   
    return {
        module : [],
        modules : [...Plugins]};     
}  

static FindModulesByName(moduleName)
{
  let extractedModule = _.find(DynamicModuleLoader.ModulesObject, function(x){
  return x.key == moduleName;
  });

  return extractedModule;
}
}

XML file:
<Modules>
<Module>
<Name>Module1</Name>
<PluginStatus>false</PluginStatus>
</Module>
<Module>
<Name>Module2</Name>
<PluginStatus>true</PluginStatus>
</Module>
</Modules>

In Core Module class:
@module(
{
modules: [ SharedModule, ...DynamicModuleLoader.GetDynamicModules().modules]
})
fwoelffel commented 6 years ago

Please format you code with ``` Read this https://help.github.com/articles/creating-and-highlighting-code-blocks/

soundarpandiyan commented 6 years ago

I got the solution from gitter community. Thanks for your time. It was great architecture to work with Nest js.

ericzon commented 6 years ago

@soundarpandiyan this was your last solution or at last you could improve it?

soundarpandiyan commented 6 years ago

@ericzon - Before creating the core module, we are importing the modules dynamically and added in the core module using reflect. I have mentioned the code as well below so it may be useful to someone. Please let me know if any other better approaches as well.

Thanks @luqezman for pointed out this idea.

async function bootstrap() {
  var loadedModules;

  loadedModules = await GetDynamicModules();
  const currentModules = Reflect.getOwnMetadata('modules', ApplicationModule);
  Reflect.defineMetadata('modules', [...currentModules, ...loadedModules], ApplicationModule);
  const app = await NestFactory.create(ApplicationModule);
  await app.listen(9000);
}

async function GetDynamicModules() {
  var Plugins = [];
  var parser = new xml2js.Parser();
  let fileData: any;
  let result: any;

  fileData = await new Promise((resolve, reject) => fs.readFile('e:\\Apigee\\NodeServerNestJs\\src\\Shared\\shared.moduleconf.xml', 'ascii', function (err, data) {
    if (err) reject(err);
    else resolve(data);
  }));

  result = await new Promise((resolve, reject) => parser.parseString(fileData.substring(0, fileData.length), async function (err, result) {
    if (err) reject(err);
    else resolve(result);
  }));

  for (const moduleDetails of result.Modules.Module) {
    if (moduleDetails.PluginStatus == 'true') {
      let detail = await GetModules(moduleDetails);
      Plugins.push(detail[moduleDetails.Name[0]]);
    }
  }

  return new Promise(resolve => resolve(Plugins));
}

async function GetModules(moduleDetails) {
  let promise = await new Promise(resolve => resolve(import(moduleDetails.Path[0])));
  return promise
}

bootstrap();

xml file:

<Modules>
<Module>
<Name>Module1</Name>
<PluginStatus>false</PluginStatus>
<Path>.//Module1//Module1.module</Path>
</Module>
<Module>
<Name>Module2</Name>
<PluginStatus>true</PluginStatus>
<Path>.//Module2//Module2.module</Path>
</Module>
</Modules>
lucasmonstrox commented 6 years ago

@soundarpadian, hope the code is working :)

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.