{"action":"created","issue":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3538","repository_url":"https://api.github.com/repos/Microsoft/TypeScript","labels_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3538/labels{/name}","comments_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3538/comments","events_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3538/events","html_url":"https://github.com/Microsoft/TypeScript/issues/3538","id":89060191,"number":3538,"title":"Proposal: Conditional Compilation","user":{"login":"kitsonk","id":1282577,"avatar_url":"https://avatars.githubusercontent.com/u/1282577?v=3","gravatar_id":"","url":"https://api.github.com/users/kitsonk","html_url":"https://github.com/kitsonk","followers_url":"https://api.github.com/users/kitsonk/followers","following_url":"https://api.github.com/users/kitsonk/following{/other_user}","gists_url":"https://api.github.com/users/kitsonk/gists{/gist_id}","starred_url":"https://api.github.com/users/kitsonk/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kitsonk/subscriptions","organizations_url":"https://api.github.com/users/kitsonk/orgs","repos_url":"https://api.github.com/users/kitsonk/repos","events_url":"https://api.github.com/users/kitsonk/events{/privacy}","received_events_url":"https://api.github.com/users/kitsonk/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Needs%20Proposal","name":"Needs Proposal","color":"556b75"},{"url":"https://api.github.com/repos/Microsoft/TypeScript/labels/Suggestion","name":"Suggestion","color":"006b75"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":17,"created_at":"2015-06-17T16:12:32Z","updated_at":"2016-06-12T01:02:50Z","closed_at":null,"body":"# Proposal: Conditional Compilation\r\n\r\n## Problem Statement\r\n\r\nAt 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.\r\n\r\nThis is directly related to #449 but it also covers some other issues in a similar problem space.\r\n\r\n## Similar Functionality\r\n\r\nThere are several other examples of apporaches to solving this problem:\r\n\r\n* In C#, this is solved via [conditional flags](https://msdn.microsoft.com/en-us/library/aa691099(v=vs.71).aspx) as well well as [conditional symbols](https://msdn.microsoft.com/en-us/library/system.diagnostics.conditionalattribute(v=vs.90).aspx).\r\n* In Dojo, this was solved via adopting [has.js](https://github.com/phiggins42/has.js/) and static flags in the build tool that would allow build time \"dead code removal\".\r\n* IE used to support [conditional compilation](https://msdn.microsoft.com/library/121hztk3(v=vs.94).aspx) using comments.\r\n* UglifyJS accomplishes this via [assertion of constants](https://github.com/mishoo/UglifyJS2#conditional-compilation) coupled with dead code removal.\r\n\r\n## Considerations\r\n\r\nMost 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:\r\n\r\n```js\r\nhas.add('host-node', (typeof process == \"object\" && process.versions && process.versions.node && process.versions.v8));\r\n\r\nif (has('host-node')) {\r\n    /* do something node */\r\n}\r\nelse {\r\n    /* do something non-node */\r\n}\r\n```\r\n\r\nIf 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.\r\n\r\nBecause 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.\r\n\r\nAlso 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.\r\n\r\nOf 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.\r\n\r\n## Proposed Changes\r\n\r\nIn 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.\r\n\r\n### Defining a Conditional Identifier at Design Time\r\n\r\nIt 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.\r\n\r\nOf 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.\r\n\r\nFor example, in TypeScript the following would be a way of declaring a condition:\r\n\r\n```typescript\r\nhas hostNode: boolean = Boolean(typeof process == \"object\" && process.versions && process.versions.node && process.versions.v8);\r\n\r\nif (hostNode) {\r\n    console.log('You are running under node.');\r\n}\r\nelse {\r\n    console.log('You are not running under node.');\r\n}\r\n```\r\n\r\nThis would then emit, assuming there is no compile time substitutional value available (and targeting ES6) as:\r\n\r\n```javascript\r\nconst hostNode = Boolean(typeof process == \"object\" && process.versions && process.versions.node && process.versions.v8);\r\nif (hostNode) {\r\n    console.log('You are running under node.');\r\n}\r\nelse {\r\n    console.log('You are not running under node.');\r\n}\r\n```\r\n\r\n### Defining the value of a Conditional Identifier at Compile Time\r\n\r\nIn 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.\r\n\r\nHere is an example of `tsconfig.json`:\r\n\r\n```json\r\n{\r\n    \"version\": \"1.6.0\",\r\n    \"compilerOptions\": {\r\n        \"target\": \"es5\",\r\n        \"module\": \"umd\",\r\n        \"declaration\": false,\r\n        \"noImplicitAny\": true,\r\n        \"removeComments\": true,\r\n        \"noLib\": false,\r\n        \"sourceMap\": true,\r\n        \"outDir\": \"./\"\r\n    },\r\n    \"hasValues\": {\r\n        \"hostNode\": true\r\n    }\r\n}\r\n```\r\n\r\n### Compiled Code\r\n\r\nSo given the `tsconfig.json` above and the following TypeScript:\r\n\r\n```typescript\r\nhas hostNode: boolean = Boolean(typeof process == \"object\" && process.versions && process.versions.node && process.versions.v8);\r\n\r\nif (hostNode) {\r\n    console.log('You are running under node.');\r\n}\r\nelse {\r\n    console.log('You are not running under node.');\r\n}\r\n```\r\n\r\nYou would expect the following to be emitted:\r\n\r\n```js\r\nconsole.log('You are running under node.');\r\n```\r\n\r\nAs 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.\r\n"},"comment":{"url":"https://api.github.com/repos/Microsoft/TypeScript/issues/comments/225403164","html_url":"https://github.com/Microsoft/TypeScript/issues/3538#issuecomment-225403164","issue_url":"https://api.github.com/repos/Microsoft/TypeScript/issues/3538","id":225403164,"user":{"login":"igabesz","id":3373181,"avatar_url":"https://avatars.githubusercontent.com/u/3373181?v=3","gravatar_id":"","url":"https://api.github.com/users/igabesz","html_url":"https://github.com/igabesz","followers_url":"https://api.github.com/users/igabesz/followers","following_url":"https://api.github.com/users/igabesz/following{/other_user}","gists_url":"https://api.github.com/users/igabesz/gists{/gist_id}","starred_url":"https://api.github.com/users/igabesz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/igabesz/subscriptions","organizations_url":"https://api.github.com/users/igabesz/orgs","repos_url":"https://api.github.com/users/igabesz/repos","events_url":"https://api.github.com/users/igabesz/events{/privacy}","received_events_url":"https://api.github.com/users/igabesz/received_events","type":"User","site_admin":false},"created_at":"2016-06-12T01:02:50Z","updated_at":"2016-06-12T01:02:50Z","body":"Some background: C/C++ and C# support preprocessors, Java doesn't. However, one can use the C++ preprocessor system in Java, see [this post](https://stackoverflow.com/questions/6525059/can-i-have-macros-in-java-source-files#6525113). \r\n\r\nI was somewhat curious about this feature in TS and I think there are valid cases where preprocessors could be used without abusing the TS/JS dev and build process. But I'm not convinced that this should be done *during* the TS compilation. Gulp offers some preprocessing right now, see [preprocess](https://www.npmjs.com/package/preprocess) or [gulp-preprocess](https://www.npmjs.com/package/gulp-preprocess) or Webpack's similar feature mentioned by @cevek. \r\n\r\nHere is a simple [experimenting of mine](https://github.com/igabesz/typescript-preprocessor) to check out gulp-preprocess with TS. (Don't expect much.) This preprocessor seems to be kinda useful, not with all of the C++ preprocessor features though. \r\n\r\nCurrently I see these major weaknesses of external preprocessors. \r\n\r\n- They can lead to invalid TS code before preprocessing. This is a real pain, however, in most cases this can be solved, avoided or workarounded. \r\n- The TS compiler won't see the difference caused by them. Changing the interface based on build options seems to indicate bad architecture. (Or at least something being very far from the JS/TS world.)\r\n- Need to use an external build system (e.g. Gulp). But if preprocessors are required then there already should be a fairly complex build system. \r\n\r\nSo I think that using preprocessors in a TS environment is a very special requirement (i.e. not general) that can be solved by using already existing tools. I recommend reconsidering this feature after at least a half or one year. There are more important and way more general feature requests now. "}}
{"actor":{"display_login":"igabesz"}}