Description
Good progress is made on supporting the case of packages that carry their own typings in #2338.
That proposal doesn't currently deal with modules that do not carry their own typings, a.k.a. all modules that need typings from DefinitelyTyped.
This issue is to investigate possibilities of, and propose a (simple) solution for preventing problems when using (multiple versions of) external modules together.
TL;DR
- No longer use
declare module "..." { }
in typings for external modules - Add an extra step to the resolution logic of External module resolution logic #2338 to look up this type of external typings
Example scenario
- myprogram
- mylib
- myotherlib
Suppose that both mylib
and myotherlib
export a function, that returns a value of a type defined in myutils
.
Existing scenario
Likely, both mylib
and myotherlib
will each have a /// <reference path="./typings/tsd.d.ts" />
line, which again has a /// <reference path="./myutils/myutils.d.ts" />
line.
Problem
Both libs carry their own version of myutils.d.ts
, which works fine as long as they are not used together.
However, when they are used together (as in this example), all the /// <reference ...>
lines will (need to) be 'merged', which essentially means the declare module "myutils" { ... }
from both mylib
and myotherlib
will end up in the same 'global module name namespace'.
This gives a compiler error, and even if the compiler would somehow allow it, would make it impossible to refer to either version of myutils
.
Additionally, tools like dts-generator
e.g. include the /// <reference>
line inside of the declare
blocks, which get ignored by the compiler. This leads to unknown-references when trying to use such a typing, and will typically be solved by adding e.g. a myutils/myutils.d.ts
to myprogram/typings/tsd.d.ts
, which will be wrong for one of the libs.
Solution
External modules have the nice property that there is no 'global namespace' (except the filesystem, maybe), and basically every reference is 'local'.
It makes sense to use the same referencing scheme that's used for resolving 'local' external modules (within a package) for 'normal' external modules (different npm packages).
Basically, the idea consists of:
- No longer using
declare module "..." { }
in typings for external modules (to prevent the global namespace clash) - Adding an extra step to the resolution logic of External module resolution logic #2338 to look up this type of external typings (e.g. in
typings/
folder)
Advantages
- Multiple versions of a package can co-exist in the same program/library
/// <reference>
lines are no longer needed in most cases (except probably for e.g. importing Mocha'sit()
and other 'true' globals)- One format to rule them all: external module typings are already generated without
declare module "..." { }
bytsc
, so all 'sorts' of external module typings will look the same - Gradual upgrade path: the
tsd.d.ts
-way will still work for packages that do still usedeclare module "..." { }
(although it will still have the name-clash problem etc.)
Disadvantages
- Typings on e.g. DefinitelyTyped will (gradually) need to be replaced with their non-wrapped version
- Extra lookup step in the compiler
- Location of these external typings (a.k.a. include path) needs to be configured somewhere and/or sensible default value chosen
Example 'new-style' typings
Most hand-crafted typings will typically look like:
/// myutils.d.ts
export interface BarType {
bar: number;
}
export declare function something(): BarType;
But just to make things more interesting, suppose the typings of myutils
look like:
/// dist/bar.d.ts
export interface BarType {
bar: number;
}
/// dist/foo.d.ts
import bar = require("./bar");
export declare function something(): bar.BarType;
Nothing special here, this is what tsc
generates today.
(Note: dist/ may be because someone used CoffeeScript, not TypeScript, right? :))
Now, the typings dir on e.g. DefinitelyTyped could look like:
DefinitelyTyped/myutils/latest/dist/bar.d.ts
DefinitelyTyped/myutils/latest/dist/foo.d.ts
DefinitelyTyped/myutils/1.0/dist/bar.d.ts
DefinitelyTyped/myutils/1.0/dist/foo.d.ts
(Or maybe 2.0
instead of latest
, ideas welcome.)
And both mylib
and myotherlib
would have:
typings/myutils/dist/bar.d.ts
typings/myutils/dist/foo.d.ts
Looking up .d.ts
Given e.g. import { something } from "myutils"
the compiler could use myutils/package.json
's main
property to get to ./dist/foo.js
, which in turn would resolve to dist/foo.d.ts
.
Nice and simple: no need to have support from the authors of myutils
, no /// <reference>
lines.
And it even supports the import bar = require("myutils/dist/bar")
case.
Open questions
- Use one typings dir for both 'wrapped' and 'unwrapped' typings?
- First do a lookup for external modules already
declare
'ed, e.g. through tsd.d.ts? If so, would probably make first bullet usable, nice for gradual upgrade path of DT typings. - Most DT typings will not want to mimic exact filesystem structure of package, using e.g.
myutils/latest/index.d.ts
will probably work out-of-the-box with external module resolution logic, butmyutils/latest/myutils.d.ts
or evenmyutils/myutils.d.ts
may be easier for filename in editor tab?
Good idea? Bad idea?