typescripttypescript-typingstypescript2.0typescript-definitionstypescript-declarations

Adding properties to existing TypeScript interface via module augmentation has no effect


I have a Node application that depends on @types/hapi. I'd like to add a property to one of the classes defined in this module. I've tried defining my new property via module augmentation:

// my-custom-hapi-typings.d.ts
import * as hapi from 'hapi';

declare module hapi {
    interface Server {
        myProperty: string;
    }
}

This doesn't cause any compiler errors, but it also doesn't add the myProperty property to the Server class. I still get errors elsewhere in my project when I try and reference the property like this:

// my-other-file.ts
import * as hapi from 'hapi';

const server = new hapi.Server({ ... });

// error: Property 'myProperty' does not exist on type 'Server'.
server.myProperty = 'hello'; 

Why does the TypeScript compiler seem to be ignoring my .d.ts file? Do I need to "import" the file or somehow make the TypeScript compiler aware that this file exists? I was under the impression that just placing a .d.ts file in the source directory was enough for TypeScript to pick up on these augmentations.


Solution

  • This is your issue:

    declare module hapi {
    

    This is actually defining a namespace, using older syntax that pre-dates ES6 modules which used to be called "internal modules". Writing module hapi is the same as writing namespace hapi, which is the preferred syntax today. More on namespaces vs modules here.

    To declare an external module you just have to put the module name in quotes:

    [...] use a construct similar to ambient namespaces, but we use the module keyword and the quoted name of the module which will be available to a later import.

    In other words, just put hapi in quotes:

    declare module "hapi"
    

    Now you have an ambient (my-custom-hapi-typings.d.ts doesn't need to be directly imported) external module definition (declares what import "hapi" gives you), and you can augment whats declared inside it.

    If you mouse over the module hapi and module "hapi" in the Playground you'll see the difference in the tooltips:

    enter image description here enter image description here

    Yes, it's subtle and confusing due the history behind it.