-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Proposal: Localized typings for definition files #4668
Comments
@weswigham @poelstra You've both expressed interest in declarations that don't pollute the global namespace. Do you have any thoughts on this proposal? |
I like the idea of allowing references to be forced 'local', but for a different use-case: when I have a test-something.ts, that file is intended to be run with mocha. However, if I currently put a However, I would prefer to not have to write these Also, having a It is indeed a solution for solving the 'accidental globals', but I think #4673 (the 'legacy mode' part of it) solves it more elegantly by basically automatically deciding to apply your 'localized' setting when importing. |
@poelstra Thanks for your comments. Let me address each one.
That depends entirely on how the definition file is written, I suppose. If it only defined
Sure, that was just for example purposes here. I would expect ideally that TypeScript would use some magic lookup logic like in your proposal if there was an
That's not true. The definition file itself can do anything it want (create globals, create ambient modules, whatever). It's only definitions that it references that are restricted. This also relates back to #4337, which is that ideally we shouldn't be writing isomorphic definition files this way anyways. Instead we should be splitting them into multiple files so that program authors can choose how they plan on using it (module or global).
Sort of. What if the definition file that is being imported using the legacy mode uses
This already solves intentional globals. Definition files that you reference from your program still have the full power that they have today to create globals. It's only dependencies of those definition files (if the definition file so chooses) that would not have the power to create globals, which is a good thing. If your program needs access to a global that's created by a dependency in JS-land, then simply include the definition file for that dependency as well and the global will be made available. Eg:
|
No,
Hmm, but if a typing makes a 'real' global available (i.e. attaches it to
That's a lot of work for the typing authors, isn't it? (Although I agree it'd be nice for us users :))
Then all of these will recursively have the same trick applied. No leaking. (Unless one uses the opt-in 'real global escape', in which case it must leak, in my opinion.) And yes, I ran into the issue you mention in #4337. It regularly happens that I forget to
They will still create the global at runtime. The fact that you're hiding the typing for it doesn't mean it isn't there. So it will still conflict at runtime. Suppose there's two (incompatible) packages:
In your proposal, By allowing the 'real global' escape, the compiler can know that the presence of Note: both our proposals suffer from this problem, which I believe can only be 'solved' (well, it can generate a proper compile-time error message) when we additionally implement this explicit global escape. |
Ah, I see what you're saying. Interesting.
But since it's not available in TS-land as a global, you wouldn't be referencing it as a global.
It is, but....
This is an area that I'm passionate about since I help maintain the definitions for React. I really want TS to improve in this area.
I wasn't clear on this point. If that's the case then it's actually a whole lot like this proposal.
Again, I think the right approach here is for TS to not complain. It's not a guarantee that the globals will conflict (many of the Promise polyfills are more or less compatible) and it just seems like it would be really hard to somehow tell TS to not spew a bunch of errors in this situation even though you know the runtime won't conflict. |
Yes, more or less :) But note the following: #4665 (comment):
i.e., one of them has // Note: consider this exported and used as 'real' global, i.e. attached to `global`
export interface Promise<T> {
constructor(...) { ... }
then(...): Promise<T> { ... }
done(...): void { ... }
} and the other one doesn't have the All is fine (no compiler errors) as long as you're only using the constructor and |
That's exactly what I'm saying, and implemented in TS-land by "hiding" all of the globals except for the one you actually directly pull into your program. |
Hi All, It seems this is an existing issue, related to my issue involving local module augmentation. I have been working away on typescript definition files for the new modular version 4 of Mike Bostock's famous D3js. I staged the work in progress in a repo for now, as I am experimenting with the ability to type the D3 has a module d3-selection which allows the creation of When 'shape testing' the internal consistency of the definition files, I noticed that the module augmentation bleeds into a typescript test module, which solely imports d3-selection ( A test module which imports both D3 modules, works as expected in the test file have in my local repo. I will push that one shortly. The issue linked at the beginning of this comment has more detail on the failure mode including a relevant code snippet and links to the test in the repo which shows module augmentation bleed. While under development, it uses straight Is there a way to localize the module augmentation at this point? Fair chance, there is another issue/answer tracked on typescript which evaded my attention. Thanks for all the hard work on typescript and pointing me in the right direction!!! |
When you load a file that contains a module augmentation, the module will be augmented all the time. there is no way to cleave the augmentation from the original declaration. This also matches the runtime behavior, at runtime a module with such side effects, usually will add some new methods on a prototype object from the other module, so any uses of the main module will see it. |
@mhegazy Thanks, you are of course correct. I chased an edge case wanting to have the cake and eat it by testing the definition shape with and without augmentation in the same repo, without explicitly narrowing the compilation scope. It's all good. |
As noted in #4673 (comment) The conclusion here was to use npm for distributing declaration files, and use the npm dependency model to resolve conflicts. Declaration files need to be modules, and not global. for UMD modules they should be using the new Please see more documentation about authoring declaration files and distributing them on npm at https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/declaration%20files/Introduction.md |
This is another proposal for the issue explained in #2839 and #4665.
Problem
Ambient external module declarations are global in nature. For example, if the following is contained in a definition file included in the program...
...then
myutils
can be referenced using an import statement anywhere in the entire program.In contrast, npm packages can generally only import modules that they have explicitly declared as a dependency or that an ancestor in the dependency graph has declared as a dependency.
This difference can cause issues when multiple copies of (possibly different versions of) definition files are pulled in for the same package. This proposal will use the following example (from #2839) as a reference.
Proposal
To solve this, I propose an opt-in way for a definition file to declare that all imports/references contained in the definition file are localized only to that definition file. This would essentially allow definition files to define packages without accidentally leaking types to the global namespace, very similar to how an npm package does not make its dependencies directly available to the dependent package.
Syntactic changes
There would need to be a way for a definition file to say that it is opting-in to this feature. For example:
I'm not personally tied to what this looks like. If this proposal takes hold, let the bike-shedding begin.
Semantic changes
Any ambient declarations (modules, namespaces, variables, etc) that are referenced by the opting-in definition file are scoped to that file only and not made available to the program at large. This includes anything pulled in either through a
/// <reference />
tag or animport
statement. Ambient declarations actually made in the definition file are, of course, made available to the requesting scope (whatever that may be).Any ambient declarations (modules, namespaces, variables, etc) from a "higher level" are allowed, but will be overridden by any ambient declarations referenced by the definition file. No interface/module/namespace merging will occur.
Other
It could be interesting to also allow this for non-definition files, to allow similar functionality for "Proper external modules" as defined in Resolving dependency conflicts when importing node package typings #4665Proper external modules are definition files, and I think this proposal should apply to them as well.__MyLib
being available globally in Guidance on writing declaration file for multi-targeted libraries #4337The text was updated successfully, but these errors were encountered: