TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Support conditional compilation

Open mpawelski opened this issue 9 years ago • 74 comments

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.

mpawelski avatar Aug 13 '14 21:08 mpawelski

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).

RyanCavanaugh avatar Aug 18 '14 19:08 RyanCavanaugh

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.

zpdDG4gta8XKpMCd avatar Aug 18 '14 20:08 zpdDG4gta8XKpMCd

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();

NoelAbrahams avatar Aug 24 '14 10:08 NoelAbrahams

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.

CyrusNajmabadi avatar Aug 27 '14 15:08 CyrusNajmabadi

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.

mpawelski avatar Aug 28 '14 22:08 mpawelski

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

RyanCavanaugh avatar Aug 28 '14 22:08 RyanCavanaugh

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);

mpawelski avatar Sep 21 '14 23:09 mpawelski

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 avatar Nov 19 '14 10:11 s-panferov

@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 avatar Dec 11 '14 22:12 paul-reilly

@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 .

zpdDG4gta8XKpMCd avatar Dec 11 '14 23:12 zpdDG4gta8XKpMCd

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).

fletchsod-developer avatar Dec 12 '14 14:12 fletchsod-developer

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.

jez9999 avatar Jan 20 '15 00:01 jez9999

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.

agnauck avatar Apr 03 '15 10:04 agnauck

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.

fletchsod-developer avatar Apr 03 '15 13:04 fletchsod-developer

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

AbubakerB avatar May 01 '15 10:05 AbubakerB

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).

mhegazy avatar May 01 '15 16:05 mhegazy

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.

tinganho avatar Jun 17 '15 02:06 tinganho

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

tinganho avatar Jun 17 '15 02:06 tinganho

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

basarat avatar Jul 16 '15 23:07 basarat

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).

karldodd avatar Oct 14 '15 10:10 karldodd

@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"

jameskeane avatar Jan 12 '16 17:01 jameskeane

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.

battmanz avatar Mar 01 '16 00:03 battmanz

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.

jameslong avatar Mar 29 '16 15:03 jameslong

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.

domchen avatar Oct 13 '16 05:10 domchen

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

zpdDG4gta8XKpMCd avatar Nov 17 '16 16:11 zpdDG4gta8XKpMCd

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…

sebas86 avatar Mar 06 '17 14:03 sebas86

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

PatrLind avatar Aug 16 '17 12:08 PatrLind

@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.

Trucoto avatar Sep 08 '17 15:09 Trucoto

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.

hanchan07 avatar Nov 28 '17 20:11 hanchan07

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.

mindplay-dk avatar Dec 09 '17 13:12 mindplay-dk