Built-in support for UMD module definitions
RyanCavanaugh opened this issue ยท 64 comments
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:
export as namespace id;where id is any Identifier.
This is only legal as a top-level declaration a .d.ts file containing other top-level export 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 of tsconfig.json's files 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
export function doThing(): string;
export function doTheOtherThing(): void;
export as namespace myLib;globalConsumer.ts
/// <reference path="my-lib.d.ts" />
myLib.doThing();importConsumer.ts
import * as m from './myLib';
m.doTheOtherThing();Problem
Many definition flies are written like this:
declare var Promise: {
// some stuff here
}
declare module 'bluebird' {
export = Promise;
}Symptoms
This is bad for three reasons:
- We can't find this file from normal module resolution, so it has to get into the compilation context through a 'reference' mechanism
- This pattern can't handle the case where you have two versions of
'bluebird'because there is no path with which to disambiguate - People who are using the module version still get global scope pollution and might accidently use the global names when they meant to use an import name
Root 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.
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:
| Syntax | Semantics |
|---|---|
namespace Promise from 'bluebird' |
Gives Promise the meaning of the default export from bluebird. |
import global Promise from 'bluebird' |
Gives Promise the meaning of the default export from bluebird. |
namespace Promise = require("bluebird") |
Gives Promise the general shape of the export= if present, or the whole module otherwise. |
declare var Promise: typeof require("bluebird") |
Gives Promise the general shape of the export= if present, or the whole module otherwise. |
โญ โจ ๐ฒ ๐ โจ โญ
My first intuition was import since that's the only keyword we have which acquires all meanings of its target, but obviously it's very confusing to have exactly one form of import ... 'modulename' which turns its containing file into a module. I'd like to avoid it if possible.
global is a mixed bag but I'm leaning toward it. It reinforces that this identifier goes into the global scope despite any connotations you might have about seeing a module name in the declaration.
It's also going to be an ambient-only thing, which makes me think we need declare as part of the construct so people know it doesn't have any runtime meaning.
I would like to avoid require since we are promoting ES6 import syntax in all other forms.
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) (export = problem)?
Or is it solving something else?
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 myLib-global.d.ts never enters the compilation context. By having the .d.ts files be "proper" modules (top-level exports, not declare module "mylib" {) it becomes possible to resolve the conflict via file paths and everything Just Works.
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 bluebird.d.ts, which still have just a single top-level export, like the following?
// 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 declare module "bluebird" {...} in there?
Right, then include a separate bluebird-global.d.ts which contains simply (:bike: :house:)
declare global Promise from 'bluebird';which you would /// <reference .... if you were using in a script (non-loader) context
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 bluebird.d.ts above would support module augmentation now that #6742 has been merged? I've been trying with typescript@next but always run into the 'can't add top-level names' error.
If we get this declare global and also declaration merging for export= both fixed, then TypeScript will be able to model pluggable CommonJS/AMD libraries very well indeed, without the whole ambient globals problem.
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:
@@require('bluebird', Promise);
declare var Promise;
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 declare module "somename" {...}, and a declaration module is a .d.ts file that contains top level export/import declarations (e.g. export declare var foo: number;).
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 import {foo} from 'bar', then it can locate the .d.ts for bar, which quite possibly won't be shipped with bar or reside in myapp/node_modules/bar/. Let's assume we solve this, and declaration files for modules start to be authored as declaration modules and TypeScript locates them correctly. Awesome! That's a few problems solved.
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. declare global var $ from... . If the declaration of $ should look the same as the declaration module it is contained within, then a special form such as declare global var $ from this or declare global var $ from ".". I think it's also important to allow the current ambient declarations with the global modifier, as when used as a script it may introduce additional artifacts the module doesn't (e.g. could still write declare global function jquery ...).
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. /// <reference path='./where/did/this/get/downloaded/to/jquery.d.ts'/>), or support a new form such as <reference library='jquery'/>, which would do the file resolution as for module resolution when looking up an import of jquery, but would adds it globals to the compilation.
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:
- Was this file
imported? - Was this file
referenced?
... now we have to figure out questions like:
- If a UMD file is in tsconfig.json's files list, do we include its global?
- If a UMD file is passed on the commandline, do we include its global?
- If a user opens two loose files in VS, do we include the globals from UMD files?
- Someone starts with a
referencedirective and animportto the same UMD file in file A, and uses the global export in file B. They realize that the file is in the compilation already, so they remove thereferencedirective, and now file B has errors. How do we explain this coherently?
This is a lot of complexity vs this guidance:
- If you want to use the module version, use
import. Animportof a script file fails. - If you want to use the global version, use
reference library =.... Areferenceto a module file fails.
Not sure I see this as overly complex. To your questions:
If a UMD file is in tsconfig.json's files list, do we include its global?
Yes. If the .d.ts file was included by a means besides module resolution, then the globals are declared.
If a UMD file is passed on the commandline, do we include its global?
Yes. If the .d.ts file was included by a means besides module resolution, then the globals are declared.
If a user opens two loose files in VS, do we include the globals from UMD files?
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?
Someone starts with a reference directive and an import to the same UMD file in file A, and uses the global export in file B. They realize that the file is in the compilation already, so they remove the reference directive, and now file B has errors. How do we explain this coherently?
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?
This is a lot of complexity vs this guidance...
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 global in the name to get the global typings"
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 export as namespace $; as a better syntax
Facts:
- May not appear in ambient external modules
- May not appear in implementation files
- May not have modifiers
- May not appear in non-module files
Questions:
- May cause 'duplicate identifier' errors?
๐ for the syntax
May cause 'duplicate identifier' errors?
It possibly would? say I use jquery@1.6 and jquery@1.8 together, then:
// in some file
import * as j from 'jquery';
// and somewhere else
import * as oldJ from 'oldJQuery';So the 1.6 jquery.d.ts and 1.8 jquery.d.ts will both loaded and their export as namespace jQuery will cause "duplicate identifier" error.
@unional It wouldn't be an error. The global from JQuery only enters the global scope if the file is loaded via /// <reference ... directive.
The means it will cause "duplicate identifier" for typings because it is using /// <reference ... in typings/main.d.ts to organize and load typings.
cc @blakeembrey
The means it will cause "duplicate identifier" for tyings because it is using /// <reference ... in typings/main.d.ts to organize and load typings
Seems like it. Options:
- typings should no longer create these root files for easy referencing, and people should just use
tsconfig.json+exclude - Instead of a simple
<reference pathimporting stuff globally perhaps there should be a special<reference libthat is the only way to import definitions globally
Or maybe something else ๐น
Instead of a simple
<reference pathimporting stuff globally perhaps there should be a special<reference libthat is the only way to import definitions globally
That is exactly what #7156 does ๐
That is exactly what #7156 does
May be not? In that issue:
Additionally, when a UMD module definition is found, its global export declaration (if present)
is added to the global scope.
Meaning it will lead to duplicate identifier if using /// <reference lib=... syntax in typings (instead of /// <reference path=...)
Part of the behavior of <reference types= (the syntax we settled on) is that if you have two files found by the same "name", it's an error if those files aren't identical, and if they are identical, only one is loaded. And another part of the behavior is that you can place a library definition in your local types folder and override that definition for all consumers of the file.
This means that a) we'll tell you precisely that you have a global conflict and b) you can fix that conflict without mucking with files that aren't nominally under your control (e.g. the contents of node_modules)
To be clear: the design of that feature was specifically targeted at the "two copies of a .d.ts file with conflicting definitions of a global" scenario
To be clear: the design of that feature was specifically targeted at the "two copies of a .d.ts file with conflicting definitions of a global" scenario
Let me move the conversation over there as it is more relevant. ๐น
@basarat Not everyone does or can use exclude, it's pretty impractical to do.
Aside from that, I'm not sure we can use the <reference library> feature either, since it's configurable by the user. If we actually can't use <reference path> anymore (which was not my interpretation, I understood it had to be a reference in the current file not transitive) the user-facing situation sounds much less fun.
/// <reference path is definitely not going away. We're not crazy ๐
@RyanCavanaugh Sorry, I wasn't saying it was going away. I'm saying that if using it causes the UMD issue that @unional is worried about and <reference library> is configurable, we won't have any other method to list libraries automatically. I think I just need to confirm, does a UMD module become global if used with <reference path> in the way it does today (E.g. to create one typings/main.d.ts file)?
Edit: Phrasing.
Tsconfig also supports a types property; would that work?
does a UMD module become global if used with
Yes, that's correct. This also happens for files in the files property in tsconfig.json. Basically if the file enters your compilation state for any reason other than because of an import declaration, its UMD declarations will enter the global scope.
A workaround for the current implementation is to educate typings author to separate the export as namespace X into a separate file. Reference that file only if user choose to use that in global scope.
Yes, that's correct.
So, that sounds like a backward-compatible breaking change. How do these properties now work together? Before, you would <reference path> and then do an import from that declaration in the reference. Is this global or a module? Is the idea that everyone must now transition to reference library directives?
Tsconfig also supports a types property; would that work?
Anything works, my issue is that it's configurable down the dependency tree. If I use one setting for compilation and someone else uses another setting, how do these work together? Sounds like it just blows up for the user. Tooling also can't depend on it 100%, since the user can just change it anyway.
Edit: I think the first paragraph may not be 100% relevant, depending. Does this syntax work with ambient module declarations? It sounds like, no, it only works with module declarations.
It sounds like, no, it only works with module declarations.
Correct. The UMD declaration syntax is only valid in a module file (not in an ambient external module declaration or in the global scope).
@RyanCavanaugh If that's correct, does that mean the new library feature consumes module files? In that case, it comes down to my comment in the other thread - does the types/library/etc (not sure what the final implementation looks like anymore) get configured per-dependency properly, or is the end-user the one in total control? Having it configured per-dependency is perfect and any system could use this (I can have Typings be TypeScript 2.0+ and use external modules properly) while having it configured by only the end-user sounds error prone - what if they change a setting that a sub-dependency was using?
get configured per-dependency properly, or is the end-user the one in total control?
The lookup logic will resolve the dependency like node would. so you would get your nested dependency.
In case of duplication, i.e. PackageA depends on PackageA\node_modules\PackageC and PackageB depends on PackageB\node_modules\PackageC, and PackageC contents in both locations is not identical, it is an error.
The user can override this error by providing their own copy of PackageC, and that will be used.
So short answer, the end-user does have total control if they so chose.
@mhegazy So basically, if I configure my local types lookup to use typings/modules and publish that, it won't work for users unless they change their own config? That's what I would like to avoid.
In the duplicate case, I'm not convinced. This is an extremely common dependency structure for NPM packages. I still can't see how overriding would even help me here. If I have two package dependencies that are different they are different for a good reason - they aren't compatible. How would I write an interface for two different incompatible packages (and why would I?). Creating a custom PackageC sounds like it'd break the type definitions in either packageA or packageB, except in the most trivial of use-cases.
again we are talking about global. things that are meant to exist in the global namespace, how would these work at run time if you have multiple of them?
for modules, there are no issues. cause they are modules.
For definition files that were written as global because they were really UMD, now we have a way to describe them and keep them as modules.
That is why i am saying that a conflict is a bad thing and the user needs to know about it.
@mhegazy I'm ok with the global use-case, I'm trying to understand the library/types interaction now - probably better to move to the other thread for that, but this already dove into the discussion.
For modules, I don't think you answered my question still. Let's say I have a package, I add type definitions locally (that comply with the library/types feature), I publish said package with my local definitions - can someone use my package without configuration now? Also, just assume everything I've asked is about modules (the global story is not as relevant to me, people don't build complex dependency trees with global modules and even using JavaScript without a package manager is way down).
Nothing changes in modules. the way modules were working before should continue to work. the only change is for global. and ///<reference types=<> /> should not be used on modules. I would say it should be an error if you use ///<reference types=<> /> to import a module.
Oh, that's unfortunate. I was thinking about how it could be used to create a better dependency system.
Doesn't this seem like a really weird combination of features then? I can't see how it'll actually solve anything, unless there's yet another feature not being discussed. Basically, <reference types> uses a lookup order that includes node_modules and expects all those modules to be an external module definition format with a UMD declaration? Can you write about how these are intended to work together? From my understanding, this system was going to be able to replace Typings.
Basically,
<reference types>uses a lookup order that includes node_modules and expects all those modules to be an external module definition format with a UMD declaration?
The files found by <reference types= do not need to be modules. They can be regular global declaration code.
Let me walk through some examples and try to lay out how this works.
Global-only Code
myFile.ts
/// <reference types="jquery" />
$(whatever);types/jquery/index.d.ts or node_modules/@types/jquery/index.d.ts
declare var $: whatever;Module-only Code
myFile.ts
import { something } from 'foo';node_modules/foo/index.d.ts or node_modules/@types/foo/index.d.ts
export something: any;UMD used as Global
myFile.ts
/// <reference types="umdlib" />
umdlib.doSomething();node_modules/umdlib/index.d.ts or node_modules/@types/umdlib/index.d.ts
export as namespace umdlib;
export function doSomething();UMD used as Module
myFile.ts
import * as umdlib from 'umdlib';
umdlib.doSomething();node_modules/umdlib/index.d.ts or node_modules/@types/umdlib/index.d.ts
export as namespace umdlib;
export function doSomething();Global Conflict
Error state: multiple global definitions for quack found
myFile.ts
/// <reference type="foo" />
/// <reference type="bar" />
// ...node_modules/@types/foo/index.d.ts
/// <reference types="quack" />
// ...node_modules/@types/foo/node_modules/@types/quack/index.d.ts
declare function v1func(): void;node_modules/@types/bar/index.d.ts
/// <reference types="quack" />
// ...node_modules/@types/bar/node_modules/@types/quack/index.d.ts
declare function v1func(): void;
declare function v2func(): void;Global Conflict Resolution
Same as above, but copy one copy of quack/index.d.ts to types/quack/index.d.ts. This is not an error state.
Multiple Versions of Dependent Module
This is not an error state
myFile.ts
import * as foo from 'foo';
import * as bar from 'bar';node_modules/@types/foo/index.d.ts
import {v1func} from 'quack';
// ...node_modules/@types/foo/node_modules/@types/quack/index.d.ts
export function v1func(): void;node_modules/@types/bar/index.d.ts
import {v2func} from 'quack';node_modules/@types/bar/node_modules/@types/quack/index.d.ts
export function v1func(): void;
export function v2func(): void;The UMD declaration syntax is only valid in a module file (not in an ambient external module declaration or in the global scope
This is a bit confusing for me, which contradicts with the example you just show. Likely it just terminology issue, but I want to point it out so everyone is clear. ๐ท
Do you mean "UMD declaration syntax is only valid in a module file and not script file? To my understanding:
- "module file" = ".ts" or ".d.ts" with top-level import/export.
- "ambient external module declaration" is a format, and it can be in a "module file" (top-level import/export) or a "script file" (wrapped inside
declare module "x" {). - "global scope" do you mean "the effect of script file"?
Those definitions are correct, except that putting an ambient external module declaration (declare module "foo" { ... }) in a module file can only augment an existing module of the same name.
The global scope is the scope of top-level declarations that aren't in a module. For example, Math is in the global scope.
Those definitions are correct, except that putting an ambient external module declaration (declare module "foo" { ... }) in a module file can only augment an existing module of the same name.
I mean in a script file, i.e. there is no top-level import/export.
When in a module file, it is simply top-level import/export (i.e. top-level module declaration).
The global scope is...
Got it.
Same as above, but copy one copy of quack/index.d.ts to types/quack/index.d.ts. This is not an error state.
Regarding this resolution mechanism, it is kind of weird that requires user to make a copy of some typings in order to make it work.
Since what they need is to use /// <reference types="..." to notify tsc that they intend to use that "lib" as script, all they really need to do is /// <reference types="<path to the right version>".
Of course, being inside 'node_modules' the relative path approach is not nice and would break CI (due to npm2/3+ differences), may be something like /// <reference types="jquery@1.6" is better?
If that is possible, then they don't really need a local types/ folder (as they write codes, not managing typings).
Just a thought. ๐ท
@RyanCavanaugh So, I think I'm back on the same page, again. This can be used to create type resolution as I mentioned. Now, the issue, which I haven't had a clear answer, is - does this break when used with sub-dependencies. If I publish a module that relies on typings/foo being resolved with this new types feature, is there anything that stops the consumer from breaking that use? (E.g. what happens when the user changes their types resolution locally, it sounds like it'll break anywhere down the dependency tree - can the dependency resolution used when authoring be respected when it becomes a sub-dependency?)
Two cases here.
If the types being loaded are from modules (e.g. you put "@types/someLib": "2.3.1" in yourpackage.json, thenimport * as whatever from 'someLib'), you'll still get versioned module resolution even if the user has placed something in theirtypes/folder, assumingsomeLib.d.tswas written correctly (e.g. *not* asdeclare module "someLib" {`).
If the types are from the global scope, then the module loading /// <reference types="someLib" /> will pick up the user's version before its own local version, which may or may not break. This shouldn't be a problem in theory since in reality there's only one global someLib at runtime, so the .d.ts file should be more-or-less reflective of whatever the runtime compatibility is (e.g. if you wanted someLib v2.3.1 but got someLib v4.3.6 from the local types folder, and v4.3.6 is a proper superset, it's unlikely the depending module will get broken).
@RyanCavanaugh The local use-case is fine by me, I still don't know if we're on the same page with the module use-case. This is not about using package.json, but using the configuration option alluded to (#7125 (comment) might be the source of hope/confusion here). Assuming typings/ is the used look-up path in a sub-dependency, will that continue working with sub-dependencies after installation. Assuming the tsconfig.json option exists, will changing it to remove node_modules/@types or typings break the sub-dependency.
I think we're going around in circles here. Can you post an example code layout of what scenario you're describing?
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 @types/signalr
the @types definitions are pushed from definitelyTyped. we have manually updated some of these packages. the sources are at https://github.com/DefinitelyTyped/DefinitelyTyped/blob/types-2.0/signalr/index.d.ts, feel free to send a PR for any changes and we will get these in.
I have a quick question regarding the following quote at the very end of the Solution summary by @RyanCavanaugh :
These declarations may engage in module merging, though this should probably be discouraged somehow.
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. d3-selection, d3-transition, d3-shape etc...
There is also a standard pre-built bundle of the 'core' modules, which is provided as a single module d3.
The modules are structured as UMD modules for bundled/unbundled use in vanilla script as well as module import use cases.
In writing the definitions, I came across the following issue related to their UMD character as. For the vanilla script scenario:
- if the standard pre-built bundle of modules is loaded from the single bundle file, it exposes a
d3global with the objects exposed by each of the modules which feed the standard bundle - if the scripts of the d3 modules are individually included (i.e. unbundled), each of them exports to/extends the same
d3global. Mike's intent being ease of code reuse for D3 users in the vanilla scenario.
Ad 1): Creating a bundle definition with UMD characteristics for the default bundle d3 is as simple as re-exporting the relevant 'core' modules and adding the export as namespace d3; for the global.
Ad 2): I am running into the issue that, adding the export as namespace d3; to the individual modules, e.g. d3-selection, d3-transition etc., creates a duplicate identifier compile error for the d3 identifier. (Typescript v2.0.0.) (Note: there are no identifier conflicts between the objects exported from the individual modules)
Despite the aforementioned quote, I suspect this is expected compiler behavior? Is there a preferred way to accomplish the module merging into the global d3?
@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 this typing of callback function contexts, which was not yet available in typescript 1.8.10.
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 d3 global question raised above here. Representative definition files can be found here for e. g.:
Note, that these definitions currently do not individually have the export namespace as d3;, because of the mentioned issue. I added them locally and get the compile error.
There is a definition file for the 'bundled' d3 module here, which uses re-exports and has the global export.
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 <reference types="x" /> not make it in? If so, how does one .d.ts going into DefinitelyTyped reference a different module's definition?
@bytenik I've tested 2.0. and it seems to be working fine (using <reference types="x" />). The libraries I've installed via npm install @types/library_name use the same syntax.
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
Nothing changes in modules. the way modules were working before should continue to work. the only change is for global. and
///<reference types=<> />should not be used on modules. I would say it should be an error if you use///<reference types=<> />to import a module.
@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 types in tsconfig.json instead of ///<reference types=<> /> and that didn't help either.
@speigg very curious how you're getting into this state -- you're loading some libraries globally, and some through a module loader?
you're loading some libraries globally, and some through a module loader?
@RyanCavanaugh Yes, exactly.