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.
const os = require('os');
const path = require('path');
// Public node modules.
const _ = require('lodash');
// Local utilities.
const json = require('strapi-utils').json;
/**
* Expose new instance of `Configuration`
*/
module.exports = strapi => {
return new Configuration();
module.exports = class Configuration {
function Configuration() {
constructor() {}
/**
* Strapi default configuration
@ -25,7 +25,7 @@ module.exports = strapi => {
* @api private
*/
this.defaults = appPath => {
defaults(context, appPath) {
// If `appPath` not specified, unfortunately, this is a fatal error,
// since reasonable defaults cannot be assumed.
@ -42,11 +42,11 @@ module.exports = strapi => {
appPath: appPath,
// Core settings non provided by hooks.
host: process.env.HOST || process.env.HOSTNAME || strapi.config.host || 'localhost',
port: process.env.PORT || strapi.config.port || 1337,
host: process.env.HOST || process.env.HOSTNAME || context.config.host || 'localhost',
port: process.env.PORT || context.config.port || 1337,
// 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.
reload: {
@ -81,9 +81,27 @@ module.exports = strapi => {
* @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');
// Local dependencies.
const Hook = require('./configuration/hooks');
const __Configuration = require('./configuration');
const __loadHooks = require('./private/loadHooks');
const Configuration = require('./configuration');
const loadHooks = require('./private/loadHooks');
const DEFAULT_HOOKS = require('./configuration/hooks/defaultHooks');
/**
@ -23,11 +22,6 @@ const DEFAULT_HOOKS = require('./configuration/hooks/defaultHooks');
*/
module.exports = function (configOverride, cb) {
const self = this;
const Configuration = __Configuration(this);
const loadHooks = __loadHooks(this);
if (this._exiting) {
this.log.error('Cannot load or start an application after it has already been stopped.');
process.exit(1);
@ -48,27 +42,38 @@ module.exports = function (configOverride, cb) {
// Apply core defaults and hook-agnostic configuration,
// esp. overrides including command-line options, environment variables,
// and options that were passed in programmatically.
config: [Configuration.load],
config: cb => new Configuration().load(this, cb),
// Optionally expose globals as soon as the
// 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).
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.
_.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);
}));
loadHooks(self.hooks, 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;
cb();
}
/**
* Initiliaze hooks,
@ -77,10 +82,9 @@ module.exports = function (configOverride, cb) {
* @api private
*/
function initializeHooks(result, cb) {
function initializeHooks(cb) {
// Reset
self.hooks = {};
this.hooks = {};
const tree = {};
@ -92,14 +96,14 @@ module.exports = function (configOverride, cb) {
});
// Extend tree with external hooks.
_.forEach(self.externalHooks, (hook, hookIdentity) => {
_.forEach(this.externalHooks, (hook, hookIdentity) => {
_.set(tree, hookIdentity, hook);
});
// 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).
_.assignWith(mapper, tree, (objValue, srcValue) => {
@ -107,15 +111,15 @@ module.exports = function (configOverride, cb) {
});
// Pick hook to load.
self.hooks = _.pickBy(mapper, value => value !== false);
this.hooks = _.pickBy(mapper, value => value !== false);
// Require only necessary hooks.
self.hooks =_.mapValues(self.hooks, (hook, hookIdentity) => {
this.hooks =_.mapValues(this.hooks, (hook, hookIdentity) => {
try {
return require(_.get(tree, hookIdentity));
} catch (err) {
try {
return require(path.resolve(self.config.appPath, 'node_modules', hookIdentity));
return require(path.resolve(this.config.appPath, 'node_modules', hookIdentity));
} catch (err) {
cb(err);
}
@ -132,44 +136,44 @@ module.exports = function (configOverride, cb) {
*/
function ready__(cb) {
self.emit('hooks:builtIn:ready');
this.emit('hooks:builtIn:ready');
return err => {
if (err) {
// Displaying errors, try to start the server through
self.log.error(err);
this.log.error(err);
}
// Automatically define the server URL from
// `proxy`, `ssl`, `host`, and `port` config.
if (_.isString(self.config.proxy)) {
self.config.url = self.config.proxy;
if (_.isString(this.config.proxy)) {
this.config.url = this.config.proxy;
} else {
if (_.isPlainObject(self.config.ssl) && self.config.ssl.disabled === false) {
self.config.url = 'https://' + self.config.host + ':' + self.config.port;
if (_.isPlainObject(this.config.ssl) && this.config.ssl.disabled === false) {
this.config.url = 'https://' + this.config.host + ':' + this.config.port;
} 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.
// Use of the `herd` node module to herd the child processes with
// zero downtime reloads.
if (_.isPlainObject(self.config.reload) && !_.isEmpty(self.config.reload) && self.config.reload.workers > 0) {
herd(self.config.name)
if (_.isPlainObject(this.config.reload) && !_.isEmpty(this.config.reload) && this.config.reload.workers > 0) {
herd(this.config.name)
.close(function () {
process.send('message');
})
.timeout(self.config.reload.timeout)
.size(self.config.reload.workers)
.timeout(this.config.reload.timeout)
.size(this.config.reload.workers)
.run(function () {
self.server.listen(self.config.port);
this.server.listen(this.config.port);
});
} 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
*/
module.exports = function (strapi) {
return (hooks, cb) => {
module.exports = function(cb) {
function prepareHook(id) {
let hookPrototype = hooks[id];
let hookPrototype = this.hooks[id];
// Handle folder-defined modules (default to `./lib/index.js`)
// Since a hook definition must be a function.
@ -48,15 +46,15 @@ module.exports = function (strapi) {
def.configKey = hookPrototype.configKey || def.identity;
// 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 applyDefaults(hook) {
// 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.
@ -65,40 +63,39 @@ module.exports = function (strapi) {
setTimeout(() => {
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);
}
}, strapi.config.hookTimeout || 1000);
}, this.config.hookTimeout || 1000);
hooks[id].load(err => {
this.hooks[id].load(err => {
timeout = false;
if (err) {
strapi.log.error('The hook `' + id + '` failed to load!');
strapi.emit('hook:' + id + ':error');
this.log.error('The hook `' + id + '` failed to load!');
this.emit('hook:' + id + ':error');
return cb(err);
}
strapi.emit('hook:' + id + ':loaded');
this.emit('hook:' + id + ':loaded');
// Defer to next tick to allow other stuff to happen.
process.nextTick(cb);
});
}
async.series(_.map(hooks, (hook, identity) => {
async.series(_.map(this.hooks, (hook, identity) => {
// Don't load disabled hook
if (_.get(strapi.config.hooks, identity) === false) {
if (_.get(this.config.hooks, identity) === false) {
return cb => {
cb();
};
}
return cb => {
prepareHook(identity);
applyDefaults(hook);
loadHook(identity, cb);
prepareHook.apply(this, [identity]);
applyDefaults.apply(this, [hook]);
loadHook.apply(this, [identity, cb]);
}
}), err => cb(err));
};
};