0

I am dynamically loading modules based on which are present in a folder. Basically I'm iterating over some folders and calling require, saving the result in a Map.

E.g., the loader does this:

const factories = new Map<string, Object>();
:
: foreach loop looking for valid dirs...
:
const obj = require(`${__dirname}/${dir.name}/index.ts`);
factories.set(dir.name, obj);

Cool, this works great!

Each factory index.ts above exports a singleton of a class:

export default{
    factory: new X_Factory() 
};

Also works, woo.

Then later on I attempt to call a function that exists on in the factory class:

const y: Object|undefined = factories.get('dynamic_module_name');
// @ts-ignore noImplicitAnyForClassMembers
y.default.factory.sync();

grr

If I remove the ts-ignore, I have no idea how to make TypeScript happy.

Suggestions? I prefer to not hack my way out of this. I tried interfaces and using Module, NodeModule and NodeRequire instead of Object, but that doesn't capture the fact that the module has a specific interface (I also tried to create an interface, but couldn't get it to work).

I thought this answer might be useful, but I'm not sure it is the same TS.

PeterT
  • 920
  • 8
  • 20

1 Answers1

1

Typescript is complaining because there are at least 2 problems with your code that I can see.

Firstly: y might be undefined, which means y.default will give you run time error.

Secondly: Even IF y is defined, you have told typescript that y can be any object type. There is no guarantee that the object have the property default. If default is undefined then default.factory would give you a run time error.

To solve the first issue, you need to programmatically check that y is NOT undefined.

if(!!y){
  y.default.factory.sync();
}

To solve the second issue, you need to use a custom type instead of a generic object type as the type of y. You will also need to change your Map to a map of string and your custom type.

eg:

type CustomType = {
  default: {
    factory: {
      sync: () => void
    }
  }
}
const factories = new Map<string, CustomType>();
const y: MyType|undefined = factories.get('dynamic_module_name');

With this, Typescript MAY be satisfied depending on your tsconfig, but I recommend that when you do the require statement, you set obj to type unknown like so:

const obj:unkown = require(`${__dirname}/${dir.name}/index.ts`)

Because it's a dynamic import, Typescript cannot check that the obj is of the correct type. Setting it to type unknown will prevent you from using obj before you programmatically check that the obj satisfies all the constraints of your CustomType before proceeding.

davidx1
  • 3,525
  • 9
  • 38
  • 65
  • Thanks! I was unaware of `type`, I had been trying to define an `interface`. I do love webpack, but dynamic loading is necessary sometimes. (Also, I removed the undefined check for brevity). – PeterT Oct 23 '20 at 15:28
  • I don't think `interface type` is your problem. `type aliasing` and `interface type` are interchangeable in most common scenarios. See here: https://stackoverflow.com/questions/37233735/typescript-interfaces-vs-types – davidx1 Oct 25 '20 at 00:02