TypeScript: Can't augment a module from global scope

I’m having a specific typings file which has a modern shape like this.

    // something.d.ts
    
    declare function something(...): ...;
    declare namespace something {
      interface A { ... }
      interface B { ... }
    }
    
    export = something;
    export as namespace something;

I’m trying to consume this module in file which is not a module, so the official module augmentation doesn’t work. My task would be to add another function to the something namespace root, and another to the A interface.

I’ve tried to utilize namespace merging, but it seems like it’s not working because instead of merging it thinks that my declaration is the only one, so the original stuff coming from the typings file is not visible at all.

    // myGlobalFile.ts
    
    declare namespace something {
      function extraFunction();

      interface A {
        extraFunctionOnA();
      }
    }

As far as the interface augmentation goes, I’ve also tried the silly syntax like

    // myGlobalFile.ts
    
    interface something.A {
      extraFunctionOnA();
    }

But of course that’s a syntax error so no way it could work, but it represents half of my goal quite well.

I’ve put this as a question onto SO but I got no answer nor a comment until now. (https://stackoverflow.com/questions/45505975/how-to-augment-a-module-in-global-context)

TypeScript Version: 2.4.2

Expected behavior: The module can be augmented from global scope.

Actual behavior: The augmentation actually kind of overwrites the entire module namespace.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 2
  • Comments: 16 (7 by maintainers)

Most upvoted comments

The only way I was able to get this to work with 'lodash' (IIRC, this used to work correctly but I can’t find any code that demonstrates that at the moment)

was to create a .d.ts file like the following

export {}; // this file needs to be a module
declare module "lodash" {
  interface LoDashStatic {
    addedToLoDash(): void;
  }
}

The augmented _ is then available in global scope.

_.addedToLoDash();

const chain = _.chain;

you can do something like:

declare module "mod" {
    module "lodash" {
        interface LoDashStatic {
            addedToLoDash(): void;
        }
    }
}

i think we need to revisit our augmentation vs. re declaration implementation for modules.

Personally, I find

// augmentations.ts
export {} // ensure this is a module
declare module "lodash" {
    interface LoDashStatic {
        addedToLoDash(): void;
    }
}

easier to read.

Ah I see now, thank you very much for the clarification. Yes this is a bit confusing indeed. Removing the namespace works like a charm. Thanks again.

That is almost correct but the the reason it doesn’t work as you expect is that the merged declaration in @types/videojs

function videojs(id: any, options: videojs.PlayerOptions, ready?: () => void): videojs.Player;
namespace videojs {...}

is exported as the value of the module itself. This means that you need to remove the namespace that wraps the additional members you declare because your augmentation is actually modifying the modules exported value.

export { }

declare module "videojs" {
  function getComponent(name: string): any;
  function registerComponent(name: string, definition: any);
  function extend(target: any, source: any);
  function plugin(name: string, constructor: new (...args: any[]) => any);

  interface Player {
    loadVideo(videoData: any);
  }
}

I use the same technique in the LoDash example actually, since LodashStatic is declared inside the namespace _ in @types/lodash but in the augmentation, the merged interface is at top level.

This can be a bit confusing.

Thank you this is a nice and elegant idea, even has an extra level of separation of concerns. I’m going to try this soon and give some feedback.