From aaaec192a745d79447aa0a6ea2bc2f546f838736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Georget?= Date: Thu, 25 Aug 2016 11:37:11 +0200 Subject: [PATCH] Simplify and reduce codebase for loading hooks --- packages/strapi/lib/configuration/index.js | 146 ++++++++++++--------- packages/strapi/lib/configuration/load.js | 67 ---------- packages/strapi/lib/load.js | 98 +++++++------- packages/strapi/lib/private/loadHooks.js | 137 ++++++++++--------- 4 files changed, 200 insertions(+), 248 deletions(-) delete mode 100644 packages/strapi/lib/configuration/load.js diff --git a/packages/strapi/lib/configuration/index.js b/packages/strapi/lib/configuration/index.js index e981e13d8d..c68739b2bb 100644 --- a/packages/strapi/lib/configuration/index.js +++ b/packages/strapi/lib/configuration/index.js @@ -6,84 +6,102 @@ // 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 + * + * @api private + */ + + defaults(context, appPath) { + + // If `appPath` not specified, unfortunately, this is a fatal error, + // since reasonable defaults cannot be assumed. + if (!appPath) { + throw new Error('Error: No `appPath` specified!'); + } + + // Set up config defaults. + return { + // Save `appPath` in implicit defaults. + // `appPath` is passed from above in case `start` was used. + // This is the directory where this Strapi process is being initiated from. + // Usually this means `process.cwd()`. + appPath: appPath, + + // Core settings non provided by hooks. + 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: context.app.env || process.env.NODE_ENV, + + // Default reload config. + reload: { + timeout: 1000, + workers: os.cpus().length + }, + + // Default paths. + paths: { + tmp: '.tmp', + config: 'config', + static: 'public', + views: 'views', + api: 'api', + controllers: 'controllers', + services: 'services', + policies: 'policies', + models: 'models' + }, + + // Start off needed empty objects and strings. + routes: {}, + collections: {}, + frontendUrl: '', + hooks: {} + }; + }; + + /** + * Load the configuration modules + * + * @api private + */ + + load(context, cb) { /** - * Strapi default configuration - * - * @api private + * Expose version/dependency info for the currently-running + * Strapi on the `strapi` object (from its `package.json`). */ - this.defaults = appPath => { + const pathToThisVersionOfStrapi = path.join(__dirname, '..', '..'); - // If `appPath` not specified, unfortunately, this is a fatal error, - // since reasonable defaults cannot be assumed. - if (!appPath) { - throw new Error('Error: No `appPath` specified!'); + json.getPackage(pathToThisVersionOfStrapi, (err, pkg) => { + if (err) { + return cb(err); } - // Set up config defaults. - return { - // Save `appPath` in implicit defaults. - // `appPath` is passed from above in case `start` was used. - // This is the directory where this Strapi process is being initiated from. - // Usually this means `process.cwd()`. - appPath: appPath, + context.version = pkg.version; + context.dependencies = pkg.dependencies; - // 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, - - // Make the environment in config match the server one. - environment: strapi.app.env || process.env.NODE_ENV, - - // Default reload config. - reload: { - timeout: 1000, - workers: os.cpus().length - }, - - // Default paths. - paths: { - tmp: '.tmp', - config: 'config', - static: 'public', - views: 'views', - api: 'api', - controllers: 'controllers', - services: 'services', - policies: 'policies', - models: 'models' - }, - - // Start off needed empty objects and strings. - routes: {}, - collections: {}, - frontendUrl: '', - hooks: {} - }; - }; - - /** - * Load the configuration modules - * - * @api private - */ - - this.load = require('./load').apply(this, [strapi]); - - // Bind the context of all instance methods. - _.bindAll(this); + // 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(); + }); } }; diff --git a/packages/strapi/lib/configuration/load.js b/packages/strapi/lib/configuration/load.js deleted file mode 100644 index 8fa83f24f6..0000000000 --- a/packages/strapi/lib/configuration/load.js +++ /dev/null @@ -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(); - }); - }; -}; diff --git a/packages/strapi/lib/load.js b/packages/strapi/lib/load.js index 9c88285c61..1357e8505c 100644 --- a/packages/strapi/lib/load.js +++ b/packages/strapi/lib/load.js @@ -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) => { - // Pre-initialize hooks for create dictionary. - _.assign(self.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], + 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(self.hooks, cb)] - }, ready__(cb)); + loadHooks: ['initializeHooks', (result, cb) => loadHooks.apply(this, [cb])] + }, (err, results) => ready__.apply(this, [cb])()); // Makes `app.load()` chainable. - return self; + 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(this.hooks, _.mapValues(_.get(DEFAULT_HOOKS, 'dictionary'), (hook, hookIdentity) => { + return require('./configuration/hooks/dictionary/' + hookIdentity); + })); + + 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); } @@ -124,7 +128,7 @@ module.exports = function (configOverride, cb) { return cb(); } - + /** * Returns function which is fired when Strapi is ready to go * @@ -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); }; } }; diff --git a/packages/strapi/lib/private/loadHooks.js b/packages/strapi/lib/private/loadHooks.js index c2d5752707..931c6ddf9f 100644 --- a/packages/strapi/lib/private/loadHooks.js +++ b/packages/strapi/lib/private/loadHooks.js @@ -18,87 +18,84 @@ const Hook = require('../configuration/hooks'); * @api private */ -module.exports = function (strapi) { - return (hooks, cb) => { +module.exports = function(cb) { + function prepareHook(id) { + let hookPrototype = this.hooks[id]; - function prepareHook(id) { - let hookPrototype = hooks[id]; - - // Handle folder-defined modules (default to `./lib/index.js`) - // Since a hook definition must be a function. - if (_.isObject(hookPrototype) && !_.isArray(hookPrototype) && !_.isFunction(hookPrototype)) { - hookPrototype = hookPrototype.index; - } - - if (!_.isFunction(hookPrototype)) { - strapi.log.error('Malformed (`' + id + '`) hook!'); - strapi.log.error('Hooks should be a function with one argument (`strapi`)'); - strapi.stop(); - } - - // Instantiate the hook. - const def = hookPrototype(strapi); - - // Mix in an `identity` property to hook definition. - def.identity = id.toLowerCase(); - - // If a config key was defined for this hook when it was loaded - // (probably because a user is overridding the default config key), - // set it on the hook definition. - def.configKey = hookPrototype.configKey || def.identity; - - // New up an actual Hook instance. - hooks[id] = new Hook(strapi, def); + // Handle folder-defined modules (default to `./lib/index.js`) + // Since a hook definition must be a function. + if (_.isObject(hookPrototype) && !_.isArray(hookPrototype) && !_.isFunction(hookPrototype)) { + hookPrototype = hookPrototype.index; } - // 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) || {}; - - _.defaultsDeep(strapi.config, defaults); + if (!_.isFunction(hookPrototype)) { + strapi.log.error('Malformed (`' + id + '`) hook!'); + strapi.log.error('Hooks should be a function with one argument (`strapi`)'); + strapi.stop(); } - // Load a hook and initialize it. - function loadHook(id, cb) { - let timeout = true; + // Instantiate the hook. + const def = hookPrototype(strapi); - setTimeout(() => { - if (timeout) { - strapi.log.error('The hook `' + id + '` wasn\'t loaded (too long to load)!'); - process.nextTick(cb); - } - }, strapi.config.hookTimeout || 1000); + // Mix in an `identity` property to hook definition. + def.identity = id.toLowerCase(); - hooks[id].load(err => { - timeout = false; + // If a config key was defined for this hook when it was loaded + // (probably because a user is overridding the default config key), + // set it on the hook definition. + def.configKey = hookPrototype.configKey || def.identity; - if (err) { - strapi.log.error('The hook `' + id + '` failed to load!'); - strapi.emit('hook:' + id + ':error'); - return cb(err); - } + // New up an actual Hook instance. + this.hooks[id] = new Hook(this, def); + } - strapi.emit('hook:' + id + ':loaded'); + // Function to apply a hook's `defaults` object or function. + function applyDefaults(hook) { + // Get the hook defaults. + const defaults = (_.isFunction(hook.defaults) ? hook.defaults(this.config) : hook.defaults) || {}; - // Defer to next tick to allow other stuff to happen. + _.defaultsDeep(this.config, defaults); + } + + // Load a hook and initialize it. + function loadHook(id, cb) { + let timeout = true; + + setTimeout(() => { + if (timeout) { + this.log.error('The hook `' + id + '` wasn\'t loaded (too long to load)!'); process.nextTick(cb); - }); + } + }, this.config.hookTimeout || 1000); + + this.hooks[id].load(err => { + timeout = false; + + if (err) { + this.log.error('The hook `' + id + '` failed to load!'); + this.emit('hook:' + id + ':error'); + return cb(err); + } + + this.emit('hook:' + id + ':loaded'); + + // Defer to next tick to allow other stuff to happen. + process.nextTick(cb); + }); + } + + async.series(_.map(this.hooks, (hook, identity) => { + // Don't load disabled hook + if (_.get(this.config.hooks, identity) === false) { + return cb => { + cb(); + }; } - async.series(_.map(hooks, (hook, identity) => { - // Don't load disabled hook - if (_.get(strapi.config.hooks, identity) === false) { - return cb => { - cb(); - }; - } - - return cb => { - prepareHook(identity); - applyDefaults(hook); - loadHook(identity, cb); - } - }), err => cb(err)); - }; + return cb => { + prepareHook.apply(this, [identity]); + applyDefaults.apply(this, [hook]); + loadHook.apply(this, [identity, cb]); + } + }), err => cb(err)); };