Typescript: Support conditional compilation

Created on 13 Aug 2014  ·  39Comments  ·  Source: microsoft/TypeScript

On codeplex this was a popular feature request:

https://typescript.codeplex.com/workitem/111
https://typescript.codeplex.com/workitem/1926

Personally I think preprocessor directives like #if, #elif, #else #endif with possibility to specify symbol to compiler would be very useful.
And a way to mark function (or ambient function declaration) as Conditional (something like ConditionalAattribute in C#) would be great improvement too.
I have lot's of console.log like function calls that decrease performance of my application and ability to easily remove all calls to this function would be great.

Needs More Info Suggestion

Most helpful comment

Some use-cases:

  • Variable defintion
// Production sources and keys        
   var foo = {
        root: "https://yyy.blob.core.foobar.net/",
        googlePlusKey: { id: "888888", key: "GIzdfBy" },
        facebookKey: { id: "444444444" }
     };

// Development sources and keys        
#if(DEBUG)
   var foo = {
        root: "https://xxx.blob.core.foobar.net/",
        googlePlusKey: { id: "458588", key: "BIzdfGy" },
        facebookKey: { id: "123219585123132" }
     };
#endif
  • Import statement
#if(DEBUG)
       import foo = require('debug');
#else
       import foo = require('release');
#endif

function doFoo(){
    foo.someMethod();
}
  • Class definition
#if(DEBUG)
       class Foo { doFoo(){ console.log('debug'); }  }
#else
       class Foo { doFoo(){ console.log('release'); }  }
#endif

var foo = new Foo();

All 39 comments

We'd like to gather some use cases on this. What do people want to use this for? There might be better solutions than conditional compilation depending on the application.

We would likely not implement a ConditionalAttribute-style attribute as it violates the "Don't depend on typechecking for emit" design goal.

For the console.log case, adding conditional compilation would be a large hammer for a small nail. Doing this no-op'ing at runtime would be essentially zero overhead (assuming that you would be writing a human-readable amount of stuff to the console in the debug case).

I can think of a few cases:

  • Being able to control which parts (files) of the project are subject for a build. One can use directive constants like tags. This would allow slicing and dicing the project any way you like and focusing on certain pieces while doing refactoring. Currently in order to focus on a certain piece, say the data layer, without having to deal with cascading breaks across all layers, one has to exclude the unrelated files before and reincluding them all back after when the refactoring is done, which is a hassle. Such problem would not exist if VS had support for special TypeScript only projects that would be linked all together and could be addressed one-by-one separately.
  • Enabling _assertions_, _debugging_ along with all sorts of _backdoors_ for non-production versions. While, as you say, tracing can be done without conditional compilation with debugging it is different, because it takes more granular control over the code. Also for performance critical code conditional compilation is the only option.

Some use-cases:

  • Variable defintion
// Production sources and keys        
   var foo = {
        root: "https://yyy.blob.core.foobar.net/",
        googlePlusKey: { id: "888888", key: "GIzdfBy" },
        facebookKey: { id: "444444444" }
     };

// Development sources and keys        
#if(DEBUG)
   var foo = {
        root: "https://xxx.blob.core.foobar.net/",
        googlePlusKey: { id: "458588", key: "BIzdfGy" },
        facebookKey: { id: "123219585123132" }
     };
#endif
  • Import statement
#if(DEBUG)
       import foo = require('debug');
#else
       import foo = require('release');
#endif

function doFoo(){
    foo.someMethod();
}
  • Class definition
#if(DEBUG)
       class Foo { doFoo(){ console.log('debug'); }  }
#else
       class Foo { doFoo(){ console.log('release'); }  }
#endif

var foo = new Foo();

Having worked on the Roslyn API support for the C#/VB Preprocessor, I would def like to avoid bringing those huge complexity farms to TypeScript.

I am amenable though to other mechanism that might achieve the same goals, albeit with less flexibility.

       -- Cyrus

From: nabog [mailto:[email protected]]
Sent: Sunday, August 24, 2014 3:41 AM
To: Microsoft/TypeScript
Subject: Re: [TypeScript] Support conditional compilation (#449)

Some use-cases:

  • Variable defintion

// Production sources and keys

var foo = {

    root: "https://yyy.blob.core.foobar.net/",

    googlePlusKey: { id: "888888", key: "GIzdfBy" },

    facebookKey: { id: "444444444" }

 };

// Development sources and keys

if(DEBUG)

var foo = {

    root: "https://xxx.blob.core.foobar.net/",

    googlePlusKey: { id: "458588", key: "BIzdfGy" },

    facebookKey: { id: "123219585123132" }

 };

endif

  • Import statement

if(DEBUG)

   import foo = require('debug');

else

   import foo = require('release');

endif

function doFoo(){

foo.someMethod();

}

  • Class definition

if(DEBUG)

   class Foo { doFoo(){ console.log('debug'); }  }

else

   class Foo { doFoo(){ console.log('release'); }  }

endif

var foo = new Foo();


Reply to this email directly or view it on GitHubhttps://github.com/Microsoft/TypeScript/issues/449#issuecomment-53187591.

I think compiler directives would be useful in scenario when you are targeting different platforms and want to have as much shared code as possible. Similar to what compiler directives are used today in C#. Many libraries (in .net framework code too) have code with conditional compiler directives to maximize code reuse.
In JavaScript it's somewhat easier to detect if given feature is available but still there are situation when differences would be better handled by preprocessor directives for example for very different platforms (mobile, set-top-boxes, smart tv). It's sometimes easier to use compiler directive when there are differences in APIs and their behaviours.

We would likely not implement a ConditionalAttribute-style attribute as it violates the "Don't depend on typechecking for emit" design goal.

Are you afraid that sometimes it might be not clear that compiler have all type information about call (like in discussion about extension methods?
Well, for people that use noImplicitAny this won't be a problem. We are also not doing any complex code transformation. Just removing some code calls so result JavaScript code would still be readable

Conditional compilation would definitely be useful in some situation. There might be other solutions but preprocessor directives are very simple to use and might be the best (and simplest) solutions.

But I must admit that the more I think about this feature, the more I consider it less important and would understand if compiler team would focus on other higher priority features.

To be clear, "noImplicitAny" is not "noAny"

[Conditional('debug')]
declare function debugPrint(s: string);
var d = { n: debugPrint };
var x: any = d.n; // Legal even with noImplicitAny
x('hello'); // Call to 'x' will not be conditionally compiled

It looks like "full" conditional compilation with preprocessor directives is a complex thing to implement, so maybe it's not worth the effort.
But is it difficult to implement something like ConditionalAttribute so it would be easy to strip-out certain function call in code?

We would likely not implement a ConditionalAttribute-style attribute as it violates the "Don't depend on typechecking for emit" design goal.

A lot of minifiers allows to exclude console.log calls and this is often searched topic.
I understand your resistance to this feature, but I believe many will find it useful. It won't be very general and often used feature but if the costs of introducing this feature is relatively small (it might not. I just guess. I don't implement compilers.) than I think it's worth considering to add.

About syntax. I guess we don't need special syntax for attributes, something like "special comment" will be fine for me if that would be easier to implement:

/// [Conditional("debug")]
declare function debugPrint(s: string);

I have just found another one good use case for this feature. I want to to enable JSON Schema validation for API calls. And, of course, I want it only for dev and test builds.

There are two aspects of the problem:

  1. I need to include modules with these schemas conditionally (I know that it can be done with async loading, but it is _much_ more complex).
  2. I need an ability to enable/disable some statements that make Schema checks.

I'm writing a lot of code in Rust language and it has a suitable solution:

  • Any item can be marked with #[cfg(...)] attribute to enable or disable this item (example):
#[cfg(debug)]
mod test {
    // ...  
}
  • They have the cfg! macro that can be used to disable some statements in code:
let my_directory = if cfg!(windows) {
    "windows-specific-directory"
} else {
    "unix-directory"
};

After this RFC will be done, #[cfg()] attribute will be allowed to use with any code block.

So there are good start steps for TS:

#[cfg(validate_schema)]
module schemas {
    export var schema1 = {
        // ...
    };  
}
export class UserApi extends ApiBase {
    static authenticate(login: string, password: string): Promise<{user: models.CurrentUser}> {
        var promise =  this.request('/api/auth', {
            type: 'POST',
            data: {
                login: login,
                password: password
            }
        });

        #[cfg(validate_schema)]
        promise = promise.then(validateSchema);

        return promise;
    }
}

What do you think?

@s-panferov : it's certainly another solution, but there is potentially more code duplication than with standard pre-processor directives. I have to say that I don't understand the resistance to include them, since TypeScript can leverage the power of the compiler. It doesn't seem logical to have to jump through workarounds that scripted languages by nature need when there is an obvious and simple solution available.

@paul-reilly budget limit a good excuse for resistance, although I am in
your team and i say there are numerous ways to use it, although many of
them can be solved just by leveraging constants say we declare const debug = false which gives the runtime a strong hint to optimise things like if (debug) {...} up to exclusion which, at least for me, covers 90% of use
cases
On Dec 11, 2014 5:33 PM, "paul-reilly" [email protected] wrote:

@s-panferov https://github.com/s-panferov : it's certainly another
solution, but there is potentially more code duplication than with standard
pre-processor directives. I have to say that I don't understand the
resistance to include them, since TypeScript can leverage the power of the
compiler. It doesn't seem logical to have to jump through workarounds that
scripted languages by nature need when there is an obvious and simple
solution available.

Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/449#issuecomment-66701695
.

How about web.config somehow? My company said it's a bad practice to use #Debug or #Release cuz it deal with processors, so my company require us to use web.config for that purpose. (The trasnformation to web.config takes care of it).

I would definitely like to see conditional compilation in TypeScript. I have a use case for it right now, where I'd like to enable some JavaScript code for when developers run our application, but not include the code at all when it is deployed. Conditional compilation would be ideal for this.

for me it would be very useful for:

1) targeting different platforms, like browser vs node
2) unit testing. Sometimes it simplifies unit testing a lot when I can just add public properties or functions to a classes with a #TEST condition.

At my company, we're discourage from using any compilation option in source code (cuz it deals with CPU) and we're required to use the config file (App.Config, Web.Config, *.Config.Debug, *.Config.Release) instead. So, it's a moot point here when it come to some business and not personal preferences.

Is there any update on this? I know its kind of a pain to implement . . . but its also very useful for us typescriptters :) .

We need a proposal for this feature. Pre-processor directives (i.e.#ifdefs) are not desirable. But something along the lines of Conditional in C# would be more like it. Either ways we need a proposal for this to move it to the next stage (i.e Accepting PRs).

Right now, this is a deal breaker for me in a project I'm working on.

I helped developed an in-house isomorphic framework that could be ran on server and the client. So in AMD we use a lot of conditional imports and conditional executions.

defined(function(exports, require) {
  if (insServer) {
    // require and do something
    // do something
  }
  else if (inClient) {
    // require and do something
    // do something
  }
  // do common stuff
});

We haven't been able to switch to TS because there are no mechanism that deal with our problem.

I think there many other projects that could benefit from this. At least many cross platform frameworks need this feature.

I'm also not sure if the conditional in C# would fit our use case.

We haven't been able to switch to TS because there are no mechanism that deal with our problem.

as a workaround why not have a different ambient .js file for server vs. client

In the msdn blog post Angular 2: Built on TypeScript:

We have worked with the Angular team to design a set of new features that will help you develop cleaner code when working with dynamic libraries like Angular 2, including a new way to annotate class declarations with metadata. Library and application developers can use these metadata annotations to cleanly separate code from information about the code, such as configuration information or conditional compilation checks.

So, isn't Angular 2 a use case? :)

Also I'd like to provide my use case: we need to compile different versions of our js library. Some versions implement a feature in a powerful complex way, while some versions implement the feature in a very simple way (size is smaller and size matters).

@aleksey-bykov I agree with you here.

Ideally I'd like to see something like the closure compiler, where you can provide compile time definitions:
closure-compiler --define "DEBUG=true"

I have a use case for this. I currently have a working iOS and Android app built on the Ionic Framework. The app uses cordova plugins to take advantage of the native capabilities of the phone or tablet on which it is run.

However, we now want to reuse the same code to build a single-page website that can run on a standard desktop browser. We cannot call cordova plugins from the desktop. In certain places, we'll have different code for the app version vs the desktop version. It would be nice if we could conditionally compile either the app version or the desktop version based on some symbol.

My use case (as mentioned by others) is when writing isomorphic code targeting different platforms. e.g. node/client. It'd be great to have a solution to this.

Hi guys, check out this project : https://github.com/domchen/typescript-plus . It is an enhanced version of the original typescript compiler, which provides conditional compilation.

You can use the defines option to declare global variables that the compiler will assume to be constants (unless defined in scope). Then all the defined global variables will be replaced with the corresponding constants. For example:

tsconfig.json:

{
    "compilerOptions": {
        "defines": {
            "DEBUG": false,
            "LANGUAGE": "en_US"
        }
    }
}

TypeScript:

declare var DEBUG:boolean;
declare var LANGUAGE:string;

if (DEBUG) {
    console.log("DEBUG is true");
}

console.log("The language is : " + LANGUAGE);

function someFunction():void {
    let DEBUG = true;
    if (DEBUG) {
        console.log("DEBUG is true");
    }
}

JavaScript:

if (false) {
    console.log("DEBUG is true");
}

console.log("The language is : " + "en_US");

function someFunction() {
    var DEBUG = true;
    if (DEBUG) {
        console.log("DEBUG is true");
    }
}

As you can see, the second if(DEBUG) in someFunction is not replaced because it is defined in scope.

Note that the compiler does not dropping the unreachable code, because it is can be easily done by other tools like UglifyJS or Google Closure Compiler.

here is a use case:

i wish i could make code broken for one single developer in my team, so that it doesn't crash the build but whoever is mentioned there won't be able to compile it unless the issue is addressed

#if AB // <-- my initials as a compile time constant that only set on my machine
 !!! please fix what's below !!!
#endif

Conditional compilation can be done with existing tools.

I use two separated files with this same declarations but different values:

  • _/src/conditional/dev/AppConstants.ts_:
    const enum AppConstants { DEBUG_MODE = 1 }
  • _/src/conditional/release/AppConstants.ts_:
    const enum AppConstants { DEBUG_MODE = 0 }

I also has two tasks in build system, both tasks refers to some shared code but first also has defined path to _/src/conditional/dev_ when second one use _/src/conditional/release_.

With that TypeScript will transpile any if-else statement where AppConstants.DEBUG_MODE is used to: if (0) { ... } else { ... } or if (1) { ... } else {...} and code like this can be easily optimized with uglifyjs…

I just put this here if someone finds it useful.
I had a lot of problems regarding this on a multi platform typescript project I am working on.
I use webpack as the final bundler and I created a small webpack loader module to help me conditionally turn on and off typescript code.
Basically I can write something like this:

//#ifdef PLATFORM_A
import { stuff } from './platform-specific-code/platform_a'
//#else
import { other_stuff } from './platform-specific-code/platform_b'
//#endif

or maybe like this with dynamic imports:

      //#ifdef __EXEC_ENV_BROWSER
      const { PlatformHandlerBrowser } = await import("./browser/platform-handler-browser");
      return new PlatformHandlerBrowser();
      //#endif
      //#ifdef __EXEC_ENV_NODE
      const { PlatformHandlerNode } = await import('./node/platform-handler-node');
      return new PlatformHandlerNode();
      //#endif
      //#ifdef __EXEC_ENV_QT
      const { PlatformHandlerQt } = await import("./qt/platform-handler-qt");
      return new PlatformHandlerQt();
      //#endif

Anyway... if you find it useful it is available here: https://github.com/Ramzeus/webpack-preprocessor-loader

@sebas86 That's not really conditional compilation. If I have code that only compiles under certain circumstances (conditions), typescript will attempt to compile all if/else blocks and it will fail.

I am also interested in something like this. Our use case being TS requires a constructor for our AngularJs classes, but they are not needed in the actual .js files. When transpiling against ES6 the constructor gets put in to the .js file and breaks our buildscripts. Unfortunately some things are out of my control with the framework the company has implemented, and I cant find a way around this. However removing the constructor from the .js files works fine with the build.

If there was a way to tell the transpiler to ingore lines, or blocks of code, maybe something like how you can tell eslint to ignore a line with a comment or something? I wouldn't care as much as how it would be implemeneted, but would love the functionality to do so.

My use-case: I'm writing a JSX/DOM patcher (for fun and practice) and it has a lot of internal decision-making about whether to recycle/discard existing elements, reposition or leave elements in-place, etc.

Writing tests for this is extremely difficult, because there isn't always any direct evidence in the DOM afterwards of the precise internal decision-making steps - however, these are very important, and testing whether it's internally making the right decisions is much simpler than testing the results and/or side-effects, and in some cases, not possible at all.

I went looking for this feature because I was hoping to use conditional compilation to maintain a log of the internal decision-making steps, and then write tests that make assertions about the decisions that were made. I could of course inject an optional logger of some sort, but this being a minimalist JSX/DOM patcher, file-size and performance are both critical factors.

So for now, I'm doing a lot of console.log() while testing the internal logic of this thing, and manually validating the decisions on-screen. I'm of course also writing tests for the resulting effects of calling this function, and I'm hoping that this well be enough, once I'm satisfied with the decision-making logic and erase the console.log() statements - however, at that point, it becomes extremely risky to change/refactor the decision-making logic.

With compiler conditionals, I could have made this internal logging-feature available for testing only.

Of course, I can still accomplish something similar using a workaround - for example, I could flag certain lines with a trailing comment like // ERASE, and then have a search-and-replace built into a build-script that removes parts of the code after running the tests.

Hello,

Our usecase is the same: dealing with console.logs in dev mode that we don't want to be effective in production.

There are many cases, haxe, haskell, c/cpp all have it. In my case I need a 'compile with mocking' and 'compile without mocking' flag.

Here's our use case. This feature would greatly simplify our lives.

We have a common code-base targeting the browsers and Cordova hybrid apps. Parts of our code base only deal with one or the other.

We would love if we could leave certain parts out for different builds. They also pull in different dependencies. So when building for the web we have to pull in Cordova dependencies otherwise TS complains about missing types.

It is possible to check code in node (with --check flag) and would be great to have similar functionality in tsc.

coming back to https://github.com/Microsoft/TypeScript/issues/449#issuecomment-261291540, we need it so bad that we gonna use a local git config to store initials and a custom tslint rule to flag the places marked by those initials in code

// @ts-ignore: FIX: AB: // <-- tslint will flag this place for AB (me)
function xxx() { // .. some ill code here

CASE: I need to include
moduleId: module.id
in a Angular @Component decorator when I am in DEV mode, but not in Prod.

see https://stackoverflow.com/q/55854688/279393

I have another case:

If you have a simple routing:

Array<{
   title: ....
  component: ....
  sitemap_content: ....
}>

Then the sitemap_content should not be send to client, doesn't make sense.

The if (static-flag) doesn't work either.
If you try using it you might get typing errors

Well, fine... You could be using a dictionary instead and create a new type for the sitemap forcing you to think about each route ... The problem then is that you loose ordering -> Yes you can create another list 'ordering' .. but then you have to edit 2 files and 2 places rather than copy paste.

Then I feel serving the compiler, not my projects.

@sebas86 That's not really conditional compilation. If I have code that only compiles under certain circumstances (conditions), typescript will attempt to compile all if/else blocks and it will fail.

But it works and for me and was good enough when I need something similar to preprocessor. Sometimes you must work with what you actually have instead waiting. This has some positives and negatives as well. Positive is that it's a regular code - your IDE see always whole code and you can safely refactor whole code base, negative is that your IDE also always see whole code and some tricks are not allowed, for example you can't duplicate some symbols or cut off whole class definitions without getting errors or warning...

Moreover please don't think of it as excuses for lack of real preprocessor. It is just a hint for others which already need a working solution without waiting for something else.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

bgrieder picture bgrieder  ·  3Comments

uber5001 picture uber5001  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments

fwanicka picture fwanicka  ·  3Comments