Skip to content

Proposal: Granular Targeting #4692

Closed
Closed
@yortus

Description

@yortus

This proposal is based on a working implementation at:
https://github.com/yortus/TypeScript/tree/granular-targeting
To try it out, clone it or install it with npm install yortus-typescript

Problem Scenario

The TypeScript compiler accepts a single target option of either ES3, ES5 or ES6. However, most realistic target environments support a mixture or ES5 and ES6, and even ES7, often known in advance (e.g. when targeting Node.js, and/or using polyfills).

Using TypeScript with target environments with mixed ES5/5/7 support presents some challenges, many of which have been discussed in other issues. E.g.:

In summary:

  • The default lib either includes all ES6 types and properties, or none of them.
  • Specifying --noLib and/or manually maintaining lib.b.ts files brings other problems:
    • separate core typings are a burden to maintain.
    • problems of missing symbols and clashing symbols.
    • burden of manually tracking fixes and additions made in the default libs.
  • Targeting ES5 as the 'lowest common denominator' means some language features known to be supported cannot be used (eg generators in Node.js).
  • Targeting ES6 (e.g. to take advantage of Node.js' support for many ES6 features) leads to further complications:
    • CommonJS modules won't compile, even though that's the only module system Node supports. (fixed by Support modules when targeting ES6 and an ES6 ModuleKind #4811)
    • The compiler emits ES6 even for features that are known not to be supported, which would fail at runtime.
    • Adding babel.js to the build pipeline adds complexity.

Workarounds

To achieve mixed ES5/ES6 core typings:

  • specify --target ES5 and selectively add ES6 typings in separately maintained files (eg from DefinitelyTyped).
  • specify --target ES6 and be careful to avoid referencing unsupported ES6 features (the compiler won't issue any errors).
  • specify --noLib and manually maintain custom core typings in your own project.

To use ES6 features supported by the target platform

  • specify --target ES5 and (a) accept that things will be down-level emitted, and (b) don't use features with no down-level emit yet (ie generators).
  • specify --target ES6 and (a) convert everything from CommonJS to ES6 modules (fixed by Support modules when targeting ES6 and an ES6 ModuleKind #4811), (b) add babel.js to the build pipeline, and (c) configure babel.js to do either pass-through or down-level emit on a feature-by-feature basis.

Proposed Solution

This proposal consists of two parts:

1. Support for conditional compilation using #if and #endif directives, so that a single default lib can offer fine-grained typings tailored to a mixed ES3/5/6/7 target environment.

The conditional compilation part is detailed in a separate proposal (#4691) with its own working implementation.

1. A mechanism allowing the default lib to offer fine-grained typings tailored to a mixed ES3/5/6/7 target environment.

This is really an internal compiler detail, so the mechanism is open to debate. It just has to match the granularity supported by the new compiler options below.

The working implementation uses #if...#endif conditional compilation proposed in #4691. But this is overkill for this use case and seems unlikely to be considered.

Several other mechanisms have been discussed (summarized here).

2. Support for additional compiler options allowing the target environment to be described on a feature-by-feature basis.

Under this proposal, the target option remains, but is now interpreted as the 'baseline' target, determining which features the target supports by default. For instance, ES6 symbols and generators are supported by default if target is set to ES6 or higher.

The additional compiler options have the form targetHasXYZ, where XYZ designates a feature. These options are used to override the target for a particular language feature. They instruct the compiler that the target environment explicitly does or does not support a particular feature, regardless of what the target option otherwise imples.

The working implementation currently supports the following additional compiler options (all boolean):

  • targetHasArrowFunctions: specify whether the target supports ES6 () => {...} syntax
  • targetHasBlockScoping: specify whether the target supports ES6 let and const
  • targetHasForOf: specify whether the target supports ES6 for..of syntax
  • targetHasGenerators: specify whether the target supports ES6 generators
  • targetHasIterables: specify whether the target supports ES6 iterables and iterators
  • targetHasModules: specify whether the target supports ES6 modules
  • targetHasPromises: specify whether the target supports ES6 promises
  • targetHasSymbols: specify whether the target supports ES6 symbols

These options work both on the command line and in tsconfig.json files.

Example tsconfig.json Files and their Behaviour

A.

{
    "target": "es6",
    "targetHasModules": false,
    "targetHasBlockScoping": false,
    "module": "commonjs"
}

Emits ES6 JavaScript, except with CommonJS module syntax, and with let/const down-leveled to var. This might match a Node.js environment.

B.

{
    "target": "es5",
    "targetHasSymbols": true
}

Emits ES5 JavaScript, except with Symbol references emitted as-is, and with full type support for well-known symbols from the default lib.

C.

{
    "target": "es5",
    "targetHasPromises": true
}

Emits ES5 JavaScript, except with full type support for ES6 promises from the default lib. This would work in an ES5 environment with a native or polyfilled Promise object.

Backward Compatibility, Design Impact, Performance, etc

  • There is no impact on existing TypeScript projects. The additional options and preprocessor directives only modify the compiler's behaviour if they are explicitly used.
  • The preprocessor directives #if and #endif add new language syntax. No existing language features are affected.
  • There is negligable impact on compiler performance.
  • Only one default lib is needed (lib.es6.d.ts). It contains many conditionally compiled sections (ie with #if and #endif)

Remaining Work and Questions

  • Support compiler options for more target features, e.g.:
    • template strings
    • classes
    • Map/Set/WeakMap/WeakSet
    • binary and octal literals
    • destructuring
    • default, rest, and optional parameters
  • How granular could/should targets be? Feature support is naturally hierarchical. E.g. block scoping may be separated into (a) let, (b) const and (c) block-level function declaration. This is true of most features and their realistic implementations (the Kangax ES6 compatibility table has a three-level hierarchy down the left side).

Metadata

Metadata

Assignees

No one assigned

    Labels

    SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions