0

Currently I am tasked with creating node modules from our Vanilla JS and moving them to typescript. I rewrote them as classes, added some functionality, wrote a legacy wrapper and the corresponding web pack configuration. The problem is that some of these modules are singletons, so instead of exporting the class by default we want to export an class instance as default. The problem is that I don't get the type checking to work properly:

import DebugJs from 'debug';
const test = (test: DebugJs) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

The problem here is that DebugJs is not recognized as a type. So currently I have to import an additional Interface in order to set the type properly.

Just for comparsion, this is what I currently do instead:

import DebugJs, { DebugJsInterface } from 'debug';
const test = (test: DebugJsInterface) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

I tried around with namespaces and module declaration, but to be honest, as someone who is totally new to node module creation and quite new to typescript I don't really know what I am doing there.

Here is my current index.d.ts file setup

import DebugJs from './src/debugJsModule';
import {DebugLevels} from "./src/types/types";

export interface DebugJsInterface {
    levels:DebugLevels;
    enabled:boolean;
    level:number;
    console(...arguments: any): void;
    enableDebug(): void;
    disableDebug(): void;
    setLevel(level:number): void;
}

export interface Module {
    DebugJs: DebugJsInterface;
}

export default DebugJs;
declare module 'debug';

Here the DebugJsInterface is defined as a work around. I am also a little puzzled since I thought that the idnex.d.ts should only have type information. But if I don't export the class instance from here my module import won't be recognized as a class properly.

This is my debugJsModule wrapper which returns a class instance:

import DebugJs from './class/DebugJs';
import { DebugLevels } from 'debug/src/types/types';

const DebugJsInstance: DebugJs = new DebugJs();

export default DebugJsInstance;

The class itself is simply defined as a class and exported as a default export as well

 class DebugJs { ... } 

Just to be clear, functionality wise everything works, all I want to figure out how I can have the proper type of my importet class instance by using the same import name, in this case DebugJs, without having to rely on an extra importet interface as a workaround.

3
  • You should paste how you're defining DebugJs, exporting it and how the singleton is made. In your first example, DebugJS should absolutely work as a type if it is a class or a type. However, if you're exporting the singleton as default it wont work. It is not clear what DebugJs is and how you're exporting the type/class and the singleton. Commented Jun 3, 2019 at 12:42
  • Actually how the singelton is made is at the bottom, the class itself is simply a class definition: ``` class DebugJs { ... } ``` Also exported as default. Everything works fine as long as I export the class itself, but as soon as i export the instance instead I cant use the export as the Type definition anymore. Commented Jun 3, 2019 at 12:45
  • Oh yes, I saw the import DebugJs and though that file was merely reexporting things, me bad Commented Jun 3, 2019 at 12:47

2 Answers 2

1

I don't now if this kind of patterns may suite you, but I usually do this by placing the class and the singleton in the same file, and exporting the singleton as default and the class as a named export:

class DebugJs {
  // code
}

const debug = new DebugJs();

export default debug;
export {DebugJs};

Then in a client if I import the singleton:

import debug from '.../path';

debug.format().log().whatever(); // typings and intellisense works fine. debug will be recognised as DebugJs instance.

And, in the event you need a fresh instance of DebugJs unrelated to the singleton, you can:

import {DebugJs} from '.../path';

const debug = new DebugJs(); // typing and intellisense will work

I'm pretty sure this will work because I use this pattern a lot.

However I don't know properly why your setup is not working. I don't know if using default exports for types, or maybe combining that with the singleton re-export may be causing your issue.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, but I still cant use DebugJs as an type that way. My IDE tells me "Cannot find name DebugJs"
If you want to annotate DebugJs a in: (test: DebugJs) => {...} you always need to import the DebugJs class to use it as a type. That's just how typescript works, as far as I know. However, you should not need to annotate the singleton to retain type-checking and intellisense. Importing the singleton will cause the typechecking and the intellisense to work, so, if you call .foo on the singleton you will get an error, and if you start typing a method you will get autocomplete. However, if you want to use the DebugJs type to annotate another variable, you must to import it directly.
1

This snippet doesn't really make that much sense:

import DebugJs from 'debug';
const test = (test: DebugJs) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

Here you are not asking for an instanceof DebugJs, you are actually telling typescript to expect an instance of an instance of DebugJs.

Thing is, I don't think it makes any sense here. Why import a instance of something and then immediately not use it?

Anyway, it is possible, it just requires this syntax:

const test = (test: typeof DebugJs) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

You'll probably either want to :

  1. Not import the instance, but only import DebugJsInterface instead. That's what you should use for the type guard.
  2. Or: don't require it as an argument.

Example 1:

const test = (test: DebugJsInterface) => {
    test.console('warn', 'does', 'respond', 'with a warning', test);
};

Example 2:

const test = () => {
    DebugJs.console('warn', 'does', 'respond', 'with a warning', test);
};

I suspect you want one of the above

3 Comments

Nope, actually I simply want to get the Class instance and want to use the same name to check if the passed argument is from the type DebugJs without having to import an seperate Interface in order to do so. It seems more and more like that this is not possible
@DustinGogoll I just made an edit that does this for you. It still seems a bit weird, but .. it works
Thank you very much! :) Yeah, I also understand the weirdness now, I am quite new to typescript and strictly typed languages in general and still have to get used to it! I probably will import the class then, it seems like this is the better way

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.