From 60c0b4e034f964871156c1bc296c4d78493685f4 Mon Sep 17 00:00:00 2001 From: chrmod Date: Sat, 6 Sep 2014 21:59:16 +0200 Subject: [PATCH 1/2] Introducing `config` hook for addons Implements: #1907 A new `config` hook is available for addons. It can be used to alternate application configuration (example: toggling FEATURE flags for Ember) or to read the current config and change addon behaviour (example: importing dependencies conditionally). Interface: ``` // environment - 'development', 'test', 'production' // currentConfig - hash with config for given environment config: function(environment, currentConfig) {} ``` --- lib/broccoli/broccoli-config-loader.js | 14 ++++++++++++-- lib/broccoli/ember-app.js | 6 +++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/broccoli/broccoli-config-loader.js b/lib/broccoli/broccoli-config-loader.js index 99dbb1ec6e..a254f24197 100644 --- a/lib/broccoli/broccoli-config-loader.js +++ b/lib/broccoli/broccoli-config-loader.js @@ -2,6 +2,7 @@ var fs = require('fs'); var path = require('path'); +var merge = require('lodash-node/modern/objects/merge'); var Writer = require('broccoli-caching-writer'); function ConfigLoader (inputTree, options) { @@ -16,6 +17,13 @@ function ConfigLoader (inputTree, options) { ConfigLoader.prototype = Object.create(Writer.prototype); ConfigLoader.prototype.constructor = ConfigLoader; +ConfigLoader.prototype.getAddonsConfig = function(env, appConfig) { + var addons = this.options.project.addons; + return addons.reduce(function(config, addon) { + return addon.config ? merge(config, addon.config(env, config)) : config; + }, appConfig); +}; + ConfigLoader.prototype.updateCache = function(srcDir, destDir) { var self = this; var configPath = path.join(this.options.project.root, srcDir, 'environment.js'); @@ -39,7 +47,9 @@ ConfigLoader.prototype.updateCache = function(srcDir, destDir) { } environments.forEach(function(env) { - var config = configGenerator(env); + var appConfig = configGenerator(env); + var addonsConfig = this.getAddonsConfig(env, appConfig); + var config = merge(appConfig, addonsConfig); var jsonString = JSON.stringify(config); var moduleString = 'export default ' + jsonString + ';'; var outputPath = path.join(outputDir, env); @@ -51,7 +61,7 @@ ConfigLoader.prototype.updateCache = function(srcDir, destDir) { if (self.options.env === env) { fs.writeFileSync(defaultPath, moduleString, { encoding: 'utf8' }); } - }); + }, this); }; module.exports = ConfigLoader; diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js index 052b092349..85a4ef2958 100644 --- a/lib/broccoli/ember-app.js +++ b/lib/broccoli/ember-app.js @@ -175,6 +175,7 @@ function EmberApp(options) { EmberApp.prototype._notifyAddonIncluded = function() { this.initializeAddons(); + this.configureAddons(); this.project.addons.forEach(function(addon) { if (addon.included) { addon.included(this); @@ -182,6 +183,10 @@ EmberApp.prototype._notifyAddonIncluded = function() { }, this); }; +EmberApp.prototype.configureAddons = function() { + // do something that will make addon `config` hook to fire +}; + EmberApp.prototype.initializeAddons = function() { this.project.initializeAddons(); }; @@ -416,7 +421,6 @@ EmberApp.prototype._configTree = function() { } var configPath = this.options.environment || 'config/environment.js'; - var configTree = configLoader(path.dirname(configPath), { env: this.env, tests: this.tests, From b0c240a24f1e45e73392abdd895ed20a9e4c4974 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Fri, 12 Sep 2014 00:13:05 -0400 Subject: [PATCH 2/2] Refactor addon config generation to Project.prototype.config. --- lib/broccoli/broccoli-config-loader.js | 20 ++--- lib/broccoli/ember-app.js | 8 +- lib/models/project.js | 37 ++++++++- tests/unit/broccoli/config-loader-test.js | 72 +++++++++++++++++ tests/unit/models/project-test.js | 98 +++++++++++++++++------ 5 files changed, 188 insertions(+), 47 deletions(-) create mode 100644 tests/unit/broccoli/config-loader-test.js diff --git a/lib/broccoli/broccoli-config-loader.js b/lib/broccoli/broccoli-config-loader.js index a254f24197..dcc84517b1 100644 --- a/lib/broccoli/broccoli-config-loader.js +++ b/lib/broccoli/broccoli-config-loader.js @@ -2,7 +2,6 @@ var fs = require('fs'); var path = require('path'); -var merge = require('lodash-node/modern/objects/merge'); var Writer = require('broccoli-caching-writer'); function ConfigLoader (inputTree, options) { @@ -17,20 +16,17 @@ function ConfigLoader (inputTree, options) { ConfigLoader.prototype = Object.create(Writer.prototype); ConfigLoader.prototype.constructor = ConfigLoader; -ConfigLoader.prototype.getAddonsConfig = function(env, appConfig) { - var addons = this.options.project.addons; - return addons.reduce(function(config, addon) { - return addon.config ? merge(config, addon.config(env, config)) : config; - }, appConfig); +ConfigLoader.prototype.clearConfigGeneratorCache = function() { + var configPath = this.options.project.configPath(); + + // clear the previously cached version of this module + delete require.cache[configPath]; }; ConfigLoader.prototype.updateCache = function(srcDir, destDir) { var self = this; - var configPath = path.join(this.options.project.root, srcDir, 'environment.js'); - // clear the previously cached version of this module - delete require.cache[configPath]; - var configGenerator = require(configPath); + this.clearConfigGeneratorCache(); var outputDir = path.join(destDir, 'environments'); fs.mkdirSync(outputDir); @@ -47,9 +43,7 @@ ConfigLoader.prototype.updateCache = function(srcDir, destDir) { } environments.forEach(function(env) { - var appConfig = configGenerator(env); - var addonsConfig = this.getAddonsConfig(env, appConfig); - var config = merge(appConfig, addonsConfig); + var config = self.options.project.config(env); var jsonString = JSON.stringify(config); var moduleString = 'export default ' + jsonString + ';'; var outputPath = path.join(outputDir, env); diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js index 85a4ef2958..4914793624 100644 --- a/lib/broccoli/ember-app.js +++ b/lib/broccoli/ember-app.js @@ -175,7 +175,6 @@ function EmberApp(options) { EmberApp.prototype._notifyAddonIncluded = function() { this.initializeAddons(); - this.configureAddons(); this.project.addons.forEach(function(addon) { if (addon.included) { addon.included(this); @@ -183,10 +182,6 @@ EmberApp.prototype._notifyAddonIncluded = function() { }, this); }; -EmberApp.prototype.configureAddons = function() { - // do something that will make addon `config` hook to fire -}; - EmberApp.prototype.initializeAddons = function() { this.project.initializeAddons(); }; @@ -424,7 +419,8 @@ EmberApp.prototype._configTree = function() { var configTree = configLoader(path.dirname(configPath), { env: this.env, tests: this.tests, - project: this.project + project: this.project, + configPath: configPath }); this._cachedConfigTree = pickFiles(configTree, { diff --git a/lib/models/project.js b/lib/models/project.js index 153dd31765..9acaa40ff8 100644 --- a/lib/models/project.js +++ b/lib/models/project.js @@ -9,6 +9,7 @@ var assign = require('lodash-node/modern/objects/assign'); var DAG = require('../utilities/DAG'); var Command = require('../models/command'); var Addon = require('../models/addon'); +var merge = require('lodash-node/modern/objects/merge'); var emberCLIVersion = require('../utilities/ember-cli-version'); @@ -48,19 +49,41 @@ Project.prototype.isEmberCLIAddon = function() { return this.pkg.keywords && this.pkg.keywords.indexOf('ember-addon') > -1; }; -Project.prototype.config = function(env) { +Project.prototype.configPath = function() { var configPath = 'config'; if (this.pkg['ember-addon'] && this.pkg['ember-addon']['configPath']) { configPath = this.pkg['ember-addon']['configPath']; } - if (fs.existsSync(path.join(this.root, configPath, 'environment.js'))) { - return this.require('./' + path.join(configPath, 'environment'))(env); + + return path.join(configPath, 'environment'); +}; + +Project.prototype.config = function(env) { + var configPath = this.configPath(); + + if (fs.existsSync(path.join(this.root, configPath + '.js'))) { + var appConfig = this.require('./' + configPath)(env); + var addonsConfig = this.getAddonsConfig(env, appConfig); + + return merge(addonsConfig, appConfig); } else { - return { }; + return this.getAddonsConfig(env, {}); } }; +Project.prototype.getAddonsConfig = function(env, appConfig) { + this.initializeAddons(); + + return this.addons.reduce(function(config, addon) { + if (addon.config) { + merge(config, addon.config(env, config)); + } + + return config; + }, appConfig); +}; + Project.prototype.has = function(file) { return fs.existsSync(path.join(this.root, file)) || fs.existsSync(path.join(this.root, file + '.js')); }; @@ -134,6 +157,10 @@ Project.prototype.addIfAddon = function(addonPath) { }; Project.prototype.initializeAddons = function() { + if (this._addonsInitialized) { + return; + } + var project = this; var graph = new DAG(); var addon, emberAddonConfig; @@ -154,6 +181,8 @@ Project.prototype.initializeAddons = function() { project.addons.push(new AddonConstructor(project)); }); + + this._addonsInitialized = true; }; Project.prototype.addonCommands = function() { diff --git a/tests/unit/broccoli/config-loader-test.js b/tests/unit/broccoli/config-loader-test.js new file mode 100644 index 0000000000..8f779e8ae1 --- /dev/null +++ b/tests/unit/broccoli/config-loader-test.js @@ -0,0 +1,72 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var ConfigLoader = require('../../../lib/broccoli/broccoli-config-loader'); +var assert = require('assert'); +var root = process.cwd(); +var tmp = require('tmp-sync'); +var tmproot = path.join(root, 'tmp'); +var rimraf = require('rimraf'); + +describe('broccoli/broccoli-config-loader', function() { + var configLoader, tmpDestDir, tmpSrcDir, project, options; + + beforeEach(function() { + tmpDestDir = tmp.in(tmproot); + tmpSrcDir = tmp.in(tmproot); + + project = { + root: tmpSrcDir, + addons: [], + configPath: function() { + return 'config/environment'; + }, + + config: function(env) { + return { + env: env, + foo: 'bar', + baz: 'qux' + }; + } + }; + + options = { + env: 'development', + tests: true, + project: project + }; + + configLoader = new ConfigLoader('.', options); + }); + + afterEach(function() { + rimraf.sync(tmpDestDir); + }); + + describe('updateCache', function() { + it('writes the current environments file', function() { + configLoader.updateCache(tmpSrcDir, tmpDestDir); + + assert(fs.existsSync(path.join(tmpDestDir, 'environment.js'))); + assert(fs.existsSync(path.join(tmpDestDir, 'environments', 'development.js'))); + assert(fs.existsSync(path.join(tmpDestDir, 'environments', 'development.json'))); + + assert(fs.existsSync(path.join(tmpDestDir, 'environments', 'test.js'))); + assert(fs.existsSync(path.join(tmpDestDir, 'environments', 'test.json'))); + }); + + it('does not generate test environment files if testing is disabled', function() { + options.tests = false; + configLoader.updateCache(tmpSrcDir, tmpDestDir); + + assert(fs.existsSync(path.join(tmpDestDir, 'environment.js'))); + assert(fs.existsSync(path.join(tmpDestDir, 'environments', 'development.js'))); + assert(fs.existsSync(path.join(tmpDestDir, 'environments', 'development.json'))); + + assert(!fs.existsSync(path.join(tmpDestDir, 'environments', 'test.js'))); + assert(!fs.existsSync(path.join(tmpDestDir, 'environments', 'test.json'))); + }); + }); +}); diff --git a/tests/unit/models/project-test.js b/tests/unit/models/project-test.js index c05ef5c72b..af995c06ae 100644 --- a/tests/unit/models/project-test.js +++ b/tests/unit/models/project-test.js @@ -12,11 +12,12 @@ var emberCLIVersion = require('../../../lib/utilities/ember-cli-version'); describe('models/project.js', function() { var project, projectPath; - describe('Project.prototype.config default', function() { + describe('Project.prototype.config', function() { var called = false; - projectPath = process.cwd() + '/tmp/test-app'; - before(function() { + beforeEach(function() { + projectPath = process.cwd() + '/tmp/test-app'; + called = false; tmp.setup(projectPath); touch(projectPath + '/config/environment.js', { @@ -39,39 +40,88 @@ describe('models/project.js', function() { project.config('development'); assert.equal(called, true); }); - }); - - describe('Project.prototype.config custom config path from addon', function() { - var called = false; - projectPath = process.cwd() + '/tmp/test-app'; - - before(function() { - tmp.setup(projectPath); - - touch(projectPath + '/tests/dummy/config/environment.js', { - baseURL: '/foo/bar' - }); - project = new Project(projectPath, { }); + it('configPath() returns tests/dummy/config/environment', function() { project.pkg = { 'ember-addon': { 'configPath': 'tests/dummy/config' } }; - project.require = function() { - called = true; - return function() {}; + + assert.equal(project.configPath(), 'tests/dummy/config/environment'); + }); + + it('calls getAddonsConfig', function() { + var addonConfigCalled = false; + + project.getAddonsConfig = function() { + addonConfigCalled = true; + + return {}; }; + project.config('development'); + assert.equal(addonConfigCalled, true); }); + + it('returns getAddonsConfig result when configPath is not present', function() { + var expected = { + foo: 'bar' + }; + tmp.setup(projectPath); // ensure no config/environment.js is present + project.getAddonsConfig = function() { + return expected; + }; - after(function() { - tmp.teardown(projectPath); + var actual = project.config('development'); + assert.deepEqual(actual, expected); }); - it('config() finds and requires tests/dummy/config/environment', function() { - project.config('development'); - assert.equal(called, true); + describe('merges getAddonsConfig result with app config', function() { + var projectConfig, addonsConfig; + + beforeEach(function() { + addonsConfig = { addon: { derp: 'herp' } }; + projectConfig = { foo: 'bar', baz: 'qux' }; + + project.getAddonsConfig = function() { + return addonsConfig; + }; + + project.require = function() { + return function() { + return projectConfig; + }; + }; + }); + + it('merges getAddonsConfig result with app config', function() { + var expected = { + foo: 'bar', + baz: 'qux', + addon: { + derp: 'herp' + } + }; + + var actual = project.config('development'); + assert.deepEqual(actual, expected); + }); + + it('getAddonsConfig does NOT override project config', function() { + var expected = { + foo: 'bar', + baz: 'qux', + addon: { + derp: 'herp' + } + }; + + addonsConfig.foo = 'NO!!!!!!'; + + var actual = project.config('development'); + assert.deepEqual(actual, expected); + }); }); });