Merge pull request #86 from strongloop/feature/load-component

Configure components via `component-config.json`
This commit is contained in:
Miroslav Bajtoš 2015-01-07 08:28:29 +01:00
commit 8aa7156d3b
10 changed files with 126 additions and 5 deletions

View File

@ -109,6 +109,8 @@ var addInstructionsToBrowserify = require('./lib/bundler');
* for files containing model definitions. * for files containing model definitions.
* @property {Object} [middleware] Middleware configuration to use instead * @property {Object} [middleware] Middleware configuration to use instead
* of `{appRootDir}/middleware.json` * of `{appRootDir}/middleware.json`
* @property {Object} [components] Component configuration to use instead
* of `{appRootDir}/component-config.json`
* @property {Array.<String>} [bootDirs] List of directories where to look * @property {Array.<String>} [bootDirs] List of directories where to look
* for boot scripts. * for boot scripts.
* @property {Array.<String>} [bootScripts] List of script files to execute * @property {Array.<String>} [bootScripts] List of script files to execute

View File

@ -11,6 +11,7 @@ var cloneDeep = require('lodash').cloneDeep;
module.exports = function addInstructionsToBrowserify(instructions, bundler) { module.exports = function addInstructionsToBrowserify(instructions, bundler) {
bundleModelScripts(instructions, bundler); bundleModelScripts(instructions, bundler);
bundleComponentScripts(instructions, bundler);
bundleOtherScripts(instructions, bundler); bundleOtherScripts(instructions, bundler);
bundleInstructions(instructions, bundler); bundleInstructions(instructions, bundler);
}; };
@ -22,19 +23,27 @@ function bundleOtherScripts(instructions, bundler) {
} }
function bundleModelScripts(instructions, bundler) { function bundleModelScripts(instructions, bundler) {
var files = instructions.models bundleSourceFiles(instructions, 'models', bundler);
}
function bundleComponentScripts(instructions, bundler) {
bundleSourceFiles(instructions, 'components', bundler);
}
function bundleSourceFiles(instructions, type, bundler) {
var files = instructions[type]
.map(function(m) { return m.sourceFile; }) .map(function(m) { return m.sourceFile; })
.filter(function(f) { return !!f; }); .filter(function(f) { return !!f; });
var modelToFileMapping = instructions.models var instructionToFileMapping = instructions[type]
.map(function(m) { return files.indexOf(m.sourceFile); }); .map(function(m) { return files.indexOf(m.sourceFile); });
addScriptsToBundle('models', files, bundler); addScriptsToBundle(type, files, bundler);
// Update `sourceFile` properties with the new paths // Update `sourceFile` properties with the new paths
modelToFileMapping.forEach(function(fileIx, modelIx) { instructionToFileMapping.forEach(function(fileIx, sourceIx) {
if (fileIx === -1) return; if (fileIx === -1) return;
instructions.models[modelIx].sourceFile = files[fileIx]; instructions[type][sourceIx].sourceFile = files[fileIx];
}); });
} }

View File

@ -53,6 +53,12 @@ module.exports = function compile(options) {
var middlewareInstructions = var middlewareInstructions =
buildMiddlewareInstructions(middlewareRootDir, middlewareConfig); buildMiddlewareInstructions(middlewareRootDir, middlewareConfig);
var componentRootDir = appRootDir; // not configurable yet
var componentConfig = options.components ||
ConfigLoader.loadComponents(componentRootDir, env);
var componentInstructions =
buildComponentInstructions(componentRootDir, componentConfig);
// require directories // require directories
var bootDirs = options.bootDirs || []; // precedence var bootDirs = options.bootDirs || []; // precedence
bootDirs = bootDirs.concat(path.join(appRootDir, 'boot')); bootDirs = bootDirs.concat(path.join(appRootDir, 'boot'));
@ -81,6 +87,7 @@ module.exports = function compile(options) {
dataSources: dataSourcesConfig, dataSources: dataSourcesConfig,
models: modelInstructions, models: modelInstructions,
middleware: middlewareInstructions, middleware: middlewareInstructions,
components: componentInstructions,
files: { files: {
boot: bootScripts boot: bootScripts
} }
@ -471,3 +478,20 @@ function resolveMiddlewareParams(rootDir, params) {
} }
}); });
} }
function buildComponentInstructions(rootDir, componentConfig) {
return Object.keys(componentConfig).map(function(name) {
var sourceFile;
if (name.indexOf('./') === 0 || name.indexOf('../') === 0) {
// Relative path
sourceFile = path.resolve(rootDir, name);
} else {
sourceFile = require.resolve(name);
}
return {
sourceFile: sourceFile,
config: componentConfig[name]
};
});
}

View File

@ -44,6 +44,16 @@ ConfigLoader.loadMiddleware = function(rootDir, env) {
return loadNamed(rootDir, env, 'middleware', mergeMiddlewareConfig); return loadNamed(rootDir, env, 'middleware', mergeMiddlewareConfig);
}; };
/**
* Load component config from `component-config.json` and friends.
* @param {String} rootDir Directory where to look for files.
* @param {String} env Environment, usually `process.env.NODE_ENV`
* @returns {Object}
*/
ConfigLoader.loadComponents = function(rootDir, env) {
return loadNamed(rootDir, env, 'component-config', mergeComponentConfig);
};
/*-- Implementation --*/ /*-- Implementation --*/
/** /**
@ -162,6 +172,15 @@ function mergePhaseConfig(target, config, phase) {
} }
} }
function mergeComponentConfig(target, config, fileName) {
for (var c in target) {
var err = mergeObjects(target[c], config[c]);
if (err) {
throw new Error('Cannot apply ' + fileName + ' to `' + c + '`: ' + err);
}
}
}
function mergeObjects(target, config, keyPrefix) { function mergeObjects(target, config, keyPrefix) {
for (var key in config) { for (var key in config) {
var fullKey = keyPrefix ? keyPrefix + '.' + key : key; var fullKey = keyPrefix ? keyPrefix + '.' + key : key;

View File

@ -27,6 +27,7 @@ module.exports = function execute(app, instructions, callback) {
setupDataSources(app, instructions); setupDataSources(app, instructions);
setupModels(app, instructions); setupModels(app, instructions);
setupMiddleware(app, instructions); setupMiddleware(app, instructions);
setupComponents(app, instructions);
// Run the boot scripts in series synchronously or asynchronously // Run the boot scripts in series synchronously or asynchronously
// Please note async supports both styles // Please note async supports both styles
@ -282,6 +283,14 @@ function setupMiddleware(app, instructions) {
}); });
} }
function setupComponents(app, instructions) {
instructions.components.forEach(function(data) {
debug('Configuring component %j', data.sourceFile);
var configFn = require(data.sourceFile);
configFn(app, data.config);
});
}
function runBootScripts(app, instructions, callback) { function runBootScripts(app, instructions, callback) {
runScripts(app, instructions.files.boot, callback); runScripts(app, instructions.files.boot, callback);
} }

View File

@ -50,6 +50,10 @@ describe('browser support', function() {
expect(app.models.Customer.settings) expect(app.models.Customer.settings)
.to.have.property('_customized', 'Customer'); .to.have.property('_customized', 'Customer');
// configured in fixtures/browser-app/component-config.json
// and fixtures/browser-app/components/dummy-component.js
expect(app.dummyComponentOptions).to.eql({ option: 'value' });
done(); done();
}); });
}); });

View File

@ -1038,6 +1038,35 @@ describe('compiler', function() {
}); });
}); });
}); });
describe('for components', function() {
it('loads component configs from multiple files', function() {
appdir.createConfigFilesSync();
appdir.writeConfigFileSync('component-config.json', {
debug: { option: 'value' }
});
appdir.writeConfigFileSync('component-config.local.json', {
debug: { local: 'applied' }
});
var env = process.env.NODE_ENV || 'development';
appdir.writeConfigFileSync('component-config.' + env + '.json', {
debug: { env: 'applied' }
});
var instructions = boot.compile(appdir.PATH);
var component = instructions.components[0];
expect(component).to.eql({
sourceFile: require.resolve('debug'),
config: {
option: 'value',
local: 'applied',
env: 'applied'
}
});
});
});
}); });
function getNameProperty(obj) { function getNameProperty(obj) {

View File

@ -414,6 +414,22 @@ describe('executor', function() {
done(); done();
}); });
}); });
it('configures components', function() {
appdir.writeConfigFileSync('component-config.json', {
'./components/test-component': {
option: 'value'
}
});
appdir.writeFileSync('components/test-component/index.js',
'module.exports = ' +
'function(app, options) { app.componentOptions = options; }');
boot(app, appdir.PATH);
expect(app.componentOptions).to.eql({ option: 'value' });
});
}); });
function assertValidDataSource(dataSource) { function assertValidDataSource(dataSource) {
@ -439,6 +455,7 @@ function someInstructions(values) {
models: values.models || [], models: values.models || [],
dataSources: values.dataSources || { db: { connector: 'memory' } }, dataSources: values.dataSources || { db: { connector: 'memory' } },
middleware: values.middleware || { phases: [], middleware: [] }, middleware: values.middleware || { phases: [], middleware: [] },
components: values.components || [],
files: { files: {
boot: [] boot: []
} }

View File

@ -0,0 +1,5 @@
{
"./components/dummy-component": {
"option": "value"
}
}

View File

@ -0,0 +1,3 @@
module.exports = function(app, options) {
app.dummyComponentOptions = options;
};