Hello! I'm sorry to both you with something that is surely my problem, but I would love to get this figured out. I have been coding for 17 years and this has got me really stuck!!! I must be missing something stupid.
FWIW, I have read documentation over and over, I have posted to StackOverflow, and I have not found a solution yet. I have to be humble and ask for support to try to overcome this.
TypeScript Version:
1.8.10
Code
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;
}
}
And from others' suggestions, I have also tried this:
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;
}
I try to import it into my project like this:
import IEmitter = require('emitter20')
export interface SwapManager extends IEmitter {
manager: any;
}
Or this:
import * as emitter from 'emitter20'
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.
With other attempts, I have gotten a "cannot find module" error as well. I have tried many things at this point to no avail.
How do I define and import the type definition for the emitter20 module?
Thank you so much.
There is a lot of confusion around this topic because there are many different ways to write these definitions depending on your target audience.
declare namespace Emitter {
export interface Emitter {
on: (eventName: string, cb: (data?: any) => void) => void;
trigger: (eventName: string, data?: any) => void;
}
}
declare module 'emitter' {
import E = Emitter.Emitter;
export = E;
}
Actually, I was assuming relative paths. See my updated example.
As @aluanhaddad noted, we need to know how this module is consumed. I am assuming here it is a node-only module.
I would recommend using export= on an identifier. so your module would look like:
// emitter20/index.d.ts
declare class Emitter {
on: (eventName: string, cb: (data?: any) => void) => void;
trigger: (eventName: string, data?: any) => void;
}
export = Emitter; // this exports a function that returns objects of type Emitter, and a type called Emitter
you would import this as:
import emitter20 = require("emitter20");
var emitter = new emitter20();
emitter.trigger("event");
@mhegazy I can't get your example to work without using a relative import.
import Emitter = require('./emitter); // OK,
import Emitter = require('emitter'); // Error

From the example above, emitter20 is an existing npm package. my sample assumes that you will be importing this from node_modules\emitter20. The index.d.ts should live next to index.js in the package.
declare module "emitter20" adds a new name ("emitter20") to the global namespace. generally polluting the global namespace is not advisable, though would work.
@mhegazy Ah, I see. So the non relative resolution works because of the index.d.ts? I see what you mean about polluting the global namespace with the name of the module, I was just trying to guard against using without importing.
if you are copying this declaration file locally, then you have to use module declaration (i.e. declare module "emitter20" { ..), and there is not another way. if this is an npm package we can do both, and not using module declarations in this context is the recommended approach.
@mhegazy Thank you for clarifying.
Thanks for your response! Unfortunately I am getting a different error now:
src/swap-manager.ts(38,19): error TS2339: Property 'manager' does not exist on type 'Emitter'.
src/swap-manager.ts(75,10): error TS2322: Type 'Emitter' is not assignable to type 'SwapManager'.
Property 'manager' is missing in type 'Emitter'.
I have index.d.ts next to the index.js in the emitter20 project, which is npm linked to the current project, so it sees it as any other npm module.
I wonder if this has something to do with trying to extend SwapManaper with Emitter? I have heard that there is both a variable space and a type space with typescript.
@raineorshine could you post your current .d.ts file as well as _swap-manager.ts_?
And yes, TypeScript has a _value_ space and a _type_ space.
Sorry for the confusion. I've tried so many different versions from different people's suggestions and none of them have worked yet. Not wanting to give up on typescript. You would think creating type definitions for a canonical module require would be straightforward? Okay... back to the issue...
Current d.ts file:
declare namespace Emitter {
export interface Emitter {
on: (eventName: string, cb: (data?: any) => void) => void;
trigger: (eventName: string, data?: any) => void;
}
}
declare module 'emitter' {
import E = Emitter.Emitter;
export = E;
}
swap-manager.ts:
import IEmitter = require('emitter20')
export interface SwapManager extends IEmitter {
manager: any;
}
export default function(): SwapManager {
return new Emitter()
}
Also trying to work through it here, where @ivanz has also been valiantly trying to provide a solution! https://gist.github.com/raineorshine/33390aec8abc0ee403ee12c2caff4d89
Thanks for your help!
For one thing you have declared Emitter as an interface. Interfaces cannot be instantiated only implemented. Unfortunately the error message is a bit confusing. It is informing you that it cannot find IEmitter. This is because the target of the new operator must be a value. Classes occupy an interesting place in the language because they are represent both a _type_ and a _value_. If you change the declaration of Emitter from an interface to a class, you will get a different error, specifically the one you mention in your second to last post.

You are receiving this error specifically because of the return type you have specified for default. The function states that it will return a SwapManager but it is defined as returning an Emitter. Since SwapManager has an extra property not present on Emitter you rightly get an error.
Try
import Emitter = require('emitter');
export class SwapManager extends Emitter {
manager: any;
}
export default function(): SwapManager {
return new SwapManager();
}
@raineorshine Here is a working sample with type declarations for that module: https://github.com/ivanz/emitter20-typescript-types-sample
You can npm install && npm start to build and test it.
Can someone comment why does the below also work as an _alternative way_ to avoid declare namespace and if it is actually designed to to work that way (or is a bug/side-effect)?
Essentially there is both a namespace and a variable with name Emitter and the module is exporting with export = Emitter;
declare module 'emitter20' {
namespace Emitter
{
export interface IEmitter {
on: (eventName: string, cb: (data?: any) => void) => void;
trigger: (eventName: string, data?: any) => void;
}
export interface IEmitterFactory {
(): Emitter.IEmitter;
}
}
var Emitter : Emitter.IEmitterFactory;
export = Emitter;
}
import * as emitter from 'emitter20';
var emitterInstance : emitter.IEmitter = emitter(); // works fine
Can someone comment why does the below also work as an _alternative way_ to avoid
declare namespaceand if it is actually designed to to work that way (or is a bug/side-effect)?
Backwards compatibly reasons namespace came after module because of the confusion around that keyword. One of the _great_ mistakes of TypeScript in that it was decided before there was clear direction from TC39 on the way modules would work in ES6 and one of the main reasons the TypeScript team doesn't like getting near syntactical features which maybe addressed by TC39 in the future.
@kitsonk What about the "clash" between variable and the exported namespace? The module type declaration's export is referring to "Emitter" (export = Emitter), which can either be the variable Emitter of type Emitter.IEmitterFactory or the namespace Emitter? Shouldn't that result an error?
In typescript declarations can "merge". an example of this is multiple declarations of the same interface, each adding a set of properties. another is merging a namespace with only types and a value. when using export = <identifier> you are exporting all meanings of this name (i.e. both the namespace Emitter and the value Emitter).
you can read more about this in http://www.typescriptlang.org/docs/handbook/declaration-merging.html
@raineorshine, has the original issue been resolved? or are you still running into issues?
@mhegazy I will try out your suggestions! Sorry for the delay. I am not a full-time developer so sometimes a couple days go by when I am at my other job.
@aluanhaddad Thank you, that works! I guess I just got tripped up on the error messages and didn't go back and take a closer look at why I was using interface instead of class. I am glad to have this issue as a resource in case I run into a similar problem again.
@ivanz Thanks for your help and persistence!
Glad to be of service.
:(
Now there are runtime errors:
swapManager.on('request', function (data) {
^
TypeError: swapManager.on is not a function
Why does a SwapManager instance not have the same methods as Emitter when it extends it?
import Emitter = require('emitter20')
export class SwapManager extends Emitter {
manager: any;
}
console.log(new Emitter()) // { on: [Function], trigger: [Function] }
console.log(new SwapManager()) // SwapManager {}
Why does a SwapManager instance not have the same methods as Emitter when it extends it?
your implementation of emitter20 puts the methods on the instance and not the prototype. so the swapManager instance does not inherit it.
consider writing your emitter as:
module.exports = (function () {
function Emitter() {
this.subscribers = [];
}
Emitter.prototype.on = function (eventName, cb) {
this.subscribers.push({
eventName: eventName,
cb: cb
});
};
Emitter.prototype.trigger = function (eventName, data) {
this.subscribers
.filter(function (subscriber) {
return subscriber.eventName === eventName;
})
.forEach(function (subscriber) {
subscriber.cb(data);
});
};
return Emitter;
}());
Because the actual code for emitter, in _index.js_, is a factory which returns an object literal. The value returned by it can be modeled with a class declaration to describe its type, but if you want to use the new operator and subclassing, you need to make Emitter a function which is meant to be newed, such a function would not return an object literal but would instead assign properties to this and not explicitly return anything. In other words the type declarations don't accurately describe the library they are typing. Now classes are not your only option there are lots of other patterns and TypeScript supports them. The key thing is, when you're writing a declaration filed from scratch you need to make sure that it matches the underlying code.
And that exactly why I modelled the typings as an exported variable with a defined interface :) On a further note - I just realised that this is your own NPM package you are trying to create typings for ( https://github.com/raineorshine/emitter20 ). Having just looked at your code:
declare) in https://github.com/raineorshine/emitter20/blob/master/index.d.ts - that's only necessary if e.g. you writing typings for a 3rd party package outside of the code of the packagetypings: 'index.d.ts' inpackage.json` to make the TS typings discoverableHave a look at my attempt to explain on my blog: http://ivanz.com/2016/06/07/how-does-typescript-discover-type-declarations-definitions-javascript/
@mhegazy Okay... I never intended it to be a class. I started going that direction because of the suggestion to make SwapManager a class, but I guess we will have to retrace our steps a bit.
emitter20 is a factory with the intention to be used as a _mixin_ on other objects. I want SwapManager to be a factory that returns an object mixed in with Emitter.
That is exactly why I modelled the typings as an exported variable with a defined interface :)
On a further note - I just realised that this is your own NPM package you are trying to create typings for ( https://github.com/raineorshine/emitter20 ). Having just looked at your code I want to make you aware that:
declare) in https://github.com/raineorshine/emitter20/blob/master/index.d.ts - that's only necessary if e.g. you writing typings for a 3rd party package outside of the code of the package. That is maybe what you are doing now in a test project where the package is installed.typings: 'index.d.ts' inpackage.json` to make the TS typings discoverableHave a look at my attempt to explain on my blog: http://ivanz.com/2016/06/07/how-does-typescript-discover-type-declarations-definitions-javascript/
Regarind mixins: http://www.typescriptlang.org/docs/handbook/mixins.html
@raineorshine try something like
export default function swapManager(): Emitter & { manager } {
const emitter = new Emitter();
emitter.manager = ......
return emitter;
}
If you are using the interface/factory approach @ivanz suggested the declaration file will be a little more complicated.
Edit: Better to use the factory approach since it is what you intended initially.
Here is a working sample with type declarations for that module: https://github.com/ivanz/emitter20-typescript-types-sample
It looks like you got it to work for a full-fledged class, which is a bit different than what I am going for. Here is the implementation that I am trying to write the correct type definition for:
export default function(): SomeType {
const e = new Emitter()
e.value = 10
return e
}
I would like to maintain my implementation decisions and come up with a type definition that matches that.
@aluanhaddad
error TS2322: Type 'Emitter' is not assignable to type 'Emitter & { manager: any; }'.
Type 'Emitter' is not assignable to type '{ manager: any; }'.
Property 'manager' is missing in type 'Emitter'.
Because I'm adding the manager property after assigning to the variable a type assertion is required
Is there a workaround?
The simplest solution in this case, since we are augmenting a value returned by another function or constructor is to use a type assertion.
For example
return e as Emitter & { manager };
This is not exactly ideal, but since we are changing the value and hence its type by mutating it to add an additional property, it is a reasonable approach.
Edit: fixed garbled speech to text output.
In general, when a module exports a factory function it is good practice to also export an interface which corresponds to the type it returns. When a module exports a class, this is often unecessary because a class is simultaneously a value and a type declaration.
Thanks @aluanhaddad. This solved the most recent problem:
import Emitter = require('emitter20')
export type SwapManager = Emitter & { manager }
export default function(): SwapManager {
const swapManager = new Emitter() as SwapManager
swapManager.manager = ...
return swapManager
}
So, I am trying to import the swap-manager.ts file from my above comment.
swap-manager.ts:
import Emitter = require('emitter20')
export type SwapManager = Emitter & { manager }
export default function(): SwapManager {
const swapManager = new Emitter() as SwapManager
swapManager.manager = ...
return swapManager
}
api.ts:
import SwapManager from './swap-manager'
export default function(swapManager: SwapManager): express.Router {
const router = express.Router()
...
return router
}
I get this error:
src/api.ts(34,38): error TS2304: Cannot find name 'SwapManager'.
Any ideas? Thanks.
You're importing the default export which is a value not a type. It depends on how you want to structure your code and what your intent is.
The following will work
import { SwapManager } from './swap-manager'; // imports the member with that name
import sw from './swap-manager'; // imports the default export.
It's important to note that we're dealing with a lot of different and interrelated concepts here some of them are TypeScript specific and some are not.
We are using ES6 module syntax.
We are in a commonjs environment.
We are mixing classes and factories for ad-hoc extensibility.
We are using type aliases along with type literals and type operators along with classes to describe these constructs.
I suggest you study the interaction between types within the context of a single module to understand the typescript specific aspects independently. Consider what a class in JavaScript actually represents and how it represents a value and a type simultaneously. Consider the runtime and the semantics of the require function. Then examine the interaction between these things and how TypeScript maps these worlds together in a conceptual space.
Thanks @aluanhaddad. There are many interrelated concepts. I made the poor assumption that they would "just work" as they seem to do when I am coding just JS. Typescript requires some extra work I see to properly import values and types. I will try to have greater acceptance for this and take a more gradual learning approach than expecting I can start using everything right away. Thanks for your help!
@raineorshine glad to help.
@raineorshine any further discussion regarding this specific case would probably be best done elsewhere.
could anybody explain this on my simple example:
//commonjs-export-function.js
module.exports = function() {
return 'func';
};
//commonjs-export-function.d.ts
declare function func(): string;
export = func;
//main.ts
import func from './commonjs-function';
console.log(func());
When I run tsc I get this error:
tsc main.ts && node main.js
main.ts(1,8): error TS1192: Module '"/Users/aleksandar/projects/typescript-playground/commonjs-function"' has no default export.
in main.js you need to use import func = require('./commonjs-function');
Most helpful comment
There is a lot of confusion around this topic because there are many different ways to write these definitions depending on your target audience.