Simplify and reduce codebase for loading hooks

This commit is contained in:
Aurélien Georget 2016-08-25 11:37:11 +02:00
parent f2f85037e4
commit aaaec192a7
4 changed files with 200 additions and 248 deletions

View File

@ -6,18 +6,18 @@
// Node.js core. // Node.js core.
const os = require('os'); const os = require('os');
const path = require('path');
// Public node modules. // Local utilities.
const _ = require('lodash'); const json = require('strapi-utils').json;
/** /**
* Expose new instance of `Configuration` * Expose new instance of `Configuration`
*/ */
module.exports = strapi => { module.exports = class Configuration {
return new Configuration();
function Configuration() { constructor() {}
/** /**
* Strapi default configuration * Strapi default configuration
@ -25,7 +25,7 @@ module.exports = strapi => {
* @api private * @api private
*/ */
this.defaults = appPath => { defaults(context, appPath) {
// If `appPath` not specified, unfortunately, this is a fatal error, // If `appPath` not specified, unfortunately, this is a fatal error,
// since reasonable defaults cannot be assumed. // since reasonable defaults cannot be assumed.
@ -42,11 +42,11 @@ module.exports = strapi => {
appPath: appPath, appPath: appPath,
// Core settings non provided by hooks. // Core settings non provided by hooks.
host: process.env.HOST || process.env.HOSTNAME || strapi.config.host || 'localhost', host: process.env.HOST || process.env.HOSTNAME || context.config.host || 'localhost',
port: process.env.PORT || strapi.config.port || 1337, port: process.env.PORT || context.config.port || 1337,
// Make the environment in config match the server one. // Make the environment in config match the server one.
environment: strapi.app.env || process.env.NODE_ENV, environment: context.app.env || process.env.NODE_ENV,
// Default reload config. // Default reload config.
reload: { reload: {
@ -81,9 +81,27 @@ module.exports = strapi => {
* @api private * @api private
*/ */
this.load = require('./load').apply(this, [strapi]); load(context, cb) {
// Bind the context of all instance methods. /**
_.bindAll(this); * Expose version/dependency info for the currently-running
* Strapi on the `strapi` object (from its `package.json`).
*/
const pathToThisVersionOfStrapi = path.join(__dirname, '..', '..');
json.getPackage(pathToThisVersionOfStrapi, (err, pkg) => {
if (err) {
return cb(err);
}
context.version = pkg.version;
context.dependencies = pkg.dependencies;
// Override the previous contents of `strapi.config` with the new, validated
// config with defaults and overrides mixed in the appropriate order.
context.config = this.defaults(context, context.config.appPath || process.cwd());
cb();
});
} }
}; };

View File

@ -1,67 +0,0 @@
'use strict';
/**
* Module dependencies
*/
// Node.js core.
const path = require('path');
// Public node modules.
const async = require('async');
// Local utilities.
const json = require('strapi-utils').json;
/**
* Expose Configuration loader
*
* Load command-line overrides
*
* For reference, config priority is:
* -> implicit defaults
* -> environment variables
* -> user config files
* -> local config file
* -> configOverride (in call to `strapi.start()`)
* -> --cmdline args
*/
module.exports = function (strapi) {
return cb => {
// Commence with loading/validating/defaulting all the rest of the config.
async.auto({
/**
* Expose version/dependency info for the currently-running
* Strapi on the `strapi` object (from its `package.json`).
*/
versionAndDependencyInfo: cb => {
const pathToThisVersionOfStrapi = path.join(__dirname, '..', '..');
json.getPackage(pathToThisVersionOfStrapi, function (err, pkg) {
if (err) {
return cb(err);
}
strapi.version = pkg.version;
strapi.dependencies = pkg.dependencies;
cb();
});
}
}, err => {
if (err) {
return cb(err);
}
// Override the previous contents of `strapi.config` with the new, validated
// config with defaults and overrides mixed in the appropriate order.
strapi.config = this.defaults(strapi.config.appPath || process.cwd());
cb();
});
};
};

View File

@ -13,9 +13,8 @@ const async = require('async');
const herd = require('herd'); const herd = require('herd');
// Local dependencies. // Local dependencies.
const Hook = require('./configuration/hooks'); const Configuration = require('./configuration');
const __Configuration = require('./configuration'); const loadHooks = require('./private/loadHooks');
const __loadHooks = require('./private/loadHooks');
const DEFAULT_HOOKS = require('./configuration/hooks/defaultHooks'); const DEFAULT_HOOKS = require('./configuration/hooks/defaultHooks');
/** /**
@ -23,11 +22,6 @@ const DEFAULT_HOOKS = require('./configuration/hooks/defaultHooks');
*/ */
module.exports = function (configOverride, cb) { module.exports = function (configOverride, cb) {
const self = this;
const Configuration = __Configuration(this);
const loadHooks = __loadHooks(this);
if (this._exiting) { if (this._exiting) {
this.log.error('Cannot load or start an application after it has already been stopped.'); this.log.error('Cannot load or start an application after it has already been stopped.');
process.exit(1); process.exit(1);
@ -48,27 +42,38 @@ module.exports = function (configOverride, cb) {
// Apply core defaults and hook-agnostic configuration, // Apply core defaults and hook-agnostic configuration,
// esp. overrides including command-line options, environment variables, // esp. overrides including command-line options, environment variables,
// and options that were passed in programmatically. // and options that were passed in programmatically.
config: [Configuration.load], config: cb => new Configuration().load(this, cb),
// Optionally expose globals as soon as the // Optionally expose globals as soon as the
// config hook is loaded. // config hook is loaded.
exposeGlobals: ['config', (result, cb) => self.exposeGlobals(cb)], exposeGlobals: ['config', (result, cb) => this.exposeGlobals(cb)],
// Initialize dictionary's hooks.
preInitializeHooks: ['exposeGlobals', (result, cb) => preInitializeHooks.apply(this, [cb])],
// Create configurations tree (dictionary). // Create configurations tree (dictionary).
dictionary: ['exposeGlobals', (result, cb) => { loadDictionary: ['preInitializeHooks', (result, cb) => loadHooks.apply(this, [cb])],
// Initialize hooks left.
initializeHooks: ['loadDictionary', (result, cb) => initializeHooks.apply(this, [cb])],
// Load hooks into memory.
loadHooks: ['initializeHooks', (result, cb) => loadHooks.apply(this, [cb])]
}, (err, results) => ready__.apply(this, [cb])());
// Makes `app.load()` chainable.
return this;
/**
* Pre-initialize hooks by putting only dictionary hooks,
* into the `hooks` global varialbe.
*
* @api private
*/
function preInitializeHooks(cb) {
// Pre-initialize hooks for create dictionary. // Pre-initialize hooks for create dictionary.
_.assign(self.hooks, _.mapValues(_.get(DEFAULT_HOOKS, 'dictionary'), (hook, hookIdentity) => { _.assign(this.hooks, _.mapValues(_.get(DEFAULT_HOOKS, 'dictionary'), (hook, hookIdentity) => {
return require('./configuration/hooks/dictionary/' + hookIdentity); return require('./configuration/hooks/dictionary/' + hookIdentity);
})); }));
loadHooks(self.hooks, cb); cb();
}], }
// Initialize hooks global variable and configurations
initializeHooks: ['dictionary', initializeHooks],
// Load hooks into memory.
loadHooks: ['initializeHooks', (result, cb) => loadHooks(self.hooks, cb)]
}, ready__(cb));
// Makes `app.load()` chainable.
return self;
/** /**
* Initiliaze hooks, * Initiliaze hooks,
@ -77,10 +82,9 @@ module.exports = function (configOverride, cb) {
* @api private * @api private
*/ */
function initializeHooks(cb) {
function initializeHooks(result, cb) {
// Reset // Reset
self.hooks = {}; this.hooks = {};
const tree = {}; const tree = {};
@ -92,14 +96,14 @@ module.exports = function (configOverride, cb) {
}); });
// Extend tree with external hooks. // Extend tree with external hooks.
_.forEach(self.externalHooks, (hook, hookIdentity) => { _.forEach(this.externalHooks, (hook, hookIdentity) => {
_.set(tree, hookIdentity, hook); _.set(tree, hookIdentity, hook);
}); });
// Remove this sensitive object. // Remove this sensitive object.
delete strapi.externalHooks; delete this.externalHooks;
const mapper = _.clone(self.config.hooks); const mapper = _.clone(this.config.hooks);
// Map (warning: we could have some order issues). // Map (warning: we could have some order issues).
_.assignWith(mapper, tree, (objValue, srcValue) => { _.assignWith(mapper, tree, (objValue, srcValue) => {
@ -107,15 +111,15 @@ module.exports = function (configOverride, cb) {
}); });
// Pick hook to load. // Pick hook to load.
self.hooks = _.pickBy(mapper, value => value !== false); this.hooks = _.pickBy(mapper, value => value !== false);
// Require only necessary hooks. // Require only necessary hooks.
self.hooks =_.mapValues(self.hooks, (hook, hookIdentity) => { this.hooks =_.mapValues(this.hooks, (hook, hookIdentity) => {
try { try {
return require(_.get(tree, hookIdentity)); return require(_.get(tree, hookIdentity));
} catch (err) { } catch (err) {
try { try {
return require(path.resolve(self.config.appPath, 'node_modules', hookIdentity)); return require(path.resolve(this.config.appPath, 'node_modules', hookIdentity));
} catch (err) { } catch (err) {
cb(err); cb(err);
} }
@ -132,44 +136,44 @@ module.exports = function (configOverride, cb) {
*/ */
function ready__(cb) { function ready__(cb) {
self.emit('hooks:builtIn:ready'); this.emit('hooks:builtIn:ready');
return err => { return err => {
if (err) { if (err) {
// Displaying errors, try to start the server through // Displaying errors, try to start the server through
self.log.error(err); this.log.error(err);
} }
// Automatically define the server URL from // Automatically define the server URL from
// `proxy`, `ssl`, `host`, and `port` config. // `proxy`, `ssl`, `host`, and `port` config.
if (_.isString(self.config.proxy)) { if (_.isString(this.config.proxy)) {
self.config.url = self.config.proxy; this.config.url = this.config.proxy;
} else { } else {
if (_.isPlainObject(self.config.ssl) && self.config.ssl.disabled === false) { if (_.isPlainObject(this.config.ssl) && this.config.ssl.disabled === false) {
self.config.url = 'https://' + self.config.host + ':' + self.config.port; this.config.url = 'https://' + this.config.host + ':' + this.config.port;
} else { } else {
self.config.url = 'http://' + self.config.host + ':' + self.config.port; this.config.url = 'http://' + this.config.host + ':' + this.config.port;
} }
} }
// We can finally make the server listen on the configured port. // We can finally make the server listen on the configured port.
// Use of the `herd` node module to herd the child processes with // Use of the `herd` node module to herd the child processes with
// zero downtime reloads. // zero downtime reloads.
if (_.isPlainObject(self.config.reload) && !_.isEmpty(self.config.reload) && self.config.reload.workers > 0) { if (_.isPlainObject(this.config.reload) && !_.isEmpty(this.config.reload) && this.config.reload.workers > 0) {
herd(self.config.name) herd(this.config.name)
.close(function () { .close(function () {
process.send('message'); process.send('message');
}) })
.timeout(self.config.reload.timeout) .timeout(this.config.reload.timeout)
.size(self.config.reload.workers) .size(this.config.reload.workers)
.run(function () { .run(function () {
self.server.listen(self.config.port); this.server.listen(this.config.port);
}); });
} else { } else {
self.server.listen(self.config.port); this.server.listen(this.config.port);
} }
cb && cb(null, self); cb && cb(null, this);
}; };
} }
}; };

View File

@ -18,11 +18,9 @@ const Hook = require('../configuration/hooks');
* @api private * @api private
*/ */
module.exports = function (strapi) { module.exports = function(cb) {
return (hooks, cb) => {
function prepareHook(id) { function prepareHook(id) {
let hookPrototype = hooks[id]; let hookPrototype = this.hooks[id];
// Handle folder-defined modules (default to `./lib/index.js`) // Handle folder-defined modules (default to `./lib/index.js`)
// Since a hook definition must be a function. // Since a hook definition must be a function.
@ -48,15 +46,15 @@ module.exports = function (strapi) {
def.configKey = hookPrototype.configKey || def.identity; def.configKey = hookPrototype.configKey || def.identity;
// New up an actual Hook instance. // New up an actual Hook instance.
hooks[id] = new Hook(strapi, def); this.hooks[id] = new Hook(this, def);
} }
// Function to apply a hook's `defaults` object or function. // Function to apply a hook's `defaults` object or function.
function applyDefaults(hook) { function applyDefaults(hook) {
// Get the hook defaults. // Get the hook defaults.
const defaults = (_.isFunction(hook.defaults) ? hook.defaults(strapi.config) : hook.defaults) || {}; const defaults = (_.isFunction(hook.defaults) ? hook.defaults(this.config) : hook.defaults) || {};
_.defaultsDeep(strapi.config, defaults); _.defaultsDeep(this.config, defaults);
} }
// Load a hook and initialize it. // Load a hook and initialize it.
@ -65,40 +63,39 @@ module.exports = function (strapi) {
setTimeout(() => { setTimeout(() => {
if (timeout) { if (timeout) {
strapi.log.error('The hook `' + id + '` wasn\'t loaded (too long to load)!'); this.log.error('The hook `' + id + '` wasn\'t loaded (too long to load)!');
process.nextTick(cb); process.nextTick(cb);
} }
}, strapi.config.hookTimeout || 1000); }, this.config.hookTimeout || 1000);
hooks[id].load(err => { this.hooks[id].load(err => {
timeout = false; timeout = false;
if (err) { if (err) {
strapi.log.error('The hook `' + id + '` failed to load!'); this.log.error('The hook `' + id + '` failed to load!');
strapi.emit('hook:' + id + ':error'); this.emit('hook:' + id + ':error');
return cb(err); return cb(err);
} }
strapi.emit('hook:' + id + ':loaded'); this.emit('hook:' + id + ':loaded');
// Defer to next tick to allow other stuff to happen. // Defer to next tick to allow other stuff to happen.
process.nextTick(cb); process.nextTick(cb);
}); });
} }
async.series(_.map(hooks, (hook, identity) => { async.series(_.map(this.hooks, (hook, identity) => {
// Don't load disabled hook // Don't load disabled hook
if (_.get(strapi.config.hooks, identity) === false) { if (_.get(this.config.hooks, identity) === false) {
return cb => { return cb => {
cb(); cb();
}; };
} }
return cb => { return cb => {
prepareHook(identity); prepareHook.apply(this, [identity]);
applyDefaults(hook); applyDefaults.apply(this, [hook]);
loadHook(identity, cb); loadHook.apply(this, [identity, cb]);
} }
}), err => cb(err)); }), err => cb(err));
}; };
};