diff --git a/README.md b/README.md index e5e2163f..4724359b 100755 --- a/README.md +++ b/README.md @@ -95,6 +95,58 @@ treeForAddon(tree) { } ``` +### Debug Tooling + +In order to allow addons to easily provide good development mode ergonomics (assertions, deprecations, etc) but +still perform well in production mode ember-cli-babel automatically manages stripping / removing certain debug +statements. This concept was originally proposed in [ember-cli/rfcs#50](https://github.com/ember-cli/rfcs/pull/50), +but has been slightly modified during implementation (after researching what works well and what does not). + +#### Debug Macros + +To add convienient deprecations and assertions, consumers (apps and/or addons) can do the following: + +```js +import { deprecate, assert } from '@ember/debug'; + +export default Ember.Component.extend({ + init() { + this._super(...arguments); + deprecate( + 'Passing a string value or the `sauce` parameter is deprecated, please pass an instance of Sauce instead', + false, + { until: '1.0.0', id: 'some-addon-sauce' } + ); + assert('You must provide sauce for x-awesome.', this.sauce); + } +}) +``` + +In testing and development environments those statements will be executed (and assert or deprecate as appropriate), but +in production builds they will be inert (and stripped during minification). + +#### General Purpose Env Flags + +In some cases you may have the need to do things in debug builds that isn't related to asserts/deprecations/etc. For +example, you may expose certain API's for debugging only. You can do that via the `DEBUG` environment flag: + +```js +import { DEBUG } from '@glimmer/env'; + +const Component = Ember.Component.extend(); + +if (DEBUG) { + Component.reopen({ + specialMethodForDebugging() { + // do things ;) + } + }); +} +``` + +In testing and development environments `DEBUG` will be replaced by the boolean literal `true`, and in production builds it will be +replaced by `false`. When ran through a minifier (with dead code elimination) the entire section will be stripped. + ### About Modules Older versions of Ember CLI (`< 2.12`) use its own ES6 module transpiler. Because of that, this plugin disables Babel diff --git a/index.js b/index.js index c2a877ec..1b7aa667 100644 --- a/index.js +++ b/index.js @@ -176,6 +176,7 @@ module.exports = { options.plugins = [].concat( userPlugins, + this._getDebugMacroPlugins(config), shouldCompileModules && this._getModulesPlugin(), this._getPresetEnvPlugins(addonProvidedConfig), userPostTransformPlugins @@ -191,6 +192,32 @@ module.exports = { return options; }, + _getDebugMacroPlugins(config) { + let addonOptions = config['ember-cli-babel'] || {}; + + if (addonOptions.disableDebugTooling) { return; } + + const DebugMacros = require('babel-plugin-debug-macros').default; + const isProduction = process.env.EMBER_ENV === 'production'; + + let options = { + envFlags: { + source: '@glimmer/env', + flags: { DEBUG: !isProduction, CI: !!process.env.CI } + }, + + externalizeHelpers: { + global: 'Ember' + }, + + debugTools: { + source: '@ember/debug' + } + }; + + return [[DebugMacros, options]]; + }, + _getPresetEnvPlugins(config) { let options = config.options; diff --git a/node-tests/addon-test.js b/node-tests/addon-test.js index cf1a740b..dfc00624 100644 --- a/node-tests/addon-test.js +++ b/node-tests/addon-test.js @@ -7,6 +7,8 @@ const CoreObject = require('core-object'); const AddonMixin = require('../index'); const path = require('path'); const resolve = require('resolve'); +const CommonTags = require('common-tags'); +const stripIndent = CommonTags.stripIndent; const BroccoliTestHelper = require('broccoli-test-helper'); const createBuilder = BroccoliTestHelper.createBuilder; const createTempDir = BroccoliTestHelper.createTempDir; @@ -14,6 +16,8 @@ const createTempDir = BroccoliTestHelper.createTempDir; let Addon = CoreObject.extend(AddonMixin); describe('ember-cli-babel', function() { + const ORIGINAL_EMBER_ENV = process.env.EMBER_ENV; + beforeEach(function() { this.ui = new MockUI(); let project = { root: __dirname }; @@ -24,6 +28,14 @@ describe('ember-cli-babel', function() { }); }); + afterEach(function() { + if (ORIGINAL_EMBER_ENV === undefined) { + delete process.env.EMBER_ENV; + } else { + process.env.EMBER_ENV = ORIGINAL_EMBER_ENV; + } + }); + describe('transpileTree', function() { this.timeout(50000); @@ -33,8 +45,6 @@ describe('ember-cli-babel', function() { beforeEach(co.wrap(function* () { input = yield createTempDir(); - subject = this.addon.transpileTree(input.path()); - output = createBuilder(subject); })); afterEach(co.wrap(function* () { @@ -48,6 +58,9 @@ describe('ember-cli-babel', function() { "bar.js": `let bar = () => {};` }); + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + yield output.build(); expect( @@ -57,6 +70,131 @@ describe('ember-cli-babel', function() { "foo.js": `var foo = function foo() {};`, }); })); + + describe('debug macros', function() { + it("can opt-out via ember-cli-babel.disableDebugTooling", co.wrap(function* () { + process.env.EMBER_ENV = 'development'; + + let contents = stripIndent` + import { DEBUG } from '@glimmer/env'; + if (DEBUG) { + console.log('debug mode!'); + } + `; + + input.write({ + "foo.js": contents + }); + + subject = this.addon.transpileTree(input.path(), { + 'ember-cli-babel': { + disableDebugTooling: true + } + }); + + output = createBuilder(subject); + + yield output.build(); + + expect( + output.read() + ).to.deep.equal({ + "foo.js": contents + }); + })); + + describe('in development', function() { + it("should replace env flags by default ", co.wrap(function* () { + process.env.EMBER_ENV = 'development'; + + input.write({ + "foo.js": stripIndent` + import { DEBUG } from '@glimmer/env'; + if (DEBUG) { console.log('debug mode!'); } + ` + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect( + output.read() + ).to.deep.equal({ + "foo.js": `\nif (true) {\n console.log('debug mode!');\n}` + }); + })); + + it("should replace debug macros by default ", co.wrap(function* () { + process.env.EMBER_ENV = 'development'; + + input.write({ + "foo.js": stripIndent` + import { assert } from '@ember/debug'; + assert('stuff here', isNotBad()); + ` + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect( + output.read() + ).to.deep.equal({ + "foo.js": `(true && Ember.assert('stuff here', isNotBad()));` + }); + })); + }); + + describe('in production', function() { + it("should replace env flags by default ", co.wrap(function* () { + process.env.EMBER_ENV = 'production'; + + input.write({ + "foo.js": stripIndent` + import { DEBUG } from '@glimmer/env'; + if (DEBUG) { console.log('debug mode!'); } + ` + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect( + output.read() + ).to.deep.equal({ + "foo.js": `\nif (false) {\n console.log('debug mode!');\n}` + }); + })); + + it("should replace debug macros by default ", co.wrap(function* () { + process.env.EMBER_ENV = 'production'; + + input.write({ + "foo.js": stripIndent` + import { assert } from '@ember/debug'; + assert('stuff here', isNotBad()); + ` + }); + + subject = this.addon.transpileTree(input.path()); + output = createBuilder(subject); + + yield output.build(); + + expect( + output.read() + ).to.deep.equal({ + "foo.js": `(false && Ember.assert('stuff here', isNotBad()));` + }); + })); + }); + }); }); describe('_getAddonOptions', function() { diff --git a/package.json b/package.json index bafb19f9..f53d6540 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "amd-name-resolver": "0.0.6", + "babel-plugin-debug-macros": "^0.1.6", "babel-plugin-transform-es2015-modules-amd": "^6.24.0", "babel-polyfill": "^6.16.0", "babel-preset-env": "^1.2.0", @@ -49,6 +50,7 @@ "broccoli-test-helper": "^1.1.0", "chai": "^3.5.0", "co": "^4.6.0", + "common-tags": "^1.4.0", "console-ui": "^1.0.2", "core-object": "^2.0.6", "ember-cli": "^2.6.2", diff --git a/yarn.lock b/yarn.lock index 18ae9d32..d9a155df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -168,10 +168,6 @@ ast-types@0.8.12: version "0.8.12" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz#a0d90e4351bb887716c83fd637ebf818af4adfcc" -ast-types@0.8.15: - version "0.8.15" - resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52" - ast-types@0.9.6: version "0.9.6" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" @@ -428,6 +424,12 @@ babel-plugin-dead-code-elimination@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz#5f7c451274dcd7cccdbfbb3e0b85dd28121f0f65" +babel-plugin-debug-macros@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.1.6.tgz#6e0a29c6f3c3e122a8bd6fdb41fc2475d8f894ce" + dependencies: + semver "^5.3.0" + babel-plugin-eval@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz#a2faed25ce6be69ade4bfec263f70169195950da" @@ -1395,6 +1397,12 @@ commander@2.9.0, commander@^2.5.0, commander@^2.6.0: dependencies: graceful-readlink ">= 1.0.0" +common-tags@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0" + dependencies: + babel-runtime "^6.18.0" + commoner@~0.10.3: version "0.10.8" resolved "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" @@ -3807,7 +3815,7 @@ qs@6.4.0, qs@^6.2.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -quick-temp@0.1.6: +quick-temp@0.1.6, quick-temp@^0.1.2, quick-temp@^0.1.3, quick-temp@^0.1.5: version "0.1.6" resolved "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.6.tgz#a6242a15cba9f9cdbd341287b5c569e318eec307" dependencies: @@ -3815,14 +3823,6 @@ quick-temp@0.1.6: rimraf "~2.2.6" underscore.string "~2.3.3" -quick-temp@^0.1.2, quick-temp@^0.1.3, quick-temp@^0.1.5: - version "0.1.8" - resolved "https://registry.npmjs.org/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408" - dependencies: - mktemp "~0.4.0" - rimraf "^2.5.4" - underscore.string "~3.3.4" - qunit-notifications@^0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/qunit-notifications/-/qunit-notifications-0.1.1.tgz#3001afc6a6a77dfbd962ccbcddde12dec5286c09" @@ -3895,7 +3895,7 @@ readdirp@^2.0.0: readable-stream "^2.0.2" set-immediate-shim "^1.0.1" -recast@0.10.33: +recast@0.10.33, recast@^0.10.10: version "0.10.33" resolved "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" dependencies: @@ -3904,15 +3904,6 @@ recast@0.10.33: private "~0.1.5" source-map "~0.5.0" -recast@^0.10.10: - version "0.10.43" - resolved "https://registry.npmjs.org/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f" - dependencies: - ast-types "0.8.15" - esprima-fb "~15001.1001.0-dev-harmony-fb" - private "~0.1.5" - source-map "~0.5.0" - recast@^0.11.17, recast@^0.11.3: version "0.11.23" resolved "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" @@ -4633,7 +4624,7 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" -underscore.string@^3.2.2, underscore.string@~3.3.4: +underscore.string@^3.2.2: version "3.3.4" resolved "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz#2c2a3f9f83e64762fdc45e6ceac65142864213db" dependencies: