Description
Problem
Currently TypeScript compiler accepts a single compiler option, target
, which specify both version of library to be included during compilation and version of JavaScript for emitting code together.
The behaviour presents limitations in two parts of the pipeline: consuming the default library and granularly controlling how JavaScript will be emitted (e.g. what features to be down-level etc.).
The proposal will mainly focus on the first issue regarding using the default library.
The current behavior of target
option doesn't allow users to have a fine-grain control on what library or feature of the library to be included, and user cannot independently control library's version from emit JavaScript version. As summarized in #4692 Proposal: Granular Targeting and #4168 Normalize our lib files by compiler setting
- Emit ES5 JavaScript while using ES6 library during design-time (and vice-versa).
- Include only sub-part of the ES6/ESnext library
- Modifying default libraries can conflict with users-defined ones.
- Using host-specific library.
Workaround
As @yortus points out in the #4692 Proposal: Granular Targeting , there are possible workarounds for above issues by maintaing customized version of the default library while target
another version of emit JavaScript.
Such approach is cumbersome to maintain and consume any necessary fixes of the default library.
Related Issues:
- #4692 Proposal: Granular Targeting
- #4168 Normalize our lib files by compiler settings
- #3215 New APIs added to lib.d.ts may break client codes. Allow duplicated members in interfaces? Make lib.d.ts overridable?
- #3005 Using ES6 type default library when targetting ES5 output
Proposed Solution
There are two parts to the proposed solution:
- Introducing a new compiler flag
--lib
. The flag will be used to allow users to granularly control the default library.
Note:target
flag will remain. Its behaviors with--lib
flag will be discussed below. - Breaking down the default library into smaller files, especially ES6 library and any future JavaScript library.
Compiler Flag --lib
The flag's options (see below for the full list) allows users to specify what library to be included into the compilation.
The flag's options can be separated into following categories:
-
JavaScript only:
es3
es5
es6
-
Host only:
node
dom
dom.iterable
webworker
scripthost
-
ES6 or ESNext by-features options:
es6.array
es6.collection
es6.function
es6.generator
es6.iterable
es6.math
es6.number
es6.object
es6.promise
es6.proxy
es6.reflect
es6.regexp
es6.string
es6.symbol
es6.symbol.wellknown
es7.array.include
The --lib
flag is an optional flag and multiple options can be used in combination (e.g --lib es6,dom.iterable
) and each option will be mapped to associated library.
So in this example, files: lib.es6.d.ts
and lib.dom.iterable.d.ts
will be loaded into the compilation..
When --lib
is specified, --target
will be used solely to indicate version of emitting JavaScript.
In additional to above behavior, we still need to determine what to be included when --lib
is not specified, should it remain the same, including version of library specifying by --target
and all host libraries, or including every library. -> _Current implementation is to include version of the library using --target
The proposal will only affect when the compiler attempts to create compilation in Program.ts. Other parts of the pipeline will not be affected by the proposal.
Usage:
The --lib
flag will take an options in the form of string separated by comma. Space will not be allowed as a separator. This rule apply to both command line and tsconfig
Valid
tsc --target es5 --lib es6
tsc --target es5 --lib es5,es6.array
"compilerOptions": {
...
"lib": "es5,es6.array"
}
Invalid command-line
tsc --target es5--lib es5, es6.array // es6.array will be considered a file-name
tsc --target es5 --lib es5 es6.array // es6.array will be considered a file-name
"compilerOptions": {
...
"lib": "es5, es6.array"
}
Working Items:
- Breaking up the library (PR Library modularization #6990, this PR is folded into Proposal: Modularize Library #6974)Adding compiler options (PR Proposal: Modularize Library #6974)Language Service support in VSAdd node.d.ts and browser.d.ts
Activity
yortus commentedon Feb 9, 2016
Thanks @yuit, great to see progress on this.
I'm wondering about how this handles cases where there is crossover between features. For example, consider the following project setup:
Example Project
Promise
is polyfilled by the project as necessary so we can rely on that.Symbol.species
andSymbol.toStringTag
are not widely supported so should not be used in source code.How to get just the right type definitions?
For the above project, I'd like to tell
tsc
: "I want to include definitions forPromise
in the build, but NOT definitions for well-known symbols or methods that take generic iterables". That way, I get type safety forPromises
and compile-time failure (rather than runtime failure) if I try to use unsupported constructs likePromise.all(someIterable)
.I believe situations like this really get to the point of granular targeting. I've used
Promise
as an example but there are similar crossover issues withRegExp
,Date
, typed arrays,Map
,Set
, and other builtins. We want early failure for referencing things we already know aren't going to work reliably at run time. The compiler can really help here by letting us catch these errors at transpile time, rather than runtime.How to handle feature crossover?
I may have missed something, but I don't see any handling of feature crossover in this proposal as it is currently stated. It looks to me like the following would be the best that can be done:
Promise
ines6.d.ts
is currently all-or-nothing, including definitions that use well-known symbols and generic iterables, even if we explicitly want to avoid them.symbols.d.ts
with well-known symbol declarations placed there, and taken out of places likepromise.d.ts
It could use declaration merging to add to existing declarations, likeinterface PromiseConstructor { [Symbol.species]: Function; }
.symbols
but notpromises
, then we'll still have aPromise
andPromiseConstructor
defined bysymbols.d.ts
.This is where a
tsc
-internal mechanism that does the equivalent of conditional compilation shines - it starts with all known definitions and filters out anything not supported on a line-by-line basis, which covers cross-over between features perfectly at arbitrary levels of granuarity. See for example thises6.d.ts
. I'm NOT suggesting conditional compilation, just a purely internal mechanism that does effectively the same thing to get the correct definitions, with no more and no less than what the user asked for via--lib
.yuit commentedon Feb 9, 2016
@yortus Thanks for the feedback and pointing out some holes in the proposal.
Yes you are right that the current proposal when you pick feature such as
promise.d.ts
, it is all or nothing. You cannot just pick only sub-part of thepromise.d.t
.So building upon your proposed solution, we can 1) separate symbol into its own file (probably same as iterator) 2) separate symbol in
Promise
or other es6 features in to their own files likees6.promiseWithSymbol.d.ts
which will get included when you specified that you wantes6.symbol
. I am trying to not to overly granularly separate the files so we don't end up creating large amounts of tiny lib. though for this as you suggest here there may not be too bad. Also, to clarify, I think these smaller files likees6.promiseWithSymbol.d.ts
should not be exposed as options to users.yortus commentedon Feb 10, 2016
@yuit sounds reasonable :)
I agree there are definitely diminishing returns to supporting finer granularity. However, I do think the choice of "grains" should be informed by how the browsers/JS runtimes actually add feature support over time, which is what this issue is meant to address after all.
For example, the
Symbol
builtin is widely supported now, but well-known symbols still have very little support. I can definitely make use ofSymbol
in my node projects today, but I must avoid pretty much all well-known symbols. And they have pretty different use-cases anyway.So, while I would not suggest having a separate flag for each of the 2 dozen or so well-known symbols, I would suggest having a separate flag for
ES6.Symbol
and forES6.WellKnownSymbols
, based on their actual availability.Agreed, the number and names of all these
.d.ts
files can just be an implementation detail known totsc
. It can pick the right set of files according to a naming convention based on the supplied--lib
flags.alexeagle commentedon Mar 6, 2016
@mhegazy note that it would be nice for one of the new lib.es6* files to be the minimal one for
tsc --target es6 --noLib node_modules/typescript/lib/lib.d.ts app.ts
(from #5504)
12 remaining items
basarat commentedon Apr 24, 2016
Going through and adding to alm. I know it says
"lib": "es5, es6.array"
. But wouldn't"lib": ["es5","es6.array"]
be better in tsconfig.json? Is this how future array options (if there aren't any yet) be represented intsconfig.json
'scompilerOptions
?Thanks! 🌹
basarat commentedon Apr 24, 2016
More questions:
getDefaultLibFileName
in language service host (perhaps we should start going withFileNames
(notice s) for these apis.getDefaultLibFileName
should have ans
suffix.es6.array
ores2015.array
. Note: the file is calledlib.es2015.array.ts
Here is a sample of the function I needed to delegate to eventually:
With this I have it working in alm. Feedback appreciated 🌹
basarat commentedon Apr 24, 2016
In fact
rootDirs
is an array from the discussion : #8245 (comment) Maybelib
should be too intsconfig.json
basarat commentedon Apr 25, 2016
Verified by @2426021684 (TypeStrong/atom-typescript#918 (comment)) it is an array, as it should be 🌹
yuit commentedon Jun 23, 2016
Update our decision on to ship node.d.ts or not to ship, the outcome is that we will not ship the node.d.ts as our default lib for the following reasons:
lib
es6 loses window and other dom stuff #9500