Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't augment a module from global scope #17736

Closed
cervengoc opened this issue Aug 11, 2017 · 16 comments
Closed

Can't augment a module from global scope #17736

cervengoc opened this issue Aug 11, 2017 · 16 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@cervengoc
Copy link

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.

@farfromrefug
Copy link

I am facing the same issue with lodash module where i can't augment it (microsoft/types-publisher#367)

@cervengoc
Copy link
Author

@mhegazy sorry for pinging you but could you please give some feedback on any progress on this one? This one blocks us from using several updated typings.

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Aug 17, 2017

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;

@cervengoc
Copy link
Author

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.

@mhegazy
Copy link
Contributor

mhegazy commented Aug 17, 2017

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.

@mhegazy mhegazy assigned ghost Aug 17, 2017
@mhegazy mhegazy added the Bug A bug in TypeScript label Aug 17, 2017
@cervengoc
Copy link
Author

@aluanhaddad Unfortunately your idea doesn't work in my case. I need to augment a module's root exported namespace and one of the interfaces inside. To be specific, the library which I'm trying to augment is videojs.

I tried to create a videojs-custom.d.ts file and put this into that, but these added definitions are not recognized. I've tried to add the export keyword everywhere but that made no difference.

// videojs-custom.d.ts

export { }

declare module "videojs" {

  namespace videojs {
    function getComponent(name: string): any;
    function registerComponent(name: string, definition: any);
    function extend(target: any, source: any);
    function plugin(name: string, constructor: Function);

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

}

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Aug 20, 2017

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.

@cervengoc
Copy link
Author

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.

@NaridaL
Copy link
Contributor

NaridaL commented Oct 8, 2017

EDIT: yeah this is mostly covered by #18877 and co
EDIT 2: I couldn't get @mhegazy 's variant of wrapping it in declare module "mod" { to work in any case.

This whole thing is weird...

// ts3dutils.d.ts
export interface V3 {
    plus(x: V3): V3
}
export as namespace ts3dutils

// ts3dutilsaugment.d.ts
export {}
declare module "./ts3dutils" {
    interface V3 {
        __magic_type: V3
    }
}

// index.ts
/// <reference path="ts3dutils.d.ts" />
/// <reference path="ts3dutilsaugment.d.ts" />

type A = ts3dutils.V3['plus'] // OK!
type X = ts3dutils.V3['__magic_type'] // OK!

BUT if the interface exported as namespace is actual reexported from somewhere else:

// V3.d.ts
export interface V3 {
    plus(x: V3): V3
}
// ts3dutils.d.ts
export * from './V3'
export as namespace ts3dutils

// ts3dutilsaugment.d.ts
export {}
declare module "./ts3dutils" {
    interface V3 {
        __magic_type: V3
    }
}

// index.ts
/// <reference path="ts3dutils.d.ts" />
/// <reference path="ts3dutilsaugment.d.ts" />

type A = ts3dutils.V3['plus'] // OK!

// ERROR: [ts] Property '__magic_type' does not exist on type 'V3'.
type X = ts3dutils.V3['__magic_type']

Related to #9681 (?) because the underlying issue seems to be that export as namespace namespaces don't behave like normal ones i.e. they can't be merged as expected.

What I was originally trying to do was hack in conditional mapped types. I.e. augment other types so I can do type MappedType<T> = (T & { __magic_type: {} })['__magic_type']

@aluanhaddad
Copy link
Contributor

Augmentations have to target the physical module that the export originates from.
There is no way augment by way of a re-export.
This is awkward since it requires taking a hard dependency on something that could be an implementation detail, the location of a re-exported module.

@mhegazy mhegazy added this to the TypeScript 2.7 milestone Oct 16, 2017
@screendriver
Copy link

@mhegazy your solution did work for me:

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

Although I don't really understand what "mod" really means here and why we have to write a module within a module 🤔

@mhegazy
Copy link
Contributor

mhegazy commented Oct 23, 2017

the name "mod" is not important here, you can replace it with any other name, what you care about is the "lodash" module augmentation (which has to be nested inside another module to be considered an augmentation).

@aluanhaddad
Copy link
Contributor

Personally, I find

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

easier to read.

@ghost ghost added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Bug A bug in TypeScript labels Nov 14, 2017
@ghost
Copy link

ghost commented Nov 14, 2017

Talked about this with @mhegazy and we couldn't think of a good solution to this besides @aluanhaddad's.

@mhegazy mhegazy unassigned ghost Nov 14, 2017
@mhegazy mhegazy removed this from the TypeScript 2.7 milestone Nov 14, 2017
@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@EyalIsr
Copy link

EyalIsr commented Jan 15, 2018

@cervengoc Can you provide a sample of consuming the custom video.js component?
I'm new to the concept of Augmentation and tried to extend a video.js component myself, but when consuming the custom module, I'm getting a compiler error:

Property 'getComponent' does not exist on type ...

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

8 participants