├── .editorconfig ├── .ember-cli ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── README.md ├── addon └── .gitkeep ├── app ├── .gitkeep └── initializers │ └── ember-cli-conditional-compile-features.js ├── config ├── ember-try.js ├── environment.js └── feature-flags.js ├── ember-cli-build.js ├── index.js ├── lib └── template-compiler.js ├── node-tests ├── addon-test.js └── with-extra-config-file │ └── config │ └── feature-flags.js ├── package.json ├── testem.js ├── tests ├── .jshintrc ├── acceptance │ └── application-test.js ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ └── application.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ └── .gitkeep │ ├── config │ │ ├── ember-cli-update.json │ │ ├── environment.js │ │ ├── feature-flags.js │ │ └── optional-features.json │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── index.js │ └── resolver.js ├── index.html ├── integration │ └── components │ │ └── compiler-test.js ├── test-helper.js └── unit │ ├── .gitkeep │ └── controllers │ └── application-test.js ├── vendor └── .gitkeep └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.hbs] 21 | insert_final_newline = false 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.css] 26 | indent_style = space 27 | indent_size = 2 28 | 29 | [*.html] 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.{diff,md}] 34 | trim_trailing_whitespace = false 35 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | sourceType: 'module' 6 | }, 7 | extends: [ 8 | 'eslint:recommended' 9 | ], 10 | env: { 11 | 'browser': false, 12 | 'es6': true, 13 | 'node': true 14 | }, 15 | globals: { 16 | 'window': true, 17 | 'EMBER_CLI_CONDITIONAL_COMPILE_INJECTIONS': true, 18 | 'ENABLE_FOO': true, 19 | 'ENABLE_BAR': true, 20 | 'visit': true, 21 | 'andThen': true, 22 | 'find': true 23 | }, 24 | rules: { 25 | 'array-bracket-spacing': ['error', 'never'], 26 | 'arrow-spacing': ['error'], 27 | 'block-spacing': ['error'], 28 | 'brace-style': ['error', '1tbs'], 29 | 'comma-dangle': ['error', 'never'], 30 | 'comma-style': ['error'], 31 | 'eqeqeq': ['error'], 32 | 'func-call-spacing': ['error', 'never'], 33 | 'indent': ['error', 2, { 34 | 'ArrayExpression': 1, 35 | 'CallExpression': { 'arguments': 1 }, 36 | 'ObjectExpression': 1, 37 | 'SwitchCase': 1, 38 | 'VariableDeclarator': 1 39 | }], 40 | 'key-spacing': ['error'], 41 | 'keyword-spacing': ['error'], 42 | 'linebreak-style': ['error'], 43 | 'no-confusing-arrow': ['error'], 44 | 'no-trailing-spaces': ['error'], 45 | 'no-var': ['error'], 46 | 'object-curly-spacing': ['error', 'always'], 47 | 'one-var-declaration-per-line': ['error'], 48 | 'semi-spacing': ['error'], 49 | 'space-before-blocks': ['error', 'always'], 50 | 'space-before-function-paren': ['error', 'never'], 51 | 'space-in-parens': ['error', 'never'], 52 | 'spaced-comment': ['error', 'always', { 53 | 'block': { 'balanced': true } 54 | }] 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | name: "Tests" 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Install Node 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: 14.x 25 | cache: 'yarn' 26 | - name: Install Dependencies 27 | run: yarn install --frozen-lockfile 28 | - name: Lint 29 | run: yarn lint 30 | - name: Run Tests 31 | run: yarn test 32 | 33 | floating: 34 | name: "Floating Dependencies" 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | - uses: actions/setup-node@v3 40 | with: 41 | node-version: 14.x 42 | cache: yarn 43 | - name: Install Dependencies 44 | run: yarn install --no-lockfile 45 | - name: Run Tests 46 | run: yarn test 47 | 48 | try-scenarios: 49 | name: ${{ matrix.try-scenario }} 50 | runs-on: ubuntu-latest 51 | needs: "test" 52 | 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | try-scenario: 57 | - ember-lts-3.16 58 | - ember-lts-3.20 59 | - ember-lts-3.24 60 | - ember-lts-3.28 61 | - ember-release 62 | - ember-beta 63 | - ember-canary 64 | - ember-classic 65 | # - embroider-safe 66 | # - embroider-optimized 67 | 68 | steps: 69 | - uses: actions/checkout@v3 70 | - name: Install Node 71 | uses: actions/setup-node@v3 72 | with: 73 | node-version: 14.x 74 | cache: yarn 75 | - name: Install Dependencies 76 | run: yarn install --frozen-lockfile 77 | - name: Run Tests 78 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }} 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | 10 | # misc 11 | /.sass-cache 12 | /connect.lock 13 | /coverage/* 14 | /libpeerconnection.log 15 | /npm-debug.log* 16 | /testem.log 17 | /yarn-error.log 18 | .tool-versions 19 | 20 | # ember-try 21 | /.node_modules.ember-try/ 22 | /bower.json.ember-try 23 | /npm-shrinkwrap.json.ember-try 24 | /package.json.ember-try 25 | /package-lock.json.ember-try 26 | /yarn.lock.ember-try 27 | 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | tests/ 3 | node-tests/ 4 | tmp/ 5 | dist/ 6 | config/ember-try.js 7 | 8 | Makefile 9 | .bowerrc 10 | .editorconfig 11 | .eslintrc.js 12 | .watchmanconfig 13 | .ember-cli 14 | .travis.yml 15 | .npmignore 16 | **/.gitkeep 17 | bower.json 18 | ember-cli-build.js 19 | Brocfile.js 20 | testem.js 21 | README.md 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "10" 5 | 6 | sudo: false 7 | cache: yarn 8 | 9 | matrix: 10 | fast_finish: true 11 | 12 | before_install: 13 | - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH 14 | 15 | install: 16 | - yarn install 17 | 18 | script: 19 | - yarn test:node && ember try:each 20 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 4 | 5 | ### Breaking changes 6 | 7 | The semantics of `includeDirByFlag` have changed and now take a file glob pattern instead of a RegExp. This is because the mechanism for removing files from the build pipeline has changed to work around a bug in broccoli. (#111) 8 | 9 | ### New features 10 | 11 | - Support for external config file to prevent leaking of feature flag names (#110) 12 | - Support for multiple environments (For example to roll out features on a staging environment) (#110) 13 | 14 | ### Updates/Changes 15 | 16 | - Modernised the code and all dependencies to be compatible with Ember 3.x and up (#107) 17 | - Added @ember/string to be compatible with Ember 5.x (#117) 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-cli-conditional-compile` 7 | * `yarn install` 8 | 9 | ## Linting 10 | 11 | * `yarn lint` 12 | * `yarn lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `ember test` – Runs the test suite on the current Ember version 17 | * `ember test --server` – Runs the test suite in "watch mode" 18 | * `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | * `ember serve` 23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REMOTE=origin 2 | 3 | # Utility target for checking required parameters 4 | guard-%: 5 | @if [ "$($*)" = '' ]; then \ 6 | echo "Missing required $* variable."; \ 7 | exit 1; \ 8 | fi; 9 | 10 | release: guard-VERSION 11 | @if [ "$$(git rev-parse --abbrev-ref HEAD)" != "master" ]; then \ 12 | echo "You must be on master to update the version"; \ 13 | exit 1; \ 14 | fi; 15 | sed -i "" -e 's/"version": ".*/"version": "$(VERSION)",/' package.json 16 | 17 | git add ./package.json 18 | git commit ./package.json -m 'Bump version to $(VERSION)' 19 | git tag release/$(VERSION) -m 'ember-cli-conditional-compile $(VERSION) - $(DATE)' 20 | git push $(REMOTE) --tags 21 | git push $(REMOTE) master 22 | npm publish 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-cli-conditional-compile 2 | 3 | ![Build Status](https://github.com/minichate/ember-cli-conditional-compile/actions/workflows/ci.yml/badge.svg) 4 | 5 | The goal of ember-cli-conditional-compile is to provide easy to use feature switches to Ember applications such that code that is hidden behind a disabled feature switch is not in the compiled code. 6 | 7 | # Getting Started 8 | 9 | This is an ember-cli addon, so all you need to do is 10 | 11 | ```bash 12 | ember install ember-cli-conditional-compile 13 | ``` 14 | 15 | # Compatibility 16 | 17 | Version 2.x targets Ember 3.x and beyond. It is tested against the latest ember release and the latest beta release (See [config/ember-try.js](config/ember-try.js) for the testing matrix) 18 | 19 | # Upgrading to 2.x 20 | 21 | Our workaround around a broccoli pipeline bug in newer versions of ember-cli slightly changes the semantics of `includeDirByFlag`. Instead of a RegExp, you now need to specify a Glob-style pattern. 22 | 23 | # Usage 24 | 25 | To actually use the feature switches you'll need to add some configuration in your `environment.js` file. For example, lets pretend you want to have two feature switches; `ENABLE_FOO` and `ENABLE_BAR`: 26 | 27 | ```javascript 28 | var ENV = { 29 | // other settings ... 30 | 31 | featureFlags: { 32 | ENABLE_FOO: true, 33 | ENABLE_BAR: true, 34 | }, 35 | includeDirByFlag: { 36 | ENABLE_FOO: ["pods/foos/**", "pods/foo/**"], 37 | ENABLE_BAR: [], 38 | }, 39 | }; 40 | 41 | // other environments ... 42 | 43 | if (environment === "production") { 44 | ENV.featureFlags.ENABLE_FOO = false; 45 | } 46 | ``` 47 | 48 | Alternatively, you can define your feature flags in `config/feature-flags.js` looking like this: 49 | 50 | ```javascript 51 | module.exports = function (environment) { 52 | const GLOBAL_FLAGS = { 53 | featureFlags: { 54 | ENABLE_FOO: true, 55 | ENABLE_BAR: true, 56 | }, 57 | includeDirByFlag: { 58 | ENABLE_FOO: [/pods\/foos/, /pods\/foo/], 59 | ENABLE_BAR: [], 60 | }, 61 | }; 62 | 63 | if (environment === "production") { 64 | GLOBAL_FLAGS.featureFlags.ENABLE_FOO = false; 65 | } 66 | 67 | return GLOBAL_FLAGS; 68 | }; 69 | ``` 70 | 71 | This has two advantages: It declutters `environment.js` a bit, especially if you have many flags, but also prevents your flag names from leaking into the application code under certain circumstances. 72 | 73 | We'll look at the two new options in more detail below, but for now we can see that by default both features are enabled, but in the `production` environment `ENABLE_FOO` is disabled, and related code under the `pods/foos` and `pods/foo` directories are excluded from compilation. 74 | 75 | ## ENV.featureFlags 76 | 77 | This setting sets up which flags will be available to actually switch use. A value of `true` means that the flag will be enabled, `false` means that it will not be. 78 | 79 | ## ENV.includeDirByFlag 80 | 81 | Given a key which has been defined above, the value is an array of regexes of files/paths which will _only_ be included in the compiled product if the related feature flag is enabled. In the example above, in the development environment `ENABLE_FOO` is `true`, so the `pods/foo` and `pods/foos` paths will be included. 82 | 83 | However, since the flag is `false` in production, any code in those directories will not be compiled in. 84 | 85 | # How it works 86 | 87 | _ember-cli-conditional-compile_ adds itself to the Broccoli compile pipeline for your Ember application. Depending on which environment you're building it acts in two different ways: 88 | 89 | ## Development and test environments 90 | 91 | Global variables are injected into the page which have the current state of the feature flags. For example: 92 | 93 | ```javascript 94 | if (ENABLE_FOO) { 95 | this.route("foo"); 96 | console.log("The feature ENABLE_FOO is enabled in this environment"); 97 | } 98 | ``` 99 | 100 | will be represented in development and test environments as: 101 | 102 | ```javascript 103 | window.ENABLE_FOO = true; 104 | 105 | if (ENABLE_FOO) { 106 | this.route("foo"); 107 | console.log("The feature ENABLE_FOO is enabled in this environment"); 108 | } 109 | ``` 110 | 111 | In Handlebars/HTMLBars templates, you can also make use of the flags using the `if-flag` block helper: 112 | 113 | ```hbs 114 | {{#if-flag ENABLE_FOO}} 115 |

Foo is enabled! \o/

116 | {{else}} 117 |

Foo is disabled

118 | {{/if-flag}} 119 | ``` 120 | 121 | You can also use the `unless-flag` style block helper: 122 | 123 | ```hbs 124 | {{#unless-flag ENABLE_FOO}} 125 |

Foo is disabled

126 | {{else}} 127 |

Foo is enabled! \o/

128 | {{/unless-flag}} 129 | ``` 130 | 131 | ## Production environment 132 | 133 | We use UglifyJS's `global_defs` feature to replace the value of feature flags with their constant values. UglifyJS's dead code implementation then cleans up unreachable code and performs inlining, such that: 134 | 135 | ```javascript 136 | if (ENABLE_FOO) { 137 | this.route("foo"); 138 | console.log("The feature ENABLE_FOO is enabled in this environment"); 139 | } 140 | ``` 141 | 142 | will be represented in the production environment as the following if `ENABLE_FOO` is configured to be `true`: 143 | 144 | ```javascript 145 | this.route("foo"); 146 | console.log("The feature ENABLE_FOO is enabled in this environment"); 147 | ``` 148 | 149 | or the following if `ENABLE_FOO` is configured to be `false`; 150 | 151 | ```javascript 152 | // empty since the condition can never be satisfied! 153 | ``` 154 | 155 | Furthermore, if you use the HTMLBars helpers the AST transformations will shake 156 | out and remove impossible-to-reach sides of the condition: 157 | 158 | ```hbs 159 | {{#if-flag ENABLE_FOO}} 160 |

Foo is enabled

161 | {{else}} 162 |

This won't be reached, because ENABLE_FOO is true

163 | {{/if-flag}} 164 | ``` 165 | 166 | will get transformed into: 167 | 168 | ```hbs 169 |

Foo is enabled

170 | ``` 171 | 172 | This is really handy, since it vastly cuts down on the amount of precompiled 173 | template code that your users need to download even though it'll never be 174 | executed! 175 | 176 | ## Defining additional environments 177 | 178 | By defining `ENV.featureFlagsEnvironment` you can separate your feature flags by more than just test/development/production, for example to have a beta environment that is identical to production but has a couple more flags activated. This only works if you have your flags in `config.featureFlags` - The `environment` passed in into the wrapper function will be `ENV.featureFlagsEnvironment` if set. 179 | 180 | # Licence 181 | 182 | This library is lovingly brought to you by the FreshBooks developers. We've released it under the MIT license. 183 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/addon/.gitkeep -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/app/.gitkeep -------------------------------------------------------------------------------- /app/initializers/ember-cli-conditional-compile-features.js: -------------------------------------------------------------------------------- 1 | 2 | const initializer = { 3 | name: 'ember-cli-conditional-compile-features', 4 | initialize: function() {} 5 | }; 6 | 7 | const feature_flags = EMBER_CLI_CONDITIONAL_COMPILE_INJECTIONS; 8 | Object.keys(feature_flags).map(function(flag) { 9 | window[flag] = feature_flags[flag]; 10 | }) 11 | 12 | export default initializer; 13 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | const getChannelURL = require('ember-source-channel-url'); 2 | 3 | module.exports = async function() { 4 | return { 5 | useYarn: true, 6 | scenarios: [ 7 | { 8 | name: 'ember-lts-3.16', 9 | npm: { 10 | devDependencies: { 11 | 'ember-source': '~3.16.10' 12 | } 13 | } 14 | }, 15 | { 16 | name: 'ember-lts-3.20', 17 | npm: { 18 | devDependencies: { 19 | 'ember-source': '~3.20.7' 20 | } 21 | } 22 | }, 23 | { 24 | name: 'ember-lts-3.24', 25 | npm: { 26 | devDependencies: { 27 | 'ember-source': '~3.24.3' 28 | } 29 | } 30 | }, 31 | { 32 | name: 'ember-lts-3.28', 33 | npm: { 34 | devDependencies: { 35 | 'ember-source': '~3.28.8' 36 | } 37 | } 38 | }, 39 | { 40 | name: 'ember-release', 41 | npm: { 42 | devDependencies: { 43 | 'ember-source': await getChannelURL('release') 44 | } 45 | } 46 | }, 47 | { 48 | name: 'ember-beta', 49 | npm: { 50 | devDependencies: { 51 | 'ember-source': await getChannelURL('beta') 52 | } 53 | } 54 | }, 55 | { 56 | name: 'ember-canary', 57 | npm: { 58 | devDependencies: { 59 | 'ember-source': await getChannelURL('canary') 60 | } 61 | } 62 | }, 63 | { 64 | name: 'ember-classic', 65 | env: { 66 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 67 | 'application-template-wrapper': true, 68 | 'default-async-observers': false, 69 | 'template-only-glimmer-components': false 70 | }) 71 | }, 72 | npm: { 73 | devDependencies: { 74 | 'ember-source': '~3.28.8' 75 | }, 76 | ember: { 77 | edition: 'classic' 78 | } 79 | } 80 | } 81 | ] 82 | }; 83 | }; 84 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /config/feature-flags.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | 3 | const GLOBAL_FLAGS = { 4 | featureFlags: { 5 | ENABLE_FOO: true, 6 | ENABLE_BAR: false 7 | }, 8 | includeDirByFlag: { 9 | ENABLE_FOO: [] 10 | } 11 | } 12 | return GLOBAL_FLAGS 13 | } -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | const EmberApp = require('ember-cli/lib/broccoli/ember-addon'); 3 | 4 | module.exports = function(defaults) { 5 | const app = new EmberApp(defaults, {}); 6 | 7 | return app.toTree(); 8 | }; 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const EmberApp = require("ember-cli/lib/broccoli/ember-app"); 2 | const merge = require("lodash.merge"); 3 | const replace = require("broccoli-replace"); 4 | const chalk = require("chalk"); 5 | const VersionChecker = require("ember-cli-version-checker"); 6 | const TemplateCompiler = require("./lib/template-compiler"); 7 | const hash = require("object-hash"); 8 | const fs = require("fs"); 9 | const path = require("path"); 10 | 11 | module.exports = { 12 | name: "ember-cli-conditional-compile", 13 | enableCompile: false, 14 | 15 | init: function () { 16 | this._super.init && this._super.init.apply(this, arguments); 17 | 18 | const checker = new VersionChecker(this); 19 | checker.for("ember-source").assertAbove("2.9.0"); 20 | 21 | this.htmlbarsVersion = checker.for("ember-cli-htmlbars", "npm"); 22 | this.uglifyVersion = checker.for("ember-cli-uglify", "npm"); 23 | this.terserVersion = checker.for("ember-cli-terser", "npm"); 24 | }, 25 | 26 | included: function (app, parentAddon) { 27 | this.readConfig(); 28 | 29 | const target = parentAddon || app; 30 | 31 | let options = { 32 | options: { 33 | compress: { 34 | global_defs: this._config.featureFlags, 35 | }, 36 | }, 37 | }; 38 | 39 | if (this.terserVersion.exists()) { 40 | target.options = merge(target.options, { 41 | "ember-cli-terser": { terser: options.options }, 42 | }); 43 | this.enableCompile = target.options["ember-cli-terser"].enabled; 44 | } else if (this.uglifyVersion.satisfies(">= 2.0.0")) { 45 | target.options = merge(target.options, { 46 | "ember-cli-uglify": { uglify: options.options }, 47 | }); 48 | this.enableCompile = target.options["ember-cli-uglify"].enabled; 49 | } else { 50 | target.options.minifyJS = merge(target.options.minifyJS, options); 51 | this.enableCompile = target.options.minifyJS.enabled; 52 | } 53 | 54 | const templateCompilerInstance = { 55 | name: "conditional-compile-template", 56 | plugin: TemplateCompiler(this._config.featureFlags), 57 | }; 58 | 59 | if (this.htmlbarsVersion.satisfies(">= 1.3.0")) { 60 | templateCompilerInstance["baseDir"] = function () { 61 | return __dirname; 62 | }; 63 | 64 | const featureFlags = this._config.featureFlags; 65 | 66 | templateCompilerInstance["cacheKey"] = function () { 67 | return hash(featureFlags); 68 | }; 69 | } else { 70 | console.log( 71 | chalk.yellow( 72 | "Upgrade to ember-cli-htmlbars >= 1.3.0 to get build caching" 73 | ) 74 | ); 75 | } 76 | 77 | target.registry.add("htmlbars-ast-plugin", templateCompilerInstance); 78 | }, 79 | 80 | readConfig() { 81 | const root = this.project.root; 82 | const config = this.project.config(EmberApp.env()); 83 | const flagsEnv = config.featureFlagsEnvironment || EmberApp.env(); 84 | 85 | let configFactory = path.join(root, "config", "feature-flags.js"); 86 | 87 | if (fs.existsSync(configFactory)) { 88 | this._config = Object.assign({}, require(configFactory)(flagsEnv)); 89 | } else { 90 | // try the app environment as a fallback 91 | const envFeatureFlags = config["featureFlags"] || {}; 92 | const envIncludeDirByFlag = config["includeDirByFlag"] || {}; 93 | this._config = { 94 | featureFlags: envFeatureFlags, 95 | includeDirByFlag: envIncludeDirByFlag, 96 | }; 97 | } 98 | }, 99 | 100 | setupPreprocessorRegistry: function (type, registry) { 101 | registry.add("js", { 102 | name: "ember-cli-conditional-compile", 103 | ext: "js", 104 | toTree: (tree) => this.transpileTree(tree), 105 | }); 106 | }, 107 | 108 | /** 109 | * Inline feature flags value so that babili's dead code elimintation plugin 110 | * removes the code non reachable. 111 | */ 112 | transpileTree(tree) { 113 | const esTranspiler = require("broccoli-babel-transpiler"); 114 | const inlineFeatureFlags = require("babel-plugin-inline-replace-variables"); 115 | if (!this.enableCompile) { 116 | return tree; 117 | } 118 | return esTranspiler(tree, { 119 | plugins: [[inlineFeatureFlags, this._config.featureFlags]], 120 | }); 121 | }, 122 | 123 | postprocessTree: function (type, tree) { 124 | if (type !== "js") return tree; 125 | 126 | let config = this.project.config(EmberApp.env()); 127 | 128 | if (!this._config.featureFlags) { 129 | console.log( 130 | chalk.red( 131 | "Could not find any feature flags." + 132 | "You may need to add them in your config/environment.js" 133 | ) 134 | ); 135 | return tree; 136 | } 137 | 138 | let excludes = []; 139 | 140 | if (this._config.featureFlags) { 141 | Object.keys(this._config.featureFlags).map(function (flag) { 142 | if ( 143 | this._config.includeDirByFlag && 144 | !this._config.featureFlags[flag] && 145 | this._config.includeDirByFlag[flag] 146 | ) { 147 | const flaggedExcludes = this._config.includeDirByFlag[flag].map( 148 | function (glob) { 149 | return config.modulePrefix + "/" + glob; 150 | } 151 | ); 152 | excludes = excludes.concat(flaggedExcludes); 153 | } 154 | }, this); 155 | } 156 | 157 | if (this.enableCompile) { 158 | tree = replace(tree, { 159 | files: [ 160 | config.modulePrefix + 161 | "/initializers/ember-cli-conditional-compile-features.js", 162 | ], 163 | patterns: [ 164 | { 165 | match: /EMBER_CLI_CONDITIONAL_COMPILE_INJECTIONS/g, 166 | replacement: "{}", 167 | }, 168 | ], 169 | }); 170 | } else { 171 | tree = replace(tree, { 172 | files: [ 173 | config.modulePrefix + 174 | "/initializers/ember-cli-conditional-compile-features.js", 175 | ], 176 | patterns: [ 177 | { 178 | match: /EMBER_CLI_CONDITIONAL_COMPILE_INJECTIONS/g, 179 | replacement: JSON.stringify(this._config.featureFlags || {}), 180 | }, 181 | ], 182 | }); 183 | } 184 | 185 | return replace(tree, { 186 | files: excludes, 187 | patterns: [ 188 | { 189 | match: /.*/g, 190 | replacement: "/**/", 191 | }, 192 | ], 193 | }); 194 | }, 195 | }; 196 | -------------------------------------------------------------------------------- /lib/template-compiler.js: -------------------------------------------------------------------------------- 1 | let compileFlags = null; 2 | let tEnv = null 3 | 4 | function transform(ast) { 5 | let walker = new tEnv.syntax.Walker(); 6 | 7 | walker.visit(ast, function(node) { 8 | if (!validate(node)) { 9 | return; 10 | } 11 | 12 | processParams(node); 13 | }); 14 | 15 | // return ast; 16 | } 17 | 18 | 19 | function ConditionalTemplateCompiler(env) { 20 | tEnv = env; 21 | return { 22 | name: 'condititional-compiler', 23 | visitor: { 24 | Program(ast) { 25 | return transform(ast); 26 | } 27 | } 28 | } 29 | } 30 | 31 | function mungeNode(node, flag, unless) { 32 | let b = tEnv.syntax.builders; 33 | 34 | if (!unless) { 35 | node.path = b.path('if'); 36 | } else { 37 | node.path = b.path('unless'); 38 | } 39 | 40 | let flagEnabled = compileFlags[flag]; 41 | 42 | node.params = [b.boolean(flagEnabled)]; 43 | 44 | if (unless) { 45 | flagEnabled = !flagEnabled; 46 | } 47 | 48 | if (flagEnabled && node.inverse) { 49 | node.inverse = b.program(false, false, node.inverse.loc); 50 | } else if (!flagEnabled && node.program) { 51 | node.program = b.program(false, false, node.program.loc); 52 | } 53 | } 54 | 55 | function mungePlainHelperNode(node, unless) { 56 | let compileFlag = node.params[0]['original']; 57 | 58 | if (!(compileFlag in compileFlags)) { 59 | throw 'No compile flag found for ' + compileFlag; 60 | } 61 | 62 | mungeNode(node, compileFlag, unless); 63 | } 64 | 65 | function processParams(node) { 66 | for (let flag in compileFlags) { 67 | if (node.path.original === 'if-flag-' + flag) { 68 | mungeNode(node, flag, false); 69 | } 70 | 71 | if (node.path.original === 'unless-flag-' + flag) { 72 | mungeNode(node, flag, true); 73 | } 74 | } 75 | 76 | if (node.path.original === 'if-flag') { 77 | mungePlainHelperNode(node, false); 78 | } 79 | 80 | if (node.path.original === 'unless-flag') { 81 | mungePlainHelperNode(node, true); 82 | } 83 | } 84 | 85 | function validate(node) { 86 | let nodeType = (node.type === 'BlockStatement' || node.type === 'MustacheStatement'); 87 | 88 | if (!nodeType) { 89 | return false; 90 | } 91 | 92 | return ( 93 | node.path && ( 94 | node.path.original.startsWith('if-flag') || 95 | node.path.original.startsWith('unless-flag') 96 | ) 97 | ); 98 | } 99 | 100 | module.exports = function(flags) { 101 | compileFlags = flags; 102 | return ConditionalTemplateCompiler; 103 | }; -------------------------------------------------------------------------------- /node-tests/addon-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const co = require('co'); 4 | const expect = require('chai').expect; 5 | const CoreObject = require('core-object'); 6 | const path = require('path'); 7 | const AddonMixin = require('../index'); 8 | const BroccoliTestHelper = require('broccoli-test-helper'); 9 | const CommonTags = require('common-tags'); 10 | const stripIndent = CommonTags.stripIndent; 11 | const createBuilder = BroccoliTestHelper.createBuilder; 12 | const createTempDir = BroccoliTestHelper.createTempDir; 13 | 14 | let Addon = CoreObject.extend(AddonMixin); 15 | 16 | describe('ember-cli-conditional-compile', function() { 17 | 18 | describe('transpileTree', function() { 19 | this.timeout(0); 20 | 21 | let input; 22 | let output; 23 | let subject; 24 | 25 | beforeEach(co.wrap(function* () { 26 | input = yield createTempDir(); 27 | 28 | let project = { 29 | root: __dirname, 30 | config: function() { 31 | return { 32 | featureFlags: { 33 | ENABLE_FOO: true 34 | } 35 | } 36 | } 37 | }; 38 | this.addon = new Addon({ 39 | project, 40 | parent: project 41 | }); 42 | this.addon.readConfig(); 43 | })); 44 | 45 | afterEach(co.wrap(function* () { 46 | yield input.dispose(); 47 | yield output.dispose(); 48 | })); 49 | 50 | describe('in developemt', function() { 51 | it("keeps the flag in development", co.wrap(function* () { 52 | process.env.EMBER_ENV = 'development'; 53 | 54 | let contents = stripIndent` 55 | if (ENABLE_FOO) { 56 | console.log('Feature Mode!'); 57 | } 58 | `; 59 | 60 | input.write({ 61 | "foo.js": contents 62 | }); 63 | 64 | subject = this.addon.transpileTree(input.path()); 65 | 66 | output = createBuilder(subject); 67 | 68 | yield output.build(); 69 | 70 | expect( 71 | output.read() 72 | ).to.deep.equal({ 73 | "foo.js": `if (ENABLE_FOO) {\n console.log('Feature Mode!');\n}` 74 | }); 75 | })); 76 | }); 77 | 78 | describe('when minification is enabled', function() { 79 | it("inlines the feature flags value", co.wrap(function* () { 80 | process.env.EMBER_ENV = 'development'; 81 | 82 | let contents = stripIndent` 83 | if (ENABLE_FOO) { 84 | console.log('Feature Mode!'); 85 | } 86 | `; 87 | 88 | input.write({ 89 | "foo.js": contents 90 | }); 91 | this.addon.enableCompile = true; 92 | subject = this.addon.transpileTree(input.path()); 93 | 94 | output = createBuilder(subject); 95 | 96 | yield output.build(); 97 | 98 | expect( 99 | output.read() 100 | ).to.deep.equal({ 101 | "foo.js": `if (true) {\n console.log('Feature Mode!');\n}` 102 | }); 103 | })); 104 | }); 105 | }); 106 | 107 | describe('with extra config file', function() { 108 | 109 | this.timeout(0); 110 | 111 | let input; 112 | let output; 113 | let subject; 114 | 115 | 116 | beforeEach(co.wrap(function* () { 117 | input = yield createTempDir(); 118 | 119 | let project = { 120 | root: path.join(__dirname, 'with-extra-config-file'), 121 | config: function() { 122 | return {} 123 | } 124 | }; 125 | this.addon = new Addon({ 126 | project, 127 | parent: project 128 | }); 129 | this.addon.readConfig(); 130 | })); 131 | 132 | afterEach(co.wrap(function* () { 133 | yield input.dispose(); 134 | yield output.dispose(); 135 | })); 136 | 137 | describe('when minification is enabled', function() { 138 | it("inlines the feature flags value", co.wrap(function* () { 139 | process.env.EMBER_ENV = 'development'; 140 | 141 | let contents = stripIndent` 142 | if (ENABLE_FROM_FILE) { 143 | console.log('Feature Mode!'); 144 | } 145 | `; 146 | let clownContents = stripIndent` 147 | if (ENABLE_FROM_FILE_CLOWNSTAGE) { 148 | console.log('Feature Mode!'); 149 | } 150 | `; 151 | 152 | input.write({ 153 | "foo.js": contents, 154 | 'clown.js': clownContents, 155 | }); 156 | 157 | this.addon.enableCompile = true; 158 | subject = this.addon.transpileTree(input.path()); 159 | 160 | output = createBuilder(subject); 161 | 162 | yield output.build(); 163 | 164 | expect( 165 | output.read() 166 | ).to.deep.equal({ 167 | "foo.js": `if (true) {\n console.log('Feature Mode!');\n}`, 168 | "clown.js": `if (false) {\n console.log('Feature Mode!');\n}` 169 | }); 170 | })); 171 | 172 | it("respects additional environments", co.wrap(function* () { 173 | process.env.EMBER_ENV = 'development'; 174 | 175 | let project = { 176 | root: path.join(__dirname, 'with-extra-config-file'), 177 | config: function() { 178 | return { 179 | featureFlagsEnvironment: 'clownstage' 180 | } 181 | } 182 | }; 183 | this.addon = new Addon({ 184 | project, 185 | parent: project 186 | }); 187 | this.addon.readConfig(); 188 | 189 | let contents = stripIndent` 190 | if (ENABLE_FROM_FILE) { 191 | console.log('Feature Mode!'); 192 | } 193 | `; 194 | let clownContents = stripIndent` 195 | if (ENABLE_FROM_FILE_CLOWNSTAGE) { 196 | console.log('Feature Mode!'); 197 | } 198 | `; 199 | 200 | input.write({ 201 | "foo.js": contents, 202 | 'clown.js': clownContents, 203 | }); 204 | 205 | this.addon.enableCompile = true; 206 | subject = this.addon.transpileTree(input.path()); 207 | 208 | output = createBuilder(subject); 209 | 210 | yield output.build(); 211 | 212 | expect( 213 | output.read() 214 | ).to.deep.equal({ 215 | "foo.js": `if (true) {\n console.log('Feature Mode!');\n}`, 216 | "clown.js": `if (true) {\n console.log('Feature Mode!');\n}` 217 | }); 218 | })); 219 | }); 220 | }) 221 | }); 222 | -------------------------------------------------------------------------------- /node-tests/with-extra-config-file/config/feature-flags.js: -------------------------------------------------------------------------------- 1 | module.exports = function(environment) { 2 | 3 | const GLOBAL_FLAGS = { 4 | featureFlags: { 5 | ENABLE_FROM_FILE: true, 6 | ENABLE_FROM_FILE_CLOWNSTAGE: false, 7 | }, 8 | includeDirByFlag: { 9 | ENABLE_FROM_FILE: [], 10 | } 11 | } 12 | if (environment === 'clownstage') { 13 | GLOBAL_FLAGS.featureFlags.ENABLE_FROM_FILE_CLOWNSTAGE = true; 14 | } 15 | 16 | return GLOBAL_FLAGS 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-conditional-compile", 3 | "version": "2.0.0", 4 | "description": "Conditional compilation (feature-flags) for Ember apps", 5 | "keywords": [ 6 | "ember-addon", 7 | "ember-cli", 8 | "feature-flags", 9 | "ember-compile" 10 | ], 11 | "repository": "https://github.com/minichate/ember-cli-conditional-compile.git", 12 | "license": "MIT", 13 | "author": "", 14 | "directories": { 15 | "doc": "doc", 16 | "test": "tests" 17 | }, 18 | "scripts": { 19 | "build": "ember build", 20 | "lint": "eslint app config lib tests", 21 | "start": "ember server", 22 | "test": "yarn test:node && yarn test:ember", 23 | "test:ember": "ember test", 24 | "test:node": "mocha node-tests" 25 | }, 26 | "dependencies": { 27 | "babel-plugin-inline-replace-variables": "1.3.1", 28 | "broccoli-babel-transpiler": "^7.1.1", 29 | "broccoli-funnel": "~2.0.1", 30 | "broccoli-replace": "0.12.0", 31 | "chalk": "~2.4.2", 32 | "ember-cli-babel": "^7.26.11", 33 | "ember-cli-version-checker": "~3.0.1", 34 | "lodash.merge": "~4.6.1", 35 | "object-hash": "^1.3.1" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.18.2", 39 | "@ember/optional-features": "^1.3.0", 40 | "@ember/string": "^3.1.1", 41 | "@ember/test-helpers": "^2.7.0", 42 | "@glimmer/component": "^1.0.0", 43 | "@glimmer/tracking": "^1.0.0", 44 | "babel-eslint": "~10.0.1", 45 | "broccoli-asset-rev": "~3.0.0", 46 | "broccoli-test-helper": "^2.0.0", 47 | "chai": "^4.2.0", 48 | "co": "^4.6.0", 49 | "common-tags": "^1.8.0", 50 | "ember-auto-import": "^2.4.2", 51 | "ember-cli": "^4.4.0", 52 | "ember-cli-dependency-checker": "~3.3.1", 53 | "ember-cli-eslint": "~5.1.0", 54 | "ember-cli-htmlbars": "6.0.1", 55 | "ember-cli-inject-live-reload": "~2.0.1", 56 | "ember-cli-release": "~1.0.0-beta.2", 57 | "ember-load-initializers": "2.0.0", 58 | "ember-maybe-import-regenerator": "^1.0.0", 59 | "ember-qunit": "^5.1.5", 60 | "ember-resolver": "^8.0.3", 61 | "ember-source": "^4.4.0", 62 | "ember-try": "^1.1.0", 63 | "eslint": "~5.12.1", 64 | "eslint-plugin-ember": "^10.6.1", 65 | "glob": "~7.1.3", 66 | "loader.js": "4.7.0", 67 | "mocha": "^5.2.0", 68 | "qunit": "^2.19.1", 69 | "qunit-dom": "^2.0.0", 70 | "webpack": "^5.72.1" 71 | }, 72 | "engines": { 73 | "node": "12.* || 14.* || >= 16" 74 | }, 75 | "ember": { 76 | "edition": "octane" 77 | }, 78 | "ember-addon": { 79 | "configPath": "tests/dummy/config", 80 | "versionCompatibility": { 81 | "ember": ">= 3.15.0" 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | test_page: 'tests/index.html?hidepassed', 4 | disable_watching: true, 5 | launch_in_ci: [ 6 | 'Chrome' 7 | ], 8 | launch_in_dev: [ 9 | 'Chrome' 10 | ], 11 | browser_start_timeout: 60, 12 | browser_disconnect_timeout: 1000, 13 | parallel: -1, 14 | browser_args: { 15 | Chrome: [ 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--disable-web-security', 20 | '--headless', 21 | '--incognito', 22 | '--mute-audio', 23 | '--no-sandbox', 24 | '--remote-debugging-address=0.0.0.0', 25 | '--remote-debugging-port=9222', 26 | '--window-size=1440,900' 27 | ] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName", 25 | "ENABLE_FOO", 26 | "ENABLE_BAR" 27 | ], 28 | "node": false, 29 | "browser": false, 30 | "boss": true, 31 | "curly": true, 32 | "debug": false, 33 | "devel": false, 34 | "eqeqeq": true, 35 | "evil": true, 36 | "forin": false, 37 | "immed": false, 38 | "laxbreak": false, 39 | "newcap": true, 40 | "noarg": true, 41 | "noempty": false, 42 | "nonew": false, 43 | "nomen": false, 44 | "onevar": false, 45 | "plusplus": false, 46 | "regexp": false, 47 | "undef": true, 48 | "sub": true, 49 | "strict": false, 50 | "white": false, 51 | "eqnull": true, 52 | "esnext": true, 53 | "unused": true 54 | } 55 | -------------------------------------------------------------------------------- /tests/acceptance/application-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { visit } from '@ember/test-helpers'; 3 | import { setupApplicationTest } from '../helpers/index'; 4 | 5 | module('Acceptance | application', function(hooks) { 6 | setupApplicationTest(hooks); 7 | 8 | test('enabled flags are shown', async function(assert) { 9 | await visit('/'); 10 | assert.dom('.enabled_foo').exists().hasText('ENABLED_FOO!! \\o/'); 11 | }); 12 | 13 | test('enabled flags are shown for unless helper', async function(assert) { 14 | await visit('/'); 15 | 16 | assert.dom('.unless_disabled_foo').exists().hasText('DISABLED_FOO!! \\o/') 17 | }); 18 | 19 | test('disabled flags are not shown', async function(assert) { 20 | await visit('/'); 21 | 22 | assert.dom('enabled_bar').doesNotExist(); 23 | }); 24 | 25 | test('disabled else blocks are shown', async function(assert) { 26 | await visit('/'); 27 | 28 | assert.dom('.disabled_bar').exists().hasText('DISABLED_BAR!! \\o/') 29 | }); 30 | 31 | test('enabled else blocks are not shown', async function(assert) { 32 | await visit('/'); 33 | 34 | assert.dom('.disabled_foo').doesNotExist(); 35 | }); 36 | 37 | test('new style flag enabled blocks are shown', async function(assert) { 38 | await visit('/'); 39 | 40 | assert.dom('.new_flag_enabled_foo').exists() 41 | assert.dom('.new_flag_disabled_foo').doesNotExist() 42 | }); 43 | 44 | test('new style unless flag enabled blocks are shown', async function(assert) { 45 | await visit('/'); 46 | 47 | assert.dom('.new_flag_unless_enabled_bar').exists() 48 | assert.dom('.new_flag_unless_disabled_bar').doesNotExist() 49 | }); 50 | }); 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import config from './config/environment'; 2 | import Application from '@ember/application'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import Resolver from 'ember-resolver'; 5 | 6 | let App; 7 | 8 | App = Application.extend({ 9 | modulePrefix: config.modulePrefix, 10 | podModulePrefix: config.podModulePrefix, 11 | Resolver: Resolver 12 | }); 13 | 14 | loadInitializers(App, config.modulePrefix); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | foo: Ember.computed(() => { 5 | return ENABLE_FOO; 6 | }), 7 | 8 | bar: Ember.computed(() => { 9 | return ENABLE_BAR; 10 | }) 11 | }); 12 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dummy 6 | 7 | 8 | 9 | {{content-for 'head'}} 10 | 11 | 12 | 13 | 14 | {{content-for 'head-footer'}} 15 | 16 | 17 | {{content-for 'body'}} 18 | 19 | 20 | 21 | 22 | {{content-for 'body-footer'}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | let Router = EmberRouter.extend({ 5 | location: config.locationType 6 | }); 7 | 8 | Router.map(function() { 9 | }); 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/styles/app.css -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Welcome to Ember

2 | 3 | {{outlet}} 4 | 5 | {{#if-flag-ENABLE_FOO}} 6 |
ENABLED_FOO!! \o/
7 | {{else}} 8 |
DISABLED_FOO!! \o/
9 | {{/if-flag-ENABLE_FOO}} 10 | 11 | {{#if-flag-ENABLE_BAR}} 12 |
ENABLED_BAR!! \o/
13 | {{else}} 14 |
DISABLED_BAR!! \o/
15 | {{/if-flag-ENABLE_BAR}} 16 | 17 | {{#unless-flag-ENABLE_FOO}} 18 |
ENABLED_FOO!! \o/
19 | {{else}} 20 |
DISABLED_FOO!! \o/
21 | {{/unless-flag-ENABLE_FOO}} 22 | 23 | {{#unless-flag-ENABLE_BAR}} 24 |
ENABLED_BAR!! \o/
25 | {{else}} 26 |
DISABLED_BAR!! \o/
27 | {{/unless-flag-ENABLE_BAR}} 28 | 29 | {{#if-flag ENABLE_FOO}} 30 |
ENABLED_FOO!! \o/
31 | {{else}} 32 |
DISABLED_FOO!! \o/
33 | {{/if-flag}} 34 | 35 | {{#unless-flag ENABLE_BAR}} 36 |
ENABLE_BAR!! \o/
37 | {{else}} 38 |
DISABLED_BAR!! \o/
39 | {{/unless-flag}} 40 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.4.0", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--yarn", 15 | "--no-welcome" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = function(environment) { 2 | let ENV = { 3 | modulePrefix: 'dummy', 4 | environment: environment, 5 | rootURL: '/', 6 | locationType: 'history', 7 | contentSecurityPolicy: { 8 | 'default-src': "'none'", 9 | 'script-src': "'self' 'unsafe-inline' 'unsafe-eval'", 10 | 'font-src': "'self' data:", 11 | 'connect-src': "'self'", 12 | 'img-src': "'self'", 13 | 'style-src': "'self' 'unsafe-inline'", 14 | 'frame-src': "" 15 | }, 16 | EmberENV: { 17 | FEATURES: { 18 | // Here you can enable experimental features on an ember canary build 19 | // e.g. 'with-controller': true 20 | } 21 | }, 22 | 23 | APP: { 24 | // Here you can pass flags/options to your application instance 25 | // when it is created 26 | } 27 | }; 28 | 29 | if (environment === 'development') { 30 | // ENV.APP.LOG_RESOLVER = true; 31 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 32 | // ENV.APP.LOG_TRANSITIONS = true; 33 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 34 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 35 | } 36 | 37 | if (environment === 'test') { 38 | // Testem prefers this... 39 | ENV.rootURL = '/'; 40 | ENV.locationType = 'none'; 41 | ENV.APP.autoboot = false; 42 | // keep test console output quieter 43 | ENV.APP.LOG_ACTIVE_GENERATION = false; 44 | ENV.APP.LOG_VIEW_LOOKUPS = false; 45 | 46 | ENV.APP.rootElement = '#ember-testing'; 47 | } 48 | 49 | return ENV; 50 | }; 51 | -------------------------------------------------------------------------------- /tests/dummy/config/feature-flags.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | 3 | const GLOBAL_FLAGS = { 4 | featureFlags: { 5 | ENABLE_FOO: true, 6 | ENABLE_BAR: false 7 | }, 8 | includeDirByFlag: { 9 | ENABLE_FOO: [] 10 | } 11 | } 12 | return GLOBAL_FLAGS 13 | } -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dummy Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/integration/components/compiler-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from '../../helpers/index'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Component | compiler', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('precompile enabled flags', async function(assert) { 10 | await render(hbs` 11 | {{#if-flag-ENABLE_FOO}}Foo{{/if-flag-ENABLE_FOO}} 12 | `); 13 | 14 | assert.dom(this.element).hasText('Foo'); 15 | }); 16 | 17 | test('precompile {{unless-flag}} enabled flags', async function(assert) { 18 | render(hbs` 19 | {{#unless-flag-ENABLE_FOO}}Foo{{/unless-flag-ENABLE_FOO}} 20 | `); 21 | 22 | assert.dom(this.element).hasText(''); 23 | }); 24 | 25 | test('precompile enabled flags', async function(assert) { 26 | await render(hbs` 27 | {{#if-flag-ENABLE_FOO}}Foo{{/if-flag-ENABLE_FOO}} 28 | `); 29 | 30 | assert.dom(this.element).hasText('Foo'); 31 | }); 32 | 33 | test('precompile {{unless-flag}} enabled flags', async function(assert) { 34 | await render(hbs` 35 | {{#unless-flag-ENABLE_FOO}}Foo{{/unless-flag-ENABLE_FOO}} 36 | `); 37 | 38 | assert.dom(this.element).hasText(''); 39 | }); 40 | 41 | test('precompile disabled flags', async function(assert) { 42 | await render(hbs` 43 | {{#if-flag-ENABLE_BAR}}Bar{{/if-flag-ENABLE_BAR}} 44 | `); 45 | 46 | assert.dom(this.element).hasText(''); 47 | }); 48 | 49 | test('precompile {{unless-flag}} disabled flags', async function(assert) { 50 | await render(hbs` 51 | {{#unless-flag-ENABLE_BAR}}Bar{{/unless-flag-ENABLE_BAR}} 52 | `); 53 | 54 | assert.dom(this.element).hasText('Bar'); 55 | }); 56 | 57 | test('precompile else block', async function(assert) { 58 | await render(hbs` 59 | {{#if-flag-ENABLE_BAR}}Bar{{else}}Baz{{/if-flag-ENABLE_BAR}} 60 | `); 61 | 62 | assert.dom(this.element).hasText('Baz'); 63 | }); 64 | 65 | test('precompile {{unless-flag}} else block', async function(assert) { 66 | await render(hbs` 67 | {{#unless-flag-ENABLE_BAR}}Bar{{else}}Baz{{/unless-flag-ENABLE_BAR}} 68 | `); 69 | 70 | assert.dom(this.element).hasText('Bar'); 71 | }); 72 | }); 73 | 74 | 75 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'dummy/app'; 2 | import config from 'dummy/config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import * as QUnit from 'qunit'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setup(QUnit.assert); 9 | setApplication(Application.create(config.APP)); 10 | start(); -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/controllers/application-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from '../../helpers/index'; 3 | 4 | module('Unit | Controller | application', function(hooks) { 5 | setupTest(hooks); 6 | 7 | // Replace this with your real tests. 8 | test('it can access set properties', function(assert) { 9 | let controller = this.owner.lookup('controller:application'); 10 | 11 | assert.ok(controller); 12 | assert.equal(true, controller.get('foo')); 13 | assert.equal(false, controller.get('bar')); 14 | }); 15 | }); -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minichate/ember-cli-conditional-compile/29b4874a33146d6e3c33327f39e3a3158edf258c/vendor/.gitkeep --------------------------------------------------------------------------------