mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 21:33:24 +03:00
Refactored helper registration code into a service
- The helper registration code is "framework" code and very specific - At the moment the "theme engine" is full of lots of disparate theme related stuff - I'm trying to make the frontend framework code clearer and also expand it to make it more useful - The helper system now also exposes 3 methods allowing you to register a directory, a helper or an alias - I've updated the codebase to use these both for our core helpers and for "apps"
This commit is contained in:
parent
554f36de55
commit
9d7049cd3f
17
core/boot.js
17
core/boot.js
@ -111,10 +111,10 @@ async function initCore({ghostServer, config}) {
|
||||
async function initServicesForFrontend() {
|
||||
debug('Begin: initServicesForFrontend');
|
||||
|
||||
debug('Begin: Frontend Routing Settings');
|
||||
debug('Begin: Routing Settings');
|
||||
const routeSettings = require('./server/services/route-settings');
|
||||
await routeSettings.init();
|
||||
debug('End: Frontend Routing Settings');
|
||||
debug('End: Routing Settings');
|
||||
|
||||
debug('Begin: Themes');
|
||||
const themeService = require('./server/services/themes');
|
||||
@ -124,6 +124,18 @@ async function initServicesForFrontend() {
|
||||
debug('End: initServicesForFrontend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Frontend is intended to be just Ghost's frontend
|
||||
*/
|
||||
async function initFrontend() {
|
||||
debug('Begin: initFrontend');
|
||||
|
||||
const helperService = require('./frontend/services/helpers');
|
||||
await helperService.init();
|
||||
|
||||
debug('End: initFrontend');
|
||||
}
|
||||
|
||||
/**
|
||||
* At the moment we load our express apps all in one go, they require themselves and are co-located
|
||||
* What we want is to be able to optionally load various components and mount them
|
||||
@ -331,6 +343,7 @@ async function bootGhost() {
|
||||
debug('Begin: Load Ghost Services & Apps');
|
||||
await initCore({ghostServer, config});
|
||||
await initServicesForFrontend();
|
||||
await initFrontend();
|
||||
const ghostApp = await initExpressApps();
|
||||
await initDynamicRouting();
|
||||
await initServices({config});
|
||||
|
@ -1,5 +1,6 @@
|
||||
const path = require('path');
|
||||
|
||||
const router = require('./lib/router');
|
||||
const registerHelpers = require('./lib/helpers');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
|
||||
// Dirty requires
|
||||
@ -22,7 +23,8 @@ module.exports = {
|
||||
let ampRoute = '*/amp/';
|
||||
|
||||
ghost.routeService.registerRouter(ampRoute, ampRouter);
|
||||
|
||||
registerHelpers(ghost);
|
||||
ghost.helperService.registerDir(path.resolve(__dirname, './lib/helpers'));
|
||||
// we use the {{ghost_head}} helper, but call it {{amp_ghost_head}}, so it's consistent
|
||||
ghost.helperService.registerAlias('amp_ghost_head', 'ghost_head');
|
||||
}
|
||||
};
|
||||
|
@ -1,18 +0,0 @@
|
||||
// Dirty require!
|
||||
const ghostHead = require('../../../../helpers/ghost_head');
|
||||
|
||||
function registerAmpHelpers(ghost) {
|
||||
ghost.helpers.registerAsync('amp_content', require('./amp_content'));
|
||||
|
||||
ghost.helpers.register('amp_components', require('./amp_components'));
|
||||
|
||||
ghost.helpers.register('amp_analytics', require('./amp_analytics'));
|
||||
|
||||
// we use the {{ghost_head}} helper, but call it {{amp_ghost_head}}, so it's consistent
|
||||
ghost.helpers.registerAsync('amp_ghost_head', ghostHead);
|
||||
|
||||
// additional injected styles for use inside the single <style amp-custom> tag
|
||||
ghost.helpers.register('amp_style', require('./amp_style'));
|
||||
}
|
||||
|
||||
module.exports = registerAmpHelpers;
|
@ -1,10 +1,10 @@
|
||||
const path = require('path');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const logging = require('@tryghost/logging');
|
||||
const errors = require('@tryghost/errors');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const middleware = require('./lib/middleware');
|
||||
const router = require('./lib/router');
|
||||
const registerHelpers = require('./lib/helpers');
|
||||
|
||||
const messages = {
|
||||
urlCannotContainPrivateSubdir: {
|
||||
@ -43,8 +43,7 @@ module.exports = {
|
||||
checkSubdir();
|
||||
|
||||
ghost.routeService.registerRouter(privateRoute, router);
|
||||
|
||||
registerHelpers(ghost);
|
||||
ghost.helperService.registerDir(path.resolve(__dirname, './lib/helpers'));
|
||||
},
|
||||
|
||||
setupMiddleware: function setupMiddleware(siteApp) {
|
||||
|
@ -1,3 +0,0 @@
|
||||
module.exports = function registerHelpers(ghost) {
|
||||
ghost.helpers.register('input_password', require('./input_password'));
|
||||
};
|
@ -13,7 +13,7 @@ const Promise = require('bluebird');
|
||||
const jsonpath = require('jsonpath');
|
||||
|
||||
const messages = {
|
||||
mustBeCalledAsBlock: 'The {{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}',
|
||||
mustBeCalledAsBlock: 'The {\\{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}',
|
||||
invalidResource: 'Invalid resource given to get helper'
|
||||
};
|
||||
|
||||
@ -194,3 +194,5 @@ module.exports = function get(resource, options) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.async = true;
|
||||
|
@ -229,3 +229,5 @@ module.exports = function ghost_head(options) { // eslint-disable-line camelcase
|
||||
return new SafeString(head.join('\n ').trim());
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.async = true;
|
||||
|
@ -12,7 +12,7 @@ const Promise = require('bluebird');
|
||||
const moment = require('moment');
|
||||
|
||||
const messages = {
|
||||
mustBeCalledAsBlock: 'The {{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}'
|
||||
mustBeCalledAsBlock: 'The {\\{{helperName}}} helper must be called as a block. E.g. {{#{helperName}}}...{{/{helperName}}}'
|
||||
};
|
||||
|
||||
const createFrame = hbs.handlebars.createFrame;
|
||||
@ -102,3 +102,6 @@ module.exports = function prevNext(options) {
|
||||
// With the guards out of the way, attempt to build the apiOptions, and then fetch the data
|
||||
return fetch.call(this, options, data);
|
||||
};
|
||||
|
||||
module.exports.async = true;
|
||||
module.exports.alias = 'next_post';
|
||||
|
@ -1,13 +1,14 @@
|
||||
const helpers = require('../../services/theme-engine/handlebars/register');
|
||||
const helperService = require('../../services/helpers');
|
||||
const routingService = require('../../services/routing');
|
||||
|
||||
module.exports.getInstance = function getInstance() {
|
||||
const appRouter = routingService.registry.getRouter('appRouter');
|
||||
|
||||
return {
|
||||
helpers: {
|
||||
register: helpers.registerThemeHelper.bind(helpers),
|
||||
registerAsync: helpers.registerAsyncThemeHelper.bind(helpers)
|
||||
helperService: {
|
||||
registerAlias: helperService.registerAlias.bind(helperService),
|
||||
registerHelper: helperService.registerHelper.bind(helperService),
|
||||
registerDir: helperService.registerDir.bind(helperService)
|
||||
},
|
||||
// Expose the route service...
|
||||
routeService: {
|
||||
|
@ -1,9 +1,9 @@
|
||||
const Promise = require('bluebird');
|
||||
const errors = require('@tryghost/errors');
|
||||
const hbs = require('../../theme-engine/engine');
|
||||
const config = require('../../../../shared/config');
|
||||
const logging = require('@tryghost/logging');
|
||||
|
||||
const {hbs} = require('../rendering');
|
||||
|
||||
// Register an async handlebars helper for a given handlebars instance
|
||||
function asyncHelperWrapper(hbsInstance, name, fn) {
|
||||
hbsInstance.registerAsyncHelper(name, function returnAsync(context, options, cb) {
|
||||
@ -13,7 +13,7 @@ function asyncHelperWrapper(hbsInstance, name, fn) {
|
||||
options = undefined;
|
||||
}
|
||||
|
||||
// Wrap the function passed in with a when.resolve so it can return either a promise or a value
|
||||
// Wrap the function passed in with a Promise.resolve so it can return either a promise or a value
|
||||
Promise.resolve(fn.call(this, context, options)).then(function asyncHelperSuccess(result) {
|
||||
cb(result);
|
||||
}).catch(function asyncHelperError(err) {
|
||||
@ -25,7 +25,7 @@ function asyncHelperWrapper(hbsInstance, name, fn) {
|
||||
}
|
||||
});
|
||||
|
||||
const result = config.get('env') === 'development' ? wrappedErr : '';
|
||||
const result = process.env.NODE_ENV === 'development' ? wrappedErr : '';
|
||||
|
||||
logging.error(wrappedErr);
|
||||
|
18
core/frontend/services/helpers/index.js
Normal file
18
core/frontend/services/helpers/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
const registry = require('./registry');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// Initialise Ghost's own helpers
|
||||
// This is a weird place for this to live!
|
||||
const init = async () => {
|
||||
const helperPath = path.join(__dirname, '../../', 'helpers');
|
||||
return await registry.registerDir(helperPath);
|
||||
};
|
||||
|
||||
// Oh look! A framework for helpers :D
|
||||
module.exports = {
|
||||
registerAlias: registry.registerAlias,
|
||||
registerDir: registry.registerDir,
|
||||
registerHelper: registry.registerHelper,
|
||||
init
|
||||
};
|
45
core/frontend/services/helpers/registry.js
Normal file
45
core/frontend/services/helpers/registry.js
Normal file
@ -0,0 +1,45 @@
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
||||
const handlebars = require('./handlebars');
|
||||
|
||||
// Internal Cache
|
||||
const registry = {};
|
||||
|
||||
const registerHelper = (name, helperFn) => {
|
||||
if (registry[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
registry[name] = helperFn;
|
||||
|
||||
if (helperFn.async) {
|
||||
handlebars.registerAsyncThemeHelper(name, helperFn);
|
||||
} else {
|
||||
handlebars.registerThemeHelper(name, helperFn);
|
||||
}
|
||||
};
|
||||
|
||||
const registerDir = (helperPath) => {
|
||||
let helperFiles = glob.sync('!(index).js', {cwd: helperPath});
|
||||
helperFiles.forEach((helper) => {
|
||||
const name = helper.replace(/.js$/, '');
|
||||
const fn = require(path.join(helperPath, helper));
|
||||
|
||||
registerHelper(name, fn);
|
||||
|
||||
if (fn.alias) {
|
||||
registerHelper(fn.alias, fn);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const registerAlias = (alias, name) => {
|
||||
registerHelper(alias, registry[name]);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
registerAlias,
|
||||
registerHelper,
|
||||
registerDir
|
||||
};
|
@ -2,7 +2,7 @@
|
||||
* This is a loose concept of a frontend rendering framework
|
||||
* Note: everything here gets deep-required from the theme-engine
|
||||
* This indicates that the theme engine is a set of services, rather than a single service
|
||||
* and could do with a refactor.
|
||||
* and could do with a further refactor.
|
||||
*
|
||||
* This at least keeps the deep requires in a single place.
|
||||
*/
|
||||
|
@ -1,54 +0,0 @@
|
||||
const register = require('./register');
|
||||
const loader = require('./loader');
|
||||
const coreHelpers = loader.getHelpers();
|
||||
const registerThemeHelper = register.registerThemeHelper;
|
||||
const registerAsyncThemeHelper = register.registerAsyncThemeHelper;
|
||||
|
||||
const registerAllCoreHelpers = function registerAllCoreHelpers() {
|
||||
// Register theme helpers
|
||||
registerThemeHelper('asset', coreHelpers.asset);
|
||||
registerThemeHelper('author', coreHelpers.author);
|
||||
registerThemeHelper('authors', coreHelpers.authors);
|
||||
registerThemeHelper('body_class', coreHelpers.body_class);
|
||||
registerThemeHelper('cancel_link', coreHelpers.cancel_link);
|
||||
registerThemeHelper('concat', coreHelpers.concat);
|
||||
registerThemeHelper('content', coreHelpers.content);
|
||||
registerThemeHelper('products', coreHelpers.products);
|
||||
registerThemeHelper('date', coreHelpers.date);
|
||||
registerThemeHelper('encode', coreHelpers.encode);
|
||||
registerThemeHelper('excerpt', coreHelpers.excerpt);
|
||||
registerThemeHelper('foreach', coreHelpers.foreach);
|
||||
registerThemeHelper('ghost_foot', coreHelpers.ghost_foot);
|
||||
registerThemeHelper('has', coreHelpers.has);
|
||||
registerThemeHelper('is', coreHelpers.is);
|
||||
registerThemeHelper('img_url', coreHelpers.img_url);
|
||||
registerThemeHelper('lang', coreHelpers.lang);
|
||||
registerThemeHelper('link', coreHelpers.link);
|
||||
registerThemeHelper('link_class', coreHelpers.link_class);
|
||||
registerThemeHelper('match', coreHelpers.match);
|
||||
registerThemeHelper('meta_description', coreHelpers.meta_description);
|
||||
registerThemeHelper('meta_title', coreHelpers.meta_title);
|
||||
registerThemeHelper('navigation', coreHelpers.navigation);
|
||||
registerThemeHelper('page_url', coreHelpers.page_url);
|
||||
registerThemeHelper('pagination', coreHelpers.pagination);
|
||||
registerThemeHelper('plural', coreHelpers.plural);
|
||||
registerThemeHelper('post_class', coreHelpers.post_class);
|
||||
registerThemeHelper('price', coreHelpers.price);
|
||||
registerThemeHelper('raw', coreHelpers.raw);
|
||||
registerThemeHelper('reading_time', coreHelpers.reading_time);
|
||||
registerThemeHelper('t', coreHelpers.t);
|
||||
registerThemeHelper('tags', coreHelpers.tags);
|
||||
registerThemeHelper('title', coreHelpers.title);
|
||||
registerThemeHelper('twitter_url', coreHelpers.twitter_url);
|
||||
registerThemeHelper('facebook_url', coreHelpers.facebook_url);
|
||||
registerThemeHelper('url', coreHelpers.url);
|
||||
|
||||
// Async theme helpers
|
||||
registerAsyncThemeHelper('ghost_head', coreHelpers.ghost_head);
|
||||
registerAsyncThemeHelper('next_post', coreHelpers.prev_post);
|
||||
registerAsyncThemeHelper('prev_post', coreHelpers.prev_post);
|
||||
registerAsyncThemeHelper('get', coreHelpers.get);
|
||||
};
|
||||
|
||||
module.exports = coreHelpers;
|
||||
module.exports.loadCoreHelpers = registerAllCoreHelpers;
|
@ -1,19 +0,0 @@
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
||||
const helperPath = path.join(__dirname, '../../../', 'helpers');
|
||||
|
||||
module.exports.getHelpers = () => {
|
||||
const helpers = {};
|
||||
|
||||
// We use glob here because it's already a dependency
|
||||
// If we want to get rid of glob we could use E.g. requiredir
|
||||
// Or require('fs').readdirSync(__dirname + '/')
|
||||
let helperFiles = glob.sync('!(index).js', {cwd: helperPath});
|
||||
helperFiles.forEach((helper) => {
|
||||
let name = helper.replace(/.js$/, '');
|
||||
helpers[name] = require(path.join(helperPath, helper));
|
||||
});
|
||||
|
||||
return helpers;
|
||||
};
|
@ -3,6 +3,5 @@ const active = require('./active');
|
||||
module.exports = {
|
||||
getActive: active.get,
|
||||
setActive: active.set,
|
||||
loadCoreHelpers: require('./handlebars/helpers').loadCoreHelpers,
|
||||
middleware: require('./middleware')
|
||||
};
|
||||
|
@ -114,8 +114,9 @@ module.exports = function setupSiteApp(options = {}) {
|
||||
// We do this here, at the top level, because helpers require so much stuff.
|
||||
// Moving this to being inside themes, where it probably should be requires the proxy to be refactored
|
||||
// Else we end up with circular dependencies
|
||||
themeEngine.loadCoreHelpers();
|
||||
debug('Helpers done');
|
||||
// themeEngine.loadCoreHelpers();
|
||||
// themeEngine.registerHandlebarsHelpers();
|
||||
// debug('Helpers done');
|
||||
|
||||
// Global handling for member session, ensures a member is logged in to the frontend
|
||||
siteApp.use(membersService.middleware.loadMemberSession);
|
||||
|
@ -1,6 +1,6 @@
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const helpers = require('../../../../core/frontend/services/theme-engine/handlebars/register');
|
||||
const helpers = require('../../../../core/frontend/services/helpers');
|
||||
const AppProxy = require('../../../../core/frontend/services/apps/proxy');
|
||||
const routing = require('../../../../core/frontend/services/routing');
|
||||
|
||||
@ -19,16 +19,17 @@ describe('Apps', function () {
|
||||
it('creates a ghost proxy', function () {
|
||||
const appProxy = AppProxy.getInstance('TestApp');
|
||||
|
||||
should.exist(appProxy.helpers);
|
||||
should.exist(appProxy.helpers.register);
|
||||
should.exist(appProxy.helpers.registerAsync);
|
||||
should.exist(appProxy.helperService);
|
||||
should.exist(appProxy.helperService.registerAlias);
|
||||
should.exist(appProxy.helperService.registerDir);
|
||||
should.exist(appProxy.helperService.registerHelper);
|
||||
});
|
||||
|
||||
it('allows helper registration', function () {
|
||||
const registerSpy = sinon.stub(helpers, 'registerThemeHelper');
|
||||
const registerSpy = sinon.stub(helpers, 'registerHelper');
|
||||
const appProxy = AppProxy.getInstance('TestApp');
|
||||
|
||||
appProxy.helpers.register('myTestHelper', sinon.stub().returns('test result'));
|
||||
appProxy.helperService.registerHelper('myTestHelper', sinon.stub().returns('test result'));
|
||||
|
||||
registerSpy.called.should.equal(true);
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ const _ = require('lodash');
|
||||
const hbs = require('../../../../../core/frontend/services/theme-engine/engine');
|
||||
|
||||
// Stuff we are testing
|
||||
const helpers = require('../../../../../core/frontend/services/theme-engine/handlebars/helpers');
|
||||
const helpers = require('../../../../../core/frontend/services/helpers');
|
||||
|
||||
describe('Helpers', function () {
|
||||
const hbsHelpers = ['each', 'if', 'unless', 'with', 'helperMissing', 'blockHelperMissing', 'log', 'lookup', 'block', 'contentFor'];
|
||||
@ -20,7 +20,7 @@ describe('Helpers', function () {
|
||||
describe('Load Core Helpers', function () {
|
||||
before(function () {
|
||||
hbs.express4();
|
||||
helpers.loadCoreHelpers();
|
||||
helpers.init();
|
||||
});
|
||||
|
||||
// This will work when we finish refactoring
|
||||
|
Loading…
Reference in New Issue
Block a user