You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Update 20150908: applied feedback from comments. Thanks @jbrantly! Also renamed "Legacy mode" to "Mixed mode" and clarified what it's about.
Update 20150909: more feedback from @jbrantly.
Now that #2338 is implemented, we have nice resolution of typings for 'native TS modules'.
However, exporting typings for non-TS modules can be problematic in case of deeper dependency trees.
Issue #2839 (by me) contains a proposal to solve this, but does not talk about deeper-than-one dependencies in case of conflicting versions. Issue #4665 (by @mhegazy) also addresses non-TS resolution, but does not explicitly mention the lookup logic.
Example dependency tree
myprogram (native TS module)
mylib (native TS module, with 'proper external' typing)
foolib@1.0 (plain JS module)
utils@3.0 (plain JS module)
barlib@1.0 (plain JS module)
utils@4.0 (plain JS module)
myotherlib (native TS module, with 'proper external' typing)
foolib@2.0 (plain JS module)
utils@4.0 (plain JS module)
Assumptions about this tree:
Mainly talking about NodeJS (CommonJS) environment here, not trying to solve the bower problem
None of the JS modules pollute the global namespace, i.e. they are 'normal' CommonJS modules
Problem description
In this case, because mylib is the 'first' package that knows about TS, it somehow needs to provide the typings for all non-TS stuff it exposes: foolib@1.0, barlib@1.0 but also both utils@3.0andutils@4.0.
In an ideal world, using 'proper external modules' (see #4665 and #2338) would solve this if they were all TS modules (i.e. they provide their own typings in their npm package).
In this case though, the import statements need to resolve to e.g. DefinitelyTyped typings, typically located in e.g. mylib/typings/.
So, mylib/typings/ needs to have a utils.d.ts for version 3.0 and 4.0, and the compiler needs to know how to find them, and which version to use.
Especially note the difference between the concept of the current JS module versus current TS module in the following proposal.
i.e. also traversing node_modules of parent packages
If Y provides its own typing (either index.d.ts or by following typings property in package.json in the package directory)
Parse that typing as a 'proper external module' typing
Set CurrentTSModule and CurrentJSModule variables to Y, i.e. any external modules used by Y should be resolved by looking in <Y>/node_modules and <Y>/typings/, no longer in <X>/typings/
Done
Otherwise, search for typings using either:
ALGORITHM A: searches for Y in X's typings folder (let's call it typings/):
ALGORITHM B: match against .d.ts files with <library ... /> tags
For all .d.ts files passed on commandline/tsconfig.json that have a <library ... /> tag, match Y's package.json name and version against the name and semver specification in the <library> tag
When a match is found:
Keep CurrentTSModule as X, but set CurrentJSModule to Y
This ensures that the correct version of any sub-package is found
If 'proper mode': parse as 'proper external module', done
If 'mixed mode': determine whether it's a 'proper external' or 'ambient external' typing:
If 'proper external': use it as-is, done
If 'ambient external':
Don't expose any ambient declarations (modules, namespaces, variables, types), nor recursive <reference>'d declarations, to the global module namespace, and
Look for the declare module "Y/Z" { ... } and use its contents as the result (i.e. 'convert to proper external')
Notes:
<majorY> etc. are based on the version as found in Y's package.json
In contrast to the lookup in node_modules, lookups in typings do not traverse typings/ dirs of parent packages. Only those of (TS-)package X are searched.
This algorithm correctly determines that foolib uses utils@3.0, but barlib uses utils@4.0 (i.e. CurrentTSModule stays mylib, but CurrentJSModule switches from foolib to barlib)
Mixed mode
Mixed mode (previously called "legacy mode"), is intended to allow existing DefinitelyTyped typings
(which use 'ambient external' scheme) to basically be used as external modules (CommonJS) without making a lot of 'accidental' globals available (e.g. the Promise type in bluebird typings), while still also allowing them to be used in AMD and plain script modes ('isomorphic typings').
These isomorphic typings typically declare lots of things as globals (perfect for plain script mode),
but these are usually not made globally available when loaded as CommonJS.
So, the idea is to wrap the whole typing into its own private space, then only make the actually requested external module part of it available.
Note that if an isomorphic typing includes a 'proper external' typing, and the proper external typing <reference>'s another typing, that typing is still allowed to declare globals (they will not be 'isolated').
Having two packages declare the same global (e.g. when a proper external typing references a .d.ts, which explicitly marks a variable, class, etc as being globally available) currently leads to a compiler error ("Duplicate identifier").
One idea I had was to 'merge' the types of such globals instead (e.g. if two packages both declare a Promise, but they are in fact of different types, the resulting global will be typed as e.g. declare var Promise: PromiseType1|PromiseType2;
This way, the compiler can error when the global is actually used (as opposed to when declared) in an incompatible way (see #4673 (comment))..
Discussion items
I'm not sure whether I prefer Algorithm A or B, yet.
Algorithm A has the advantage of being 'filesystem based', just like node's environment.
But B supports more complex semver matches and prevents bikeshedding over e.g. the name and structure of typings/.
When using Algorithm A (files), @jbrantly suggested to have an extra check for ES6 typings, i.e. preferring e.g. typings/<Y>.es6.d.ts over typings/<Y>.d.ts when available. Or possibly typings/<Y>.<target>.d.ts where <target> is the --target passed to tsc. See comments below for discussions on pros/cons.
Update 20150908: applied feedback from comments. Thanks @jbrantly! Also renamed "Legacy mode" to "Mixed mode" and clarified what it's about.
Update 20150909: more feedback from @jbrantly.
Now that #2338 is implemented, we have nice resolution of typings for 'native TS modules'.
However, exporting typings for non-TS modules can be problematic in case of deeper dependency trees.
Issue #2839 (by me) contains a proposal to solve this, but does not talk about deeper-than-one dependencies in case of conflicting versions. Issue #4665 (by @mhegazy) also addresses non-TS resolution, but does not explicitly mention the lookup logic.
Example dependency tree
Assumptions about this tree:
Problem description
In this case, because
mylibis the 'first' package that knows about TS, it somehow needs to provide the typings for all non-TS stuff it exposes:foolib@1.0,barlib@1.0but also bothutils@3.0andutils@4.0.In an ideal world, using 'proper external modules' (see #4665 and #2338) would solve this if they were all TS modules (i.e. they provide their own typings in their npm package).
In this case though, the
importstatements need to resolve to e.g. DefinitelyTyped typings, typically located in e.g.mylib/typings/.So,
mylib/typings/needs to have autils.d.tsfor version 3.0 and 4.0, and the compiler needs to know how to find them, and which version to use.Especially note the difference between the concept of the current JS module versus current TS module in the following proposal.
Proposed algorithm
Naming (taken from #4665):
declare module "Y" { ... }declarationsdeclare module "Y" { ... }, but directly export their classes, variables, etc.When compiling a package
X, and looking for typings of an external moduleY/Z, let:CurrentTSModule= XCurrentJSModule= XZ="index"if module is imported without a path (i.e.import "Y"instead ofimport "Y/Z")Now:
Y'spackage.json (starting atCurrentJSModule`)node_modulesof parent packagesYprovides its own typing (eitherindex.d.tsor by followingtypingsproperty inpackage.jsonin the package directory)CurrentTSModuleandCurrentJSModulevariables toY, i.e. any external modules used byYshould be resolved by looking in<Y>/node_modulesand<Y>/typings/, no longer in<X>/typings/YinX's typings folder (let's call ittypings/):typings/<Y>@<majorY.minorY.patchY>/<Z>.d.ts(proper mode)typings/<Y>@<majorY.minorY>/<Z>.d.ts(proper mode)typings/<Y>@<majorY>/<Z>.d.ts(proper mode)typings/<Y>/<Z>.d.ts(proper mode)typings/<Y>/<Y>.d.ts(mixed mode)typings/<Y>.d.ts(mixed mode)<library ... />tags<library ... />tag, matchY'spackage.jsonname and version against the name and semver specification in the<library>tagCurrentTSModuleasX, but setCurrentJSModuletoY<reference>'d declarations, to the global module namespace, anddeclare module "Y/Z" { ... }and use its contents as the result (i.e. 'convert to proper external')Notes:
<majorY>etc. are based on the version as found inY'spackage.jsonnode_modules, lookups intypingsdo not traversetypings/dirs of parent packages. Only those of (TS-)packageXare searched.foolibusesutils@3.0, butbarlibusesutils@4.0(i.e.CurrentTSModulestaysmylib, butCurrentJSModuleswitches fromfoolibtobarlib)Mixed mode
Mixed mode (previously called "legacy mode"), is intended to allow existing DefinitelyTyped typings
(which use 'ambient external' scheme) to basically be used as external modules (CommonJS) without making a lot of 'accidental' globals available (e.g. the
Promisetype inbluebirdtypings), while still also allowing them to be used in AMD and plain script modes ('isomorphic typings').These isomorphic typings typically declare lots of things as globals (perfect for plain script mode),
but these are usually not made globally available when loaded as CommonJS.
So, the idea is to wrap the whole typing into its own private space, then only make the actually requested external module part of it available.
Note that if an isomorphic typing includes a 'proper external' typing, and the proper external typing
<reference>'s another typing, that typing is still allowed to declare globals (they will not be 'isolated').Having two packages declare the same global (e.g. when a proper external typing references a .d.ts, which explicitly marks a variable, class, etc as being globally available) currently leads to a compiler error ("Duplicate identifier").
One idea I had was to 'merge' the types of such globals instead (e.g. if two packages both declare a
Promise, but they are in fact of different types, the resulting global will be typed as e.g.declare var Promise: PromiseType1|PromiseType2;This way, the compiler can error when the global is actually used (as opposed to when declared) in an incompatible way (see #4673 (comment))..
Discussion items
Algorithm A has the advantage of being 'filesystem based', just like node's environment.
But B supports more complex semver matches and prevents bikeshedding over e.g. the name and structure of
typings/.typings/<Y>.es6.d.tsovertypings/<Y>.d.tswhen available. Or possiblytypings/<Y>.<target>.d.tswhere<target>is the--targetpassed to tsc. See comments below for discussions on pros/cons.