-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Built-in support for UMD module definitions #7125
Comments
Syntax bikeshedding is fun :), some more options: namespace Promise from 'bluebird`;
namespace Promise = require("bluebird");
declare var Promise: typeof require("bluebird"); |
Syntax bikeshedding is great, but it depends a lot on what the intended semantics are. It's important to note that the general syntax around imports has been frustratingly confusing, and it needs to be obvious what is happening in each instance. For example, I assumed the following from each of the above:
|
⭐ ✨ 🚲 🏠 ✨ ⭐ My first intuition was
It's also going to be an ambient-only thing, which makes me think we need I would like to avoid Putting all that together I would consider declare global Promise from 'bluebird'; to be the most indicative of our intent. |
Maybe there was some more detailed team discussion preceding this issue, but I can't quite grasp exactly what's being proposed here from the issue text. Is this related to #7015 (comment) ( It seems like the proposed solution still involves creating an ambient external module and an ambient global name, but in the opposite order. Why would import sites want to create an ambient global when they import bluebird? Can someone clarify? |
The root root cause is that we're trying to design a good type acquisition strategy. That sounds simple enough, but when you start diving into the scenarios that are going to come up in the future when .d.ts files start following semver breaks, things get hairy pretty quickly. The general idea here is to allow file structures like this: mylib.d.ts export function DoSomething() { } mylib-global.d.ts declare global myLib from './mylib'; The root problem here is that you might be using myLib v1 and myLib v2 in the same compilation context (because you depend on libraries A and B which, respectively, import the v1 and v2 definitions of myLib). Today, this is going to get you a one-way ticket to "Duplicate identifier" hell if there are separate definition files for v1 and v2. But if you only consume myLib (directly or indirectly) via module imports, there's actually no conflict, because On the other hand, if two libraries claim they're both consuming myLib from a global reference, there really is a problem and the user is going to have to resolve that conflict by deciding which global is actually loaded at runtime (you'll see another issue sometime soon on how we intend to solve that problem). |
Aha got it, thanks. This will be very useful. So with this fix in place, would the idea be to rewrite things like // File: bluebird.d.ts
declare var Promise: PromiseConstructor;
//...
interface Promise<T> {
then<U>...
//...
}
declare namespace Promise {
export interface CancellationError...
export interface Resolver...
//...
}
export = Promise; ... the main difference being there's no longer the ambient |
Right, then include a separate declare global Promise from 'bluebird'; which you would |
Brilliant! +1 :) This provides much closer correspondence between what happens at compile-time and run-time. Partly related, but do you know if the formulation I wrote for If we get this |
Just being devil's advocate for the 🚲 🏠, why is the team staying away from non semantic directives like the triple-slash comments? Or would this be a legitimate use case for design time decorators (#2900) with something like:
I guess what I want to challenge is that this (while 100% useful) is even more "erasable" than typical typing information. |
In the spirit of UMD, I think it would be beneficial to be able to write the types for a library in one file regardless of how it is referenced, rather than needing two .d.ts files to declare its types depending on if it is used as a global or a module - especially as it's quite likely only one line needed in the majority of cases to declare that when used as a script, some global identifier has the same shape as the module has. First, to be sure everyone is clear on terminology for this discussion (as I had to look it up 😄 ): An ambient module declaration is of the form Part of the reason nearly all type definitions for modules today are written as ambient module declarations, is that declaration modules need to live in a location where module resolution would be able locate them. If we want to encourage .d.ts files for modules to be written as declaration modules, then we need a resolution mechanism whereby if my code says Now two challenges: How could I also declare the types in this same file when referenced as a script (global), and how do I reference it in my app. For the first, I don't see any reason why the same syntax outlined above couldn't work, i.e. To reference this as a global (assuming for a module you don't need to reference it, module resolution finds it when your app imports it), you could either reference the file directly as we do today (i.e. Thus a canonical declaration may look something like: // .d.ts file for the 'breakfast' library
export interface Eggs { /* ... */ }
export function sausages(): string;
export var bacon: Eggs;
declare global var breakfast from this; To use it in a module I could just write the below (and the module .d.ts would resolve) import * as foo from 'breakfast'
foo.sausages();
// etc... Or to use it as a global script I could write something like: /// <reference library="breakfast"/>
console.log(breakfast.bacon);
// etc.. Thoughts? |
We currently only have one bit per file: Is this file part of your compilation, or not? If we make this a two-bit system:
... now we have to figure out questions like:
This is a lot of complexity vs this guidance:
|
Not sure I see this as overly complex. To your questions:
Yes. If the .d.ts file was included by a means besides module resolution, then the globals are declared.
Yes. If the .d.ts file was included by a means besides module resolution, then the globals are declared.
Not sure I understand. Are these files the .d.ts files in question? One of them? Are they referencing or importing said .d.ts files somehow? Can you give me more details on the scenario you had in mind as problematic?
How is this different to today where if I remove a reference to a .d.ts file I need, then the declaration disappears and now files using it have errors? With the proposed system the import would resolve the module typing, but they'd need a reference to the .global.d.ts to get the global identifier, so doesn't the same scenario/error remain if they remove the library reference?
There may be some work on the compiler side (there is for any of this), but I'm trying to simplify the experience for the end user and type definition author, and maintain the "one JS library = one .d.ts file" symmetry we generally have today. The guidance of "import a module and the typings just work, reference a typing to get the globals" remains the same in either, the guidance of "download one definition file that matches the library name and reference it to add global typings" seems simpler than "download these two definition files for each library and reference the one that has Maybe this is bike-shedding, as either seems workable and better than what we have now. I'm just looking for the optimal simplicity for TypeScript users in what has been a confusing space to date. |
I think your approach is workable; I've implemented a prototype and it's not as complex as I expected. https://github.com/RyanCavanaugh/TypeScript/tree/umd Maybe pull it down and try it out |
Slogged; we like |
Facts:
Questions:
|
👍 for the syntax |
Is this not included in 2.0 beta? |
it is. please see documentation in https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Modules.md#umd-modules |
Does this not support legacy typings without the new export syntax? i.e. the SignalR typings file in |
the |
I have a quick question regarding the following quote at the very end of the Solution summary by @RyanCavanaugh :
I have been drawing up new typescript definitions for Mike Bostock's popular, newly modularized version 4 of the D3 data visualization tools. D3 is now split up into several modules, e. g. There is also a standard pre-built bundle of the 'core' modules, which is provided as a single module In writing the definitions, I came across the following issue related to their UMD character as. For the vanilla script scenario:
Ad 1): Creating a bundle definition with UMD characteristics for the default bundle Ad 2): I am running into the issue that, adding the Despite the aforementioned quote, I suspect this is expected compiler behavior? Is there a preferred way to accomplish the module merging into the global |
@tomwanzek it is kinda hard to see what is happening without looking at the code. is there a chance you can share your declarations and the errors you are getting. it would also be great if you open a new issue for this. |
My gut tells me you need to do global augmentation for 2) |
Thanks for the quick response. @mhegazy I created a repo here to stage the D3 definitions while I am drafting them. I did not use my DefinitelyTyped fork to create a pull-request right away, because I am using I intend to move them into DefinitelyTyped as soon as possible for those that have completed 'shape tests' of the definitions. And then incrementally as testing completes. The repo itself carries an issue for the Note, that these definitions currently do not individually have the There is a definition file for the 'bundled' I can open a new issue for typescript and cross-reference anew, do you have a preferred name to track it? |
I'm confused still. Did |
@bytenik I've tested 2.0. and it seems to be working fine (using |
see some examples in https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/declaration%20files/Library%20Structures.md#consuming-dependencies and full documentation for authoring and publishing declarations in https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/declaration%20files/Introduction.md |
@mhegazy Am a bit annoyed by this restriction right now. Getting a TS2686 error for referencing a UMD identifier (which is loaded globally) from within a module. I tried using |
@speigg very curious how you're getting into this state -- you're loading some libraries globally, and some through a module loader? |
@RyanCavanaugh Yes, exactly. |
Edit 2/29: Update proposal based on design changes; moved 'Solution' to top since most people viewing this are familiar with the problems
Solution
Support a "native UMD" declaration form that allows for a global var to be of the type of a module.
A global module export declaration has the syntax:
where
id
is any Identifier.This is only legal as a top-level declaration a
.d.ts
file containing other top-levelexport
declarations (e.g.export function
,export var
,export =
, etc.). Multiple of these declarations may appear in the same file so long as they supply different identifiers.When the containing file is imported through
import
syntax, the declaration has no effect.When the containing file is added to the compilation via a
/// <reference
directive or by being a top-level file provided to the compiler (e.g. on the commandline, or as part oftsconfig.json
'sfiles
list), the supplied identifier[s] are added to the global scope of the program. The type of these identifiers is the type of the module object of the file in which they were declared.These declarations may engage in module merging, though this should probably be discouraged somehow.
Example
my-lib.d.ts
globalConsumer.ts
importConsumer.ts
Problem
Many definition flies are written like this:
Symptoms
This is bad for three reasons:
'bluebird'
because there is no path with which to disambiguateRoot Cause
The reason people do this (define globals, then
export =
from an ambient module declaration) is that the *reverse * (define a module, then declare a var with that module's type) is impossible.The text was updated successfully, but these errors were encountered: