node.js - Typescript dynamisk modulinnlasting ved kjøring


Jeg har definert en abstrakt BaseClass i et NodeJS Typescript-prosjekt, og jeg har en liste over avledede klasser som implementerer og utvider denne BaseClass.

// baseModule.ts
export abstract class BaseClass {
  constructor() {}
  abstract method(): void;
}

export interface ModuleConstructor<T extends BaseClass> {
  new (): T
}

export function createModule<T extends BaseClass>(type: ModuleConstructor<T>): T {
  return new type();
}

Jeg prøver å finne en måte å programmere til å opprette en instans av en av disse klassene under kjøring.

Begrensningen her er at jeg ønsker å kunne legge en ny myDerivedClass.ts-fil i prosjektmappen min og få den automatisk inkludert i en liste over tilgjengelige moduler ved kjøretid.

Utviklerflyten vil være 1) lage ny fil myNewModule.ts 2) lage og eksportere en klasse som utvider BaseClass 3) lagre myNewModule.ts til ./myModules

// ./myModules/myNewModule.ts
export class MyModule extends BaseClass {
  constructor() {
    super()
  }

  method() {
    //Do something custom
  }
}

Kjøringstidens flyt (ideelt sett uten å måtte bygge på nytt) vil være 1) Brukeren velger fra en liste over tilgjengelige moduler 2) createModule fabrikkfunksjon oppretter en ny forekomst av valgt modul og sender som en forekomst av

// someOtherClass.ts

const modules = require('./myModules/*') //<- Something to this effect
import { BaseClass, createModule, ModuleConstructor } from './BaseClass'

export class SomeOtherClass {
  public mod: BaseClass
  constructor(mod: ModuleConstructor) {
    this.mod = createModule(mod)
  }
}

for (let m in modules) {
  console.log(modules[m].name);
}

let someObj = SomeOtherClass(modules[m]);
someObj.mod // <- instance of derived class.

Svar #1

Her er løsningen jeg endte opp med å bruke, kanskje det finnes en enklere måte, men.

1) Lag en dynamisk modulinnlastingsfunksjon 2) Bruk NodeJS fs-modulen for å skanne katalogen som inneholder modulene 3) Iterer gjennom hver fil og dynamisk importer filen til en array

// BaseModule.ts
import * as path from 'path'
import { promisify } from 'utils'

const readdirAsync = promisify(fs.readdir);

export async function loadModules() {
  let files = await readdirAsync(path.resolve(__dirname, 'rel/path/to/modules'));
  let imports = await Promise.all(files.map(file => (
    import(path.resolve(__dirname, '..', './exchanges/brokers', file))))
  )

  // this next part will depend on how you're exporting within
  // the module. In my case, each module has an "export class"
  // statement. Typically you would "import { className } from 'moduleName'"

  let moduleNames: { [name: string]: number } = {};
  let modules: ModuleConstructor<BaseClass>[] = [];
  for (let i in imports) {
    Object.keys(imports[i]).forEach((key: string) => {
      moduleNames[key] = modules.length;
      modules.push(imports[i][key])
    })
  }
  return [moduleNames, modules];
}


// someOtherClass.ts
import { BaseClass, loadModules, ModuleConstructor } from './BaseClass'

export class SomeOtherClass<T extends BaseClass> {
  public mod: T
  constructor(mod: T ) {
    this.mod = mod;
  }
}

loadModules()
  .then(([moduleNames, modules]: [string[], ModuleConstructor<BaseClass>[]] => {

    // not necessary, but just for purpose of demonstration
    let names: string[] = Object.keys(moduleNames);
    let name = names[0]; // pick one of the module names

    // use the name dictionary as a lookup into the constructor array
    let someObj = SomeOtherClass(new modules[moduleNames[name]]);
    someObj.mod // <- instance of derived class.
  })
  .catch((err) => {
    console.log(err.message);
  });

Etter å ha gjort dette innså jeg at NPM-pakken systemJS håndterer dynamisk lastning.

Relaterte artikler