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

1

6

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.

Catalan answered 24/10, 2017 at 18:0 Comment(5)
The top-level import makes the file a module. Try removing it.Athabaska
Thanks for the response, @Aaron, but I just tried removing the top-level import and the result is the same.Catalan
Try putting "hapi" in quotes: declare module "hapi". You might also need to export the Server interface.Athabaska
@Aaron - putting 'hapi' in quotes fixed the issue! Thanks! Not sure I understand why though. If you want to add your comment as an answer, I'll accept.Catalan
Sure, I'll try to explain in answer...Athabaska
A
6

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.

Athabaska answered 25/10, 2017 at 16:16 Comment(2)
Thanks for the thorough explanation, @Aaron. Although the only change I needed to make was wrapping 'hapi' in quotes. If I remove the import statement, my declaration seems like it is overwriting the hapi module definition instead of augmenting it - the only properties I can access are ones that I define myself. So I think the import statement is necessary for augmentation.Catalan
@NathanFriend Hm, you're right. I think its actually a top-level export that makes it a module, or else being a module doesn't actually change the effect of declare module "hapi". You can also import inside the module declaration. I'll edit my answer... don't want this answer to mislead anyone. :)Athabaska

© 2022 - 2024 — McMap. All rights reserved.