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

Module augmentation difficulties with AMD/CommonJS/globals #7015

Closed
yortus opened this issue Feb 11, 2016 · 13 comments
Closed

Module augmentation difficulties with AMD/CommonJS/globals #7015

yortus opened this issue Feb 11, 2016 · 13 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@yortus
Copy link
Contributor

yortus commented Feb 11, 2016

I'm just curious about the rationale for the following aspect of the new module augmentation feature in v1.8:

Neither module augmentations nor global augmentations can add new items to the top level scope - they can only "patch" existing declarations.

I couldn't find any discussion about why this restriction exists. Is it technical? It is to direct people towards specific practices?

I ask because it seems to make it difficult to model existing JavaScript behaviour in some cases.

Example: A jQuery Plugin

With this restriction in place, what is the best way, for example, to write the type declaration for plugin, which adds itself as a top-level property of the object exported by jquery? Assume AMD module loading. I tried a few approaches:

Option 1

This is to me the most obvious and the simplest approach, but it is not allowed.

// file: plugin.d.ts
declare module 'jquery' {
    export var plugin: Plugin; // ERROR: cannot introduce new names in top level scope
}

export interface Plugin {/***/}
// Other exports...

Option 2

This approach tries to augment an existing declaration to satisfy the augmentation rules.

// file: plugin.d.ts
declare module 'jquery' {
    interface JQueryStatic { // ERROR: cannot introduce new names in top level scope
        plugin: Plugin;
    }
}

export interface Plugin {/***/}
// Other exports...

Option 2 still doesn't work because JQueryStatic is not exported from jquery. There are also other problems with this approach:

  • even if JQueryStatic was exported (which makes this option work), the augmenter needs to study the 'internal' details of jquery.d.ts to find the right name to augment. This seems like an unnecessary detail, not to mention risky because the name might be changed in a future jquery.d.ts rearrangement.
  • Just adding export in front of JQueryStatic in jquery.d.ts doesn't work, because, like very many typings on DefinitelyTyped, it uses export= syntax which precludes other exports, so to export JQueryStatic would involve more extensive changes in jquery.d.ts.

Option 3

This approach works, but doesn't use the new module augmentation feature at all. It relies on good old ambient declarations.

// file: plugin.d.ts
interface JQueryStatic {
    plugin: Plugin;
}
// IMPORTANT: must be no imports or exports in this file!
interface Plugin {/***/}

Option 3 also has problems:


I've used jquery as an example, but this also applies to global augmentations, and of course to other commonly augmented JavaScript libraries.

@weswigham
Copy link
Member

At least for modules, top level exports are immutable in ES6, while their members are not. IMO, it makes sense that the restriction matches this.

@yortus
Copy link
Contributor Author

yortus commented Feb 11, 2016

@weswigham should that restriction apply to systems using AMD (or commonjs) loading? Or to global augmentations?

@yortus yortus changed the title Question about module augmentation Module augmentation difficulties with AMD/CommonJS/globals Feb 12, 2016
@Seikho
Copy link

Seikho commented Feb 15, 2016

Augmenting modules seem unnecessarily strict and directed. Even trying to augment your own global object with 3rd party type definitions won't work due to built-in restrictions.

@mhegazy
Copy link
Contributor

mhegazy commented Feb 17, 2016

should that restriction apply to systems using AMD (or commonjs) loading?

ES6 modules are the standard now; though not implemented :), i do not see why we need to diverge in semantics based on the output system. In your example, the original .d.ts (JQuery) can add the JQueryStatic interface definition, and then plugin authors can just augment it.

Or to global augmentations?

I do not see why not. this is something we talked about as a desired feature, and we can definitely enable that.

@mhegazy mhegazy added the Question An issue which isn't directly actionable in code label Feb 17, 2016
@yortus
Copy link
Contributor Author

yortus commented Feb 17, 2016

@mhegazy so even if a project sets module='amd' or module='commonjs', TypeScript is enforcing a rule that only applies to module='es6'. This means that some commonly used semantics of AMD/commonJS cannot be represented in TypeScript without resorting to global ambient modules.

Sure ES6 modules are now the standard, but they still have virtually no runtime support. AMD and CommonJS will be around the many years to come. TypeScript claims to support AMD and CommonJS. Why impose a restriction that doesn't apply to these module systems? This either prevents TypeScript from being able to type existing JavaScript patterns, or prevents moving away from global ambient module declarations.

I'm still curious what's the upside of this decision, either for TypeScript, or the for many AMD/CommonJS projects out there?

@mhegazy
Copy link
Contributor

mhegazy commented Feb 17, 2016

his either prevents TypeScript from being able to type existing JavaScript patterns, or prevents moving away from global ambient module declarations.

I think Option2 should work. today you need to have knowledge of the jquery.d.ts or react.d.ts to be able to extend it anyways, so not see why this is a limitation. and any change the base .d.ts will break your extension, again just like today. export= is a problem, but i contend that they are written this way because there is no other way to solve it, and we should solve this issue, but this is different from module augmentation.

I'm still curious what's the upside of this decision, either for TypeScript, or the for many AMD/CommonJS projects out there?

limit complexity, unless it is absolutely necessary :)

@yortus
Copy link
Contributor Author

yortus commented Feb 17, 2016

limit complexity

OK, but if you remove this restriction for the global case (please do!), surely it's not adding much complexity to also remove it for AMD/CommonJS, where this restriction is also at odds with actual semantics?

I think Option2 should work

I think you may underestimate the pain factor of this. Once module augmentations landed, I looked forward to removing hacks from some of our module declarations, in particular global ambient module declarations. I quickly discovered that I couldn't do that because of the killer combination of (a) can't add top-level decls (b) the manner in which many third-party typings are already written.

@yortus
Copy link
Contributor Author

yortus commented Feb 17, 2016

I think Option2 should work

@mhegazy @weswigham can you help me to work out how to use Option 2 in practice for a lib like jQuery, with AMD loading? I've tried the following approaches without success:


First attempt

The following doesn't work because it relies on export default, which is incompatible at import sites (ie require('jquery') is typed as { default: JQueryStatic; } rather than just JQueryStatic. Or is there a workaround on the consuming side?

// File: jquery.d.ts
export interface JQueryStatic {
    someMember(x: any): any;
    //...
}

export interface SomeType { /***/ }

declare var $: JQueryStatic;
export default $;

Second attempt

I thought the following might work with typescript@next given the merged PR #6742:

// File: jquery.d.ts
declare namespace api {
    export function someMember(x: any): any;
    export interface SomeType { /***/ }
}
export = api;

But I still get the following error when trying to augment this:

// File: plugin.ts
declare module 'jquery' {
    export function myPlugin(); // ERROR: cannot add top-level names
}

I thought the idea behind #6742 was if the RHS of the export= is a namespace, then module augmentations will go into that namespace. Am I doing it wrong?

@mhegazy
Copy link
Contributor

mhegazy commented Feb 20, 2016

closing in favor of #7125

@mhegazy mhegazy closed this as completed Feb 20, 2016
@yortus
Copy link
Contributor Author

yortus commented Feb 20, 2016

@mhegazy how does #7125 help to solve this issue? This issue is about module augmentation, #7125 is about type acquisition.

@yortus
Copy link
Contributor Author

yortus commented Feb 20, 2016

Did you mean #7148?

@yortus
Copy link
Contributor Author

yortus commented Feb 20, 2016

Also, AFAIK there is now no open issue tracking global augmentations, which you said was a desired feature in #7015 (comment)

EDIT: I created an issue for it: #7161

@mhegazy
Copy link
Contributor

mhegazy commented Feb 22, 2016

thanks @yortus, #7148 is the one i meant.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants