Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I import a type definition module? (error TS2656: ...not a module)

Tags:

typescript

I have a simple npm module, emitter20, that I am trying to add type definitions to. Here is all 20 lines of its source:

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)
        })
    }
  }
}

Here is the index.d.ts file in the emitter20 project root:

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;
}

I have also tried this:

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;
  }
}

I try to import it into my project like this:

import IEmitter = require('emitter20')

export interface SwapManager extends IEmitter {
  manager: any;
}

but I get the following error:

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 do I define and import the type definition for the emitter20 module?

(Aside: Typescript imports/exports... one hell of a learning curve!)

like image 576
Raine Revere Avatar asked Oct 19 '22 07:10

Raine Revere


1 Answers

The problem here is that this is not a "proper" JS module and be warned that it doesn't actually expose any types per se, so you will not be able to subclass it... I think.

If this was a "normal"(?) or ES6 JS module which returned classes, etc you would be able to just simply use a bunch of export this; export that; export default function factory() like you tried, but this is one of those modules that are a function and export = is used in my approach which is documented as:

For backwards compatibility with CommonJS and AMD style modules, TypeScript also supports export-equals declarations of the form export = Point. Unlike default export declarations, which are just shorthand for an export named default, export-equals declarations designate an entity to be exported in place of the actual module.

I know of the following two approaches to get this "typed":

Approach 1: Use an Interface:

Pros:

  • Gives you intellisense/autocomplete and type safety

Cons:

  • Doesn't expose the interface for explicit type declarations (you can't 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;
}

Approach 2: Function + Namespace with the same name

Pros:

  • Gives you intellisense/autocomplete and type safety
  • Exposes the interface

Cons:

  • Looks a bit like dark magic? :)

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;
}

Approach 3: Declare a global namespace

Pros:

  • Gives you intellisense/autocomplete and type safety
  • Exposes the interface
  • Doesn't look like dark magic

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;
}
like image 199
Ivan Zlatev Avatar answered Oct 21 '22 04:10

Ivan Zlatev