5

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

1 Answer 1

3

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; } 
Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the response! I copied the code exactly for Approach 2 and I still got the "not a module" error :/.
@Raine Both work for me, including running the code. I have also previously used Approach 2 for typings for another module. Make sure the d.ts file is not excluded (or if you use explicit file list - make sure it's included in your tsconfig.json. I have added an example with out put to the question. Please consider marking the answer as accepted
What version of typescript are you using?
latest stable 1.8.x
Thanks Ivan. I'm glad it is working for you, as it means I must still be missing something! I tried isolating it down to the smallest possible reproducible state in this gist: gist.github.com/raineorshine/33390aec8abc0ee403ee12c2caff4d89. I am either getting a "module not found" or "emitter20 is not a module" error no matter what I do.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.