What steps should I take to import a module with type definitions? (receiving error TS2656: ...not a module)

I am currently working on enhancing the type definitions for a simple npm module called emitter20. The source code of this module spans 20 lines and looks like this:

module.exports = function() {
  var subscribers = []
  return {
    on: function (eventName, cb) {
      subscribers.push({
        eventName: eventName,
        cb: cb
      })
    },
    trigger: function (eventName, data) {
      subscribers
        .filter(function (subscriber) {
          return subscriber.eventName === eventName
        })
        .forEach(function (subscriber) {
          subscriber.cb(data)
        })
    }
  }
}

Within the emitter20 project root, there is an index.d.ts file structured as follows:

declare module 'emitter20' {
  interface IEmitter {
    on: (eventName: string, cb: (data?: any) => void) => void;
    trigger: (eventName: string, data?: any) => void;
  }
  interface EmitterFactory {
    new(): IEmitter;
  }
  export = IEmitter;
}

In my attempts to refine the type definitions, I have also experimented with the following approach:

declare module 'emitter20' {
  export interface IEmitter {
    on: (eventName: string, cb: (data?: any) => void) => void;
    trigger: (eventName: string, data?: any) => void;
  }
  export interface EmitterFactory {
    new(): IEmitter;
  }
}

When trying to import this module into my project using the code snippet below:

import IEmitter = require('emitter20')

export interface SwapManager extends IEmitter {
  manager: any;
}

I encountered the following error message:

error TS2656: Exported external package typings file './node_modules/emitter20/index.d.ts' is not a module. Please contact the package author to update the package definition.

How can I properly define and import the type definition for the emitter20 module?

(On a side note: Typescript imports/exports... definitely a challenging aspect to grasp!)

Answer №1

The issue at hand is that this JS module does not follow the typical structure and does not explicitly expose any types, making it difficult to subclass... I believe.

If this were a standard or ES6 JS module that returned classes, you could simply use export statements like

export this; export that; export default function factory()
, but in this case, it is a function with the use of export = as described in my approach:

To maintain compatibility with CommonJS and AMD style modules, TypeScript also supports export-equals declarations like export = Point. Unlike default export declarations that are shorthand for an export named default, export-equals declarations specify an entity to be exported instead of the actual module itself.

I am aware of two methods to address this "typing" issue:

Method 1: Implementing an Interface:

Pros:

  • Provides intellisense/autocomplete and type safety

Cons:

  • Does not expose the interface for explicit type declarations (unable to declare a variable with that type)

main.ts

import * as emitterFactory from 'emitter20';

var emitterInstance = emitterFactory();
emitterInstance.on(...);

types.d.ts

declare module 'emitter20' {
    interface IEmitter {
        on: (eventName: string, cb: (data?: any) => void) => void;
        trigger: (eventName: string, data?: any) => void;
    }

    interface IEmitterFactory {
        (): IEmitter;
    }

    var EmitterFactory : IEmitterFactory;

    export = EmitterFactory;
}

Method 2: Function + Namespace Combination

Pros:

  • Offers intellisense/autocomplete and type safety
  • Exposes the interface

Cons:

  • May seem mysterious? :)

main.ts

import * as emitter from 'emitter20';

var emitterInstance : emitter.IEmitter = emitter();
emitterInstance.on("event", (data : any) => {
    console.log(data.foo);
})
emitterInstance.trigger("event", {"foo": "bar"});

Output:

bar

types.d.ts

declare module 'emitter20' {

    function Emitter(): Emitter.IEmitter;
    namespace Emitter {
        interface IEmitter {
            on: (eventName: string, cb: (data?: any) => void) => void;
            trigger: (eventName: string, data?: any) => void;
        }
    }


    export = Emitter;
}

Method 3: Declare a Global Namespace

Pros:

  • Provides intellisense/autocomplete and type safety
  • Exposes the interface
  • Does not appear mysterious

Cons:

  • The Emitter namespace becomes global and visible everywhere (global ambient declaration)

main.ts

import * as emitter from 'emitter20';

var emitterInstance : Emitter.IEmitter = emitter();

types.d.ts

declare namespace Emitter
{
    export interface IEmitter {
        on: (eventName: string, cb: (data?: any) => void) => void;
        trigger: (eventName: string, data?: any) => void;
    }

    export interface IEmitterFactory {
        (): Emitter.IEmitter;
    }
}

declare module 'emitter20' {
    var EmitterFactory : Emitter.IEmitterFactory;

    export = EmitterFactory;
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Testing chai: verifying the inclusion of object types in an array

I am currently in the process of testing a Node.js/Typescript application. My goal is to have my function return an array consisting of objects. These objects should adhere to the following type: type myType = { title: string; description: string; ...

Retrieve the :id parameter from the URL as a numerical value in Node.js using Typescript

Is there a way to directly get the :id param from the URL as a number instead of a string in order to easily pass it on to TypeORM for fetching data based on a specific ID? Currently, I am using the following approach where I have to create an additional ...

An angular component that is functioning properly when connected to a live server, however, it is experiencing issues when trying to run `

I tried integrating versitka into my Angular component by placing the version HTML file and all necessary assets in the appropriate directories. However, when I run ng serve, only the HTML file seems to be working, while the CSS and images fail to load. I ...

IntelliJ IDEA does not support the recognition of HTML tags and directives

I seem to have lost the ability to switch between my HTML and TS files in Intellij IDEA; the tags, directives, and autocompletion in HTML are no longer working. Additionally, I'm receiving some warnings: https://i.stack.imgur.com/QjmNk.png Is there ...

flushMicrotasks does not function properly in conjunction with the image.onload event

Working on an Angular project, I'm currently developing an object with an image field. The method responsible for loading the image returns a promise that resolves in the onload function of the image. When trying to test this method using the flushMi ...

What could be the reason my component is not displaying the ContentChild associated with a directive?

It appears that utilizing a directive to target a content child from another directive is the recommended approach (source). However, why isn't my component able to recognize the component marked with the directive? ./my.component.ts import { Comp ...

Is it possible to use an object's attribute as a switch case in TypeScript with useReducer?

I am attempting to convert switch case String into an object, but for some reason typescript is misunderstanding the switch case within useReducer: Prior to version update, everything was functioning correctly: export const LOGIN_USER = "LOGIN_USER&qu ...

The function "overloading" of the union type is not functioning properly

I attempted to "overload" a function by defining it as a union function type in order to have the type of the input parameter dictate the type of the `data` property in the returned object. However, this resulted in an error: type FN1 = (a: string) => { ...

Problem with MongoDB - increasing number of connections

I have encountered an issue with my current approach to connecting to MongoDB. The method I am using is outlined below: import { Db, MongoClient } from "mongodb"; let cachedConnection: { client: MongoClient; db: Db } | null = null; export asyn ...

Error in Angular 2 component when loading background images using relative URLs from an external CSS skin

In my angular2 component, I am utilizing a third-party JavaScript library. The skin CSS of the component attempts to load images using relative URL paths. Since I am following a component-based architecture, I prefer to have all component dependencies enca ...

What is the best way to incorporate the TUI image editor for Javascript into my Angular application?

Issue - I'm facing a challenge with my Angular application as I want to integrate Toast UI image editor. However, I am unsure about how to properly add the imports to app.module.ts in order to utilize it. Despite following the npm installation instru ...

Angular 7: Polyfill required for npm package to support 'Class'

I am encountering an issue where my Angular 7-based app is not functioning in Internet Explorer 11. The npm package I am using begins in index.js: class PackageClass { // code } While the app works as intended in other browsers, it fails to open in ...

How to modify the background color within the mat-menu-panel

Incorporating angular 9 and less into my current project, I have encountered an issue with a mat-menu-panel where my mat-menu-item is located. While I have successfully changed the color of my mat-menu-item, I am now faced with the challenge of changing th ...

What is the best way to retrieve the current complete URL in a Next.js/Typescript component?

I'm working on a component and I need to retrieve the current full URL. Here's a simplified version of what I have: /** * Share dropdown component */ export const ShareDropdown: React.FC<{ className: string }> = ({ className, }) => { ...

Commitments shatter amidst constructing a website

Utilizing promise and http.get to retrieve data from a JSON API in Wordpress. Once the data is retrieved, it should be displayed on a page... However, an error occurs when attempting to build the page due to the data not being available. What steps can ...

Creating a ref in React with TypeScript to access the state of a child component

Is there a way to access the state of a child component within the parent component without using handlers in the child or lifting the state up in the parent? How can I utilize refs in React with TypeScript to createRef and retrieve the child's state ...

Files are nowhere to be found when setting up an angular project

After creating an Angular project, I noticed that some key files were missing in the initial setup, such as app.modules.ts and app-routing.modules.ts The project was generated using the command ng new name Here is a screenshot displaying all the files th ...

Associating function parameters with object types in TypeScript

In the conclusion of this post, I provide operational code for associating object types with a function that accepts an object containing matching properties. The code snippet I shared results in 'result' being resolved as: type result = { GE ...

What is the process for setting up a Quill Editor within an Angular 2 Component?

I am currently working on creating my own Quill editor component for my Angular 2 project. To integrate Quill into my project, I utilized npm for installation. My goal is to develop a word counter application using this component and I am referring to the ...

Is it feasible to access a service instance within a parameter decorator in nest.js?

I am looking to replicate the functionality of Spring framework in nest.js with a similar code snippet like this: @Controller('/test') class TestController { @Get() get(@Principal() principal: Principal) { } } After spending countless ho ...