Skip to content

Proposal: Conditional Compilation #3538

Closed as not planned
Closed as not planned
@kitsonk

Description

@kitsonk

Proposal: Conditional Compilation

Problem Statement

At design time, developers often find that they need to deal with certain scenarios to make their code ubiquitous and runs in every environment and under every runtime condition. At build time however, they want to emit code that is more suited for the runtime environment that they are targetting by not emitting code that is relevant to that environment.

This is directly related to #449 but it also covers some other issues in a similar problem space.

Similar Functionality

There are several other examples of apporaches to solving this problem:

Considerations

Most of the solutions above use "magic" language features that significantly affect the AST of the code. One of the benefits of the has.js approach is that the code is transparent for runtime feature detection and build time optimisation. For example, the following would be how design time would work:

has.add('host-node', (typeof process == "object" && process.versions && process.versions.node && process.versions.v8));

if (has('host-node')) {
    /* do something node */
}
else {
    /* do something non-node */
}

If you then wanted to do a build that targeted NodeJS, then you would simply assert to the build tool (staticHasFlags) that instead of detecting that feature at runtime, host-node was in fact true. The build tool would then realise that the else branch was unreachable and remove that branch from the built code.

Because the solution sits entirely within the language syntax without any sort of "magical" directives or syntax, it does not take a lot of knowledge for a developer to leverage it.

Also by doing this, you do not have to do heavy changes to the AST as part of the complication process and it should be easy to identify branches that are "dead" and can be dropped out of the emit.

Of course this approach doesn't specifically address conditionality of other language features, like the ability to conditionally load modules or conditional classes, though there are other features being introduced in TypeScript (e.g. local types #3266) which when coupled with this would address conditionality of other language features.

Proposed Changes

In order to support conditional compile time emitting, there needs to be a language mechanic to identify blocks of code that should be emitted under certain conditions and a mechanism for determining if they are to be emitted. There also needs to be a mechanism to determine these conditions at compile time.

Defining a Conditional Identifier at Design Time

It is proposed that a new keyword is introduced to allow the introduction of a different class of identifier that is neither a variable or a constant. Introduction of a TypeScript only keyword should not be taken lightly and it is proposed that either condition or has is used to express these identifiers. When expressed at design time, the identifier will be given a value which can be evaluated at runtime, with block scope. This then can be substituted though a compile time with another value.

Of the two keywords, this proposal suggests that has is more functional in meaning, but might be less desirable because of potential for existing code breakage, but examples utlise the has keyword.

For example, in TypeScript the following would be a way of declaring a condition:

has hostNode: boolean = Boolean(typeof process == "object" && process.versions && process.versions.node && process.versions.v8);

if (hostNode) {
    console.log('You are running under node.');
}
else {
    console.log('You are not running under node.');
}

This would then emit, assuming there is no compile time substitutional value available (and targeting ES6) as:

const hostNode = Boolean(typeof process == "object" && process.versions && process.versions.node && process.versions.v8);
if (hostNode) {
    console.log('You are running under node.');
}
else {
    console.log('You are not running under node.');
}

Defining the value of a Conditional Identifier at Compile Time

In order to provide the compile time values, an augmentation of the tsconfig.json is proposed. A new attribute will be proposed that will be named in line with the keyword of either conditionValues or hasValues. Different tsconfig.json can be used for the different builds desired. Not considered in this proposal is consideration of how these values might be passed to tsc directly.

Here is an example of tsconfig.json:

{
    "version": "1.6.0",
    "compilerOptions": {
        "target": "es5",
        "module": "umd",
        "declaration": false,
        "noImplicitAny": true,
        "removeComments": true,
        "noLib": false,
        "sourceMap": true,
        "outDir": "./"
    },
    "hasValues": {
        "hostNode": true
    }
}

Compiled Code

So given the tsconfig.json above and the following TypeScript:

has hostNode: boolean = Boolean(typeof process == "object" && process.versions && process.versions.node && process.versions.v8);

if (hostNode) {
    console.log('You are running under node.');
}
else {
    console.log('You are not running under node.');
}

You would expect the following to be emitted:

console.log('You are running under node.');

As the compiler would replace the symbol of hostNode with the value provided in tsconfig.json and then substitute that value in the AST. It would then realise that the one of the branches was unreachable at compile time and then collapse the AST branch and only emit the reachable code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions