-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support conditional compilation #449
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 For the |
I can think of a few cases:
|
Some use-cases:
// 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
#if(DEBUG)
import foo = require('debug');
#else
import foo = require('release');
#endif
function doFoo(){
foo.someMethod();
}
#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.
From: nabog [mailto:notifications@github.com] Some use-cases:
// Production sources and keys var foo = {
// Development sources and keys #if(DEBUG) var foo = {
#endif
#if(DEBUG)
#else
#endif function doFoo(){
}
#if(DEBUG)
#else
#endif var foo = new Foo(); — |
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.
Are you afraid that sometimes it might be not clear that compiler have all type information about call (like in discussion about extension methods? 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.
A lot of minifiers allows to exclude console.log calls and this is often searched topic. 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:
|
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:
I'm writing a lot of code in Rust language and it has a suitable solution:
#[cfg(debug)]
mod test {
// ...
}
let my_directory = if cfg!(windows) {
"windows-specific-directory"
} else {
"unix-directory"
}; After this RFC will be done, 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
|
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:
|
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. |
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. |
as a workaround why not have a different ambient |
In the msdn blog post Angular 2: Built on TypeScript:
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: |
On TS side the implementation is as complicated as providing a hook processing like
Of course could be made more complex like haxe and others. On browser side its equally simple. Now how abuot packaging ? If you drop packaging (cause TS does pakcage anyway) dist/+client/... However amount of cases might explose once you add if x < 1.0 & 1.1 .. But the question is a different one: If JS had specs TS would follow.
The funny thing is that JS doesn't have the typing issue. Cause its not typed. I mean if client / server code has different types JS will not mind. So its more a 'typing issue' rather than a JS issue ? No doubt TS makes life easier in many cases. But the TS idea can be applied to more languages. So if you're interested in starting the latter drop me an mail. Cause TypeScript has raised the bar IMHO. And esbuild /next.js rewriting babble or such using rust show So there are questions on other levels, too. |
Thanks for this more in depth explanation of the complexity of implementing an IDE language server. I think you've convinced me that there should be a better way.
So I'm going to suggest that we might backup a level and rethink the solution. I'm all for simpler! Does the existing TypeScript compiler perform compiler optimizations like
If so, then we may already have conditional compilation! (Post: OK, I now see that listed under TS Non-goals is "Aggressively optimize the runtime performance of programs. Instead, emit idiomatic JavaScript code that plays well with the performance characteristics of runtime platforms." So I'm guessing the answer is NO to the optimizations being already done. I wonder if this feature request would be sufficient argument for reconsidering these as potential code reduction optimizations?) |
No. This would not work. I even gave an example above of how this would break existing legal typescript code. Specifically, it would change the meaning of this code: let v = `foo {
#if false
bar
#else
baz
#endif
} quux`; Furthermore, this would not work with any higher layers as now even basic things like positions and file contents would be wrong. Take something as basic as formatting. You can't make edits safely if your positions in the file are incorrect or you have an indirect understanding of what text exists between any tokens. |
Ok, I apologize for misinterpreting then. Can you please clarify if I understood correctly now. It sounds to me like we all agree on this:
Is that correct? And, you also say that this would be very hard to add to the compiler, right? |
This is a feature that would be useful to some users of the language.
Yes. Agreed.
If it is to ever be added, it must not change the meaning of
existing/unaware valid programs (e.g suddenly start interpreting contents
of the strings).
Right. Breaking changes are bad.
And, you also say that this would be very hard to add to the compiler,
righ
I'd put it in the very-complex/costly category. It is both challenging to
add *and* it impacts practically everything above.
Given that, it has to be massively useful to a huge part of the ecosystem
to justify its costs.
…On Mon, Oct 31, 2022, 5:21 PM alenl ***@***.***> wrote:
Please stop trying to infer positions from me that i am not stating
Ok, I apologize for misinterpreting then. Can you please clarify if I
understood correctly now. It sounds to me like we all agree on this:
1. This is a feature that would be useful to some users of the
language.
2. If it is to ever be added, it must not change the meaning of
existing/unaware valid programs (e.g suddenly start interpreting contents
of the strings).
Is that correct?
And, you also say that this would be very hard to add to the compiler,
right?
—
Reply to this email directly, view it on GitHub
<#449 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABC2MY4VGSPD6YSEWUM7DMTWGBPBPANCNFSM4AS4QOEQ>
.
You are receiving this because you commented.
Message ID: ***@***.***>
|
I want to understand something... If left alone, how is the following valid code?
Calling If that's the case, it seems to me that if the developer created that code, they wanted it to be conditionally compiled. Nevermind that the But maybe the developer did that on purpose because |
how is the following valid code?
It's simply a multi line template string literal. This has been supported
in JavaScript and typescript for years.
Calling v();
You can't invoke v. It's a string.
because #if false isn't valid TypeScript. Correct?
The #if is just content in the string. JavaScript/typescript don't care
about it at all (just like any other content in a multi line string).
|
Sorry... At first glance, it looked like a function definition. 🤦♂️ |
Nothing to be sorry about :) |
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 |
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 |
It's so hard to add a rule validation on tsc BTW configured in .tsconfig? It would be something like C# compilation process with Debug and Release rules IMHO |
It's so hard to add a rule validation on *tsc* BTW configured in
*.tsconfig*? It would be something like C# compilation process with *Debug*
and *Release* rules IMHO
What would that do? C# doesn't understand typescript*at all*, so it
wouldn't have the first clue how to even parse it's files. And say you even
accepted the c# parse of things (which again doesn't at all match the
meaning of Js/TS give), what then? There's no facility for c# to somehow
pass that interpretation off to TS.
I highly recommend for those that think this is not complex to attempt this
:-)
|
My example I think has skewed and limited the scope of what I meant so let me try to clarify. More Examples: const PLATFORM = "Windows";
const LOGLEVEL = 2;
// if statement is entirely removed since PLATFORM === "Linux" is always false
if (PLATFORM === "Linux") {
dostuff1();
}
// condition is always true so if statement is removed and we emit just: { dostuff2(); }
if (LOGLEVEL <= 3) {
dostuff2();
}
// condition is always true so if statement is replaced with just: { dostuff3(); }
if (LOGLEVEL <= 3 && PLATFORM === "Windows") {
dostuff3();
} else {
dostuff4();
}
// replace with isWindows = true;
let isWindows = PLATFORM === "Windows";
// condition is always true so replace with: const msg = "Yes"
const msg = LOGLEVEL <= 3 && PLATFORM === "Windows" ? "Yes" : "No";
// msg condition is always false remove the if statement
if (msg === "No") {
dostuff5();
}
// replace with { dostuff6(); }
switch (PLATFORM) {
case "Windows":
doestuff6();
break;
case "Linux":
doestuff7();
break;
default:
doestuff8();
} A compiler optimiser traverses the AST tree and identifies sub expressions that contain constants (litererals and const variables) and converts them to a single value at compile time. For an This essentially gets us As has been pointed out, in JS/TS you can break the rules and force update a Finally, to make good use this feature, you need a conveniently way to set one or more constants at compile time without altering the code so you can script different build types that tweak these constants. This is commonly done in C/C++/C# land as a commmand line option. Example:
So this would mean the compiler would pre-declare these constants before compiling the each source file. You're likely to also want to be able to set a default value for these constants so the command line compiler options just overrides changes to these defaults. One logical place for setting these default values I think would be in |
So if you go down the compiler optimisation route ... What do you loose over a pre-processing solution? I can think of a few things. As always from me ... these are just "brainstorming ideas" not "opinions" 😉 . Firstly, you can't easily do conditional imports. Placing an We hit the same issue with conditional declarations of variables and functions inside
... is not the same as ...
... since in the 1st example our function declaration is restricted to the scope of the if/else block. A related missing feature would be macros that remove code. An example of this in C/C++ is #if DEBUG
#define assert(cond) if (!(cond)) throw new AssertException(##cond)
#else
#define assert(cond) To fix this, you'd need some kind of compiler optimisation to say ... remove calls to empty functions ... but again you've got to solve the same nested block declaration problem. So where does that take us? I think this little mental exercise suggests that without a pre-processor we're lacking the language primitive to be able to conditionally modify declarations in the current block scope and that while optimising the standard For TS, this even might mean the compiler would require all code paths in an |
Hi everyone, If anyone is interested i made Prebuilder Currently works best with rollup, but for typescript projects it would need an adapter But it can be easily hoked up to your projects manually anyway, once you take time to configure source/output folders. |
Well done @ANFADEV ! |
Use-case: catching errors before run-time. IMHO the main purpose of Typescript. Take this error in code shared between client and server builds:
At edit-time, both client and server are considered available, so this would not error. |
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. |
I agree with this comment - #449 (comment) - I want to build cross-environment packages that leverage browser, node, etc native APIs in a sane manner. An example use case is a package that runs advanced text parsing operations without pulling in a bazillion NPM packages (so: TextDecoder and TextEncoder APIs, Node's Crypto.Hash vs Web's CryptoSubtle; Fetch from We would need both
|
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? |
Switching keyboard can be mapped. You can also use safe hooks of editor. Unlikely to be related to this topic ? Maybe need be one. If it's a real pain point there are more options like auto hotkey contact me.
|
@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. |
Oh god, please not
I think people want two different things from this: Conditional methods like
|
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. |
No, |
I think one of meaningful use case is we can use it for feature flag. #if FEAT_1_ENABLED
// the feature code
#end |
In C#, I use
I use this for quick, small edits and it really helps me to keep me in the flow when developing. |
There are cases where type-driven conditional-compilation could be useful: JS 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); // ✅
} |
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.The text was updated successfully, but these errors were encountered: