Skip to content

Support conditional compilation #449

Description

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.

Activity

  1. RyanCavanaugh commented on Aug 18, 2014

    @RyanCavanaugh
    Member

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

  2. zpdDG4gta8XKpMCd commented on Aug 18, 2014

    @zpdDG4gta8XKpMCd

    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.
  3. NoelAbrahams commented on Aug 24, 2014

    @NoelAbrahams

    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();
  4. CyrusNajmabadi commented on Aug 27, 2014

    @CyrusNajmabadi
    Contributor

    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:notifications@github.com]
    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//issues/449#issuecomment-53187591.

  5. mpawelski commented on Aug 28, 2014

    @mpawelski
    Author

    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.

  6. RyanCavanaugh commented on Aug 28, 2014

    @RyanCavanaugh
    Member

    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
  7. mpawelski commented on Sep 21, 2014

    @mpawelski
    Author

    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);
    
  8. s-panferov commented on Nov 19, 2014

    @s-panferov

    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?

  9. paul-reilly commented on Dec 11, 2014

    @paul-reilly

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

  10. zpdDG4gta8XKpMCd commented on Dec 11, 2014

    @zpdDG4gta8XKpMCd

    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" notifications@github.com wrote:

    Stanislav Panferov (@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
    #449 (comment)
    .

  11. fletchsod-developer commented on Dec 12, 2014

    @fletchsod-developer

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

  12. jez9999 commented on Jan 20, 2015

    @jez9999

    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.

  13. agnauck commented on Apr 3, 2015

    @agnauck

    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.
  14. 70 remaining items

  15. tohagan commented on Jan 19, 2023

    @tohagan

    Did you consider my suggestion above for using compiler optimisation as a means to (a) preserve existing syntax and semantics (b) perform conditional compilation using constant expression evaluation and dead code elimination in if statements?
    Simplest example:

    if (DEBUG) {
      console.debug("something")
    }

    Compiler would evaluate DEBUG constant at compile time and if falsey not output the if statement body.

    How do you know its constant and isn't mutated? This is the problem Prepack was trying to work through and AFAIK is dead in the water because its HARD

    Yes i can understand that in the general case you can't make this assumption but what you can do it tell the compiler via a config option that it's safe to make this kind of assumption and consequent optimisation.

  16. JessicaMulein commented on Jun 10, 2023

    @JessicaMulein

    This is more of a question than a feature request, but I am interested in doing some C#/C style macros to transliterate if/then/etc into cyrillic/ukrainian so that developers can develop in their native languages without switching back and forth into english keyboard charsets to input certain characters. Is this already possible?

  17. MarcWeber commented on Jun 14, 2023

    @MarcWeber
  18. zm-cttae commented on Jun 18, 2023

    @zm-cttae

    Marc Weber (@MarcWeber) I think you may have the wrong issue!


    I solved my issues by having a base config for development only, then doing multiple passes of different configs that change the include setting.

  19. qwertie commented on Jan 11, 2024

    @qwertie

    Oh god, please not #if. It's a trifecta of awfulness:

    1. it tends to be very limited (e.g. in C, you cannot write conditions that refer to the program's types, functions or variables)
    2. the engineering effort to implement it (including syntax highlighting in VS Code etc) would be high
    3. the syntax/semantics of #if are strange for people who aren't used to it, and code using it tends to be ugly

    I think people want two different things from this:

    Conditional methods like debugLog(...) that vanish in production

    Here the goal is to avoid a performance penalty. It seems like you can't completely accomplish this with "#if" inside the definition of debugLog, since there may be a cost to call a completely empty function ... or would modern JS runtimes fully optimize that away? My guess: JS runtimes can optimize away calls to empty global functions, but maybe not empty methods.

    Vanishing 'if' blocks, like if (process.env.NODE_ENV !== 'production') {...}

    This exact syntax is already supported by the create-react-app toolchain (not sure which part of it).

    To me, the obvious way for TypeScript to support this is at the type-system level, so you could write things like

    type VersionIsAtLeast<PackageName extends string, V extends number> =
        (magic expression for checking the major version number of a package);
    
    if type (VersionIsAtLeast<"react", 18>) {
        import { Suspense } from 'react';
    
        export function Foo() { use_react_suspense(); }
    } else if type (compilerOptions<'target'> extends 'esnext') {
        export function Foo() { use_esnext_feature(); }
    }
    
    if type (tryGet<DEBUG, true>)
        export function bar() { console.log('debug_version'); etc(); }
    else
        export function bar() { etc(); }

    I'm assuming:

    • You can call Foo() outside the if type block (its braces are deleted)
    • There are intrinsics like compilerOptions (from tsconfig.json) and dependencies (from package.json)
    • tryGet<T, Alt=unknown> is a magic error-suppression intrinsic that returns Alt if symbol T cannot be found
    • X extends Y means X extends Y ? true : false (this need not be limited to the if type context)
    • if type (X) means if type (X extends (false | 0 | null | undefined | '')) except that it shows an error if X is never or unknown or ambiguous (both truthy and falsy, e.g. any or boolean or a generic type T).

    For a feature like this to work, the way TypeScript gathers type information must be publicly defined, so we can answer questions like "does the following code show an error that X is not defined?"

    if type (X) {
        function foo() {}
    }
    type X = true;

    And what if X is defined in another module 'x', and 'x' also imports the current module?

    It would be nice if this was bundled with other type-level operators like type Untrue = 3 < 2 because this kind of feature would make people try stuff like that.

  20. tohagan commented on Jan 11, 2024

    @tohagan

    We're not advocating that we implement #if / #define etc, just that we find a reasonable method to extend TS to support the use cases that a C/C++ preprocessor fulfilled.

    The solution you've proposed only offers different methods of expressing the condition but it fails to solve the fundamental problem of import scope that I explained in my examples above.

    Once you make this assumption...

    "You can call Foo() outside the if type block (its braces are deleted)"

    ... you've broken backwards compatibility and fundamentally altered the semantics of the TS scoping rules.

  21. qwertie commented on Jan 11, 2024

    @qwertie

    No, if type is a fundamentally new construct so there's no backward compatibility to break. (But if it would make you feel better, it could be called #if without taking on any of the other syntactic or semantic baggage from C/C++)

  22. matrixcloud commented on Aug 30, 2024

    @matrixcloud

    I think one of meaningful use case is we can use it for feature flag.

    #if FEAT_1_ENABLED
    // the feature code
    #end
  23. RWeigelt commented on Oct 7, 2024

    @RWeigelt

    In C#, I use #if a lot when refactoring or fixing bugs. I prefix the conditions with TBR (to be removed). When I'm done, I simply search for #if TBR, collapse the block and delete it. In TypeScript, I use block comments, but they cannot be nested.

    #if... #else also allows me to keep the old code around for comparison (we're talking about a few lines here, not pages of code).

    #if TBR_OLD_CODE
    ... old code ...
    #else
    ... new code ...
    #endif
    

    I use this for quick, small edits and it really helps me to keep me in the flow when developing.

  24. Rudxain commented on Nov 13, 2024

    @Rudxain

    There are cases where type-driven conditional-compilation could be useful: JS reduce can't know the expected type of an empty array (which TS can know), so the only disambiguator is an initial value. But this is a catch-22 because to provide a sensible init you need TS knowledge of function call-sites, which we can hack by hard-coding the overloads to force the dev to provide such information (this feels like manual monomorphization... yikes)

    Meanwhile, other langs:

    //edition = "2021"
    /*
    This is somewhat unfair,
    because the TS snippet didn't `import` 3p deps.
    
    But (AFAIK) no TS module can remove the extra param,
    so this is still a "win" for langs like Rust
    */
    use num_traits::Num; // 0.2
    
    /*
    We could use `core::Iterator::sum`,
    but `num_traits` supports a wider set of types (including big-nums),
    so we define our own `sum`
    */
    
    // off-topic: is `clone` unavoidable here?
    fn sum<T: Num + Clone>(a: &[T]) -> T { // no `dyn`amic dispatch!
        a.iter().fold(T::zero(), |acc, x| acc + x.clone())
    }
    
    // same result if we use `let` inside `main`
    const I: [u8; 0] = [];
    const F: Vec<f32> = Vec::new(); // fancy!
    
    fn main() {
        sum(&vec![0, 1, 2, -1, 3]); // ints ✅
        sum(&[0.0, 1.1, 2.5, -1.9, 3.333]); // floats ✅
    
        assert_eq!(sum(&I), 0u8); // ✅
        assert_eq!(sum(&F), 0.0f32); // ✅
    }
  25. denis-migdal commented on Sep 8, 2025

    @denis-migdal

    Wouldn't it be more interesting to use tools like Terser on top of TS ?

    TS responsibility is to type check. Optimizing code by removing debug stuff (and other) is more of the responsibility of tools like Terser, Webpack, etc.

  26. tohagan commented on Sep 9, 2025

    @tohagan

    Using Vite for conditional bundling.

    Many TypeScript apps now use Vite that supports conditional bundling via import.meta.env. Here's some use case examples:

    Client & server use case

    A classic use case is apps that need separate server-side and client-side logic when implementing SPA / SSR rendering:

    if (import.meta.env.SSR) { // server side
      const serverOnlyModule = await import('./server-only-module.js');
      // ... use serverOnlyModule
    } else { // client side
      const clientOnlyModule = await import('./client-only-module.js');
      // ... use clientOnlyModule
    }

    Conditional Features

    If you need to create different versions of an app (e.g. Free vs Premium) or different customer builds you can use a VITE_ prefixed environment variable:

    if (import.meta.env.VITE_FEATURE_ENABLED === 'true') {
      const featureModule = await import('./feature-module.js');
      // ... use featureModule
    }

    Conditional client-side routes

    We often wish to develop 2 highly related SPA apps for say "normal" users and "admin" users. Admins need all the client side routes that normal users have + the admin feature routes. Because these two app share many features, it's convenient to build all the features and their client routes for both app using a single TypeScript project using an SPA framework like React or Vue. However, we'd like the lite non-admin app to load faster without the admin level baggage and there are security benefits of not exposing the admin code to all users. Using Vite, I can build the client side routes for each app using if (import.meta.env.VITE_ADMIN_APP) conditions and then let Vite's conditional bundling, tree shaking and dynamic loading do the rest with no TypeScript compilers or language servers being harmed in the process.

    I posted an article about this that includes a demo app.

  27. denis-migdal commented on Sep 9, 2025

    @denis-migdal

    Conditional compilation with Terser :

    if( __DEBUG__ ) {
         console.warn("debug");
    }
    console.warn("Hello");
    $ terser main.js -d __DEBUG__=false
    console.warn("Hello");

    You can also use it for functions :

    function __LOG__(m) {
            if(__DEBUG__) console.warn(m);
    }
    
    __LOG__("ok");
    $ terser main.js --toplevel -d __DEBUG__=false
    

    You can also specify nested constants: --define env.DEBUG=false.

    Of course there is a lot of options, and a node API.

  28. tohagan commented on Sep 10, 2025

    @tohagan

    There's also unplugin-preprocessor-directives that can be used in Vite, Rollup, Webpack, Nuxt and esbuild to get build-time preprocessing. It supports: #if / #elif / #else / #endif conditions via comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs More InfoThe issue still hasn't been fully clarifiedSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions