0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-25 02:31:59 -05:00

Divided f/e proxy into true proxy + rendering service

- The original intention of the proxy was to collect up all the requires in our helpers into one place
- This has since been expanded and used in more places, in more ways
- In hindsight there are now multiple different types of requires in the proxy:
   - One: true frontend rendering framework requires (stuff from deep inside theme-engine)
   - Two: data manipulation/sdk stuff, belongs to the frontend, ways to process API data
   - Three: actual core stuff from Ghost, that we wish wasn't here / needs to be passed in a controlled way
- This commit pulls out One into a new rendering service, so at least that stuff is managed independently
- This draws the lines clearly between what's internal to the frontend and what isn't
- It also highlights that the theme-engine needs to be divided up / refactored so that we don't have these deep requires
This commit is contained in:
Hannah Wolfe 2021-09-28 15:06:33 +01:00
parent b89a4c23a2
commit fd20f90cca
No known key found for this signature in database
GPG key ID: 9F8C7532D0A6BA55
43 changed files with 99 additions and 70 deletions

View file

@ -2,8 +2,8 @@
// Usage: `{{amp_analytics}}`
//
// Outputs inline scripts used for analytics
const {SafeString, settingsCache} = require('../../../../services/proxy');
const {settingsCache} = require('../../../../services/proxy');
const {SafeString} = require('../../../../services/rendering');
function ampComponents() {
let components = [];

View file

@ -8,7 +8,8 @@
// By default supported AMP HTML tags (no additional script tag necessary):
// amp-img, amp-ad, amp-embed, amp-video and amp-pixel.
// (less) dirty requires
const {SafeString, settingsCache} = require('../../../../services/proxy');
const {settingsCache} = require('../../../../services/proxy');
const {SafeString} = require('../../../../services/rendering');
function ampComponents() {
let components = [];

View file

@ -12,7 +12,7 @@ const moment = require('moment');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');
const {SafeString} = require('../../../../services/proxy');
const {SafeString} = require('../../../../services/rendering');
const amperizeCache = {};
let allowedAMPTags = [];

View file

@ -1,4 +1,4 @@
const {SafeString, escapeExpression} = require('../../../../services/proxy');
const {SafeString, escapeExpression} = require('../../../../services/rendering');
module.exports = function amp_style(options) { // eslint-disable-line camelcase
if (options.data.site.accent_color) {

View file

@ -4,7 +4,7 @@
// Password input used on private.hbs for password-protected blogs
// (less) dirty requires
const {SafeString, templates} = require('../../../../services/proxy');
const {SafeString, templates} = require('../../../../services/rendering');
// We use the name input_password to match the helper for consistency:
module.exports = function input_password(options) { // eslint-disable-line camelcase

View file

@ -2,7 +2,8 @@
// Usage: `{{asset "css/screen.css"}}`
//
// Returns the path to the specified asset.
const {SafeString, metaData} = require('../services/proxy');
const {metaData} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

View file

@ -9,16 +9,18 @@
//
// Block helper: `{{#author}}{{/author}}`
// This is the default handlebars behaviour of dropping into the author object scope
const {urlService, SafeString, escapeExpression, hbs, templates} = require('../services/proxy');
const buildInHelpers = hbs.handlebars.helpers;
const {urlService} = require('../services/proxy');
const {SafeString, escapeExpression, hbs, templates} = require('../services/rendering');
const isString = require('lodash/isString');
const builtInHelpers = hbs.handlebars.helpers;
/**
* @deprecated: single authors was superceded by multiple authors in Ghost 1.22.0
*/
module.exports = function author(options) {
if (options.fn) {
return buildInHelpers.with.call(this, this.author, options);
return builtInHelpers.with.call(this, this.author, options);
}
const autolink = isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true;

View file

@ -6,9 +6,10 @@
// By default, authors are separated by commas.
//
// Note that the standard {{#each authors}} implementation is unaffected by this helper.
const {urlService} = require('../services/proxy');
const {SafeString, escapeExpression, templates} = require('../services/rendering');
const isString = require('lodash/isString');
const {SafeString, escapeExpression, templates, urlService} = require('../services/proxy');
const ghostHelperUtils = require('@tryghost/helpers').utils;
const {utils} = require('@tryghost/helpers');
module.exports = function authors(options = {}) {
options.hash = options.hash || {};
@ -38,7 +39,7 @@ module.exports = function authors(options = {}) {
}) : escapeExpression(author.name);
}
return ghostHelperUtils.visibility.filter(authorsList, visibility, processAuthor);
return utils.visibility.filter(authorsList, visibility, processAuthor);
}
if (this.authors && this.authors.length) {

View file

@ -2,7 +2,7 @@
// Usage: `{{body_class}}`
//
// Output classes for the body element
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
// We use the name body_class to match the helper for consistency
module.exports = function body_class(options) { // eslint-disable-line camelcase

View file

@ -5,7 +5,8 @@
// Outputs cancel/renew links to manage subscription renewal after the subscription period ends.
//
// Defaults to class="cancel-subscription-link" errorClass="cancel-subscription-error" cancelLabel="Cancel subscription" continueLabel="Continue subscription"
const {templates, labs} = require('../services/proxy');
const {labs} = require('../services/proxy');
const {templates} = require('../services/rendering');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

View file

@ -1,4 +1,4 @@
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
module.exports = function concat(...args) {
const options = args.pop();

View file

@ -10,7 +10,7 @@
//
// Dev flag feature: In case of restricted content access for member-only posts, shows CTA box
const {templates, hbs, SafeString} = require('../services/proxy');
const {templates, hbs, SafeString} = require('../services/rendering');
const downsize = require('downsize');
const _ = require('lodash');
const createFrame = hbs.handlebars.createFrame;

View file

@ -3,7 +3,7 @@
//
// Formats a date using moment-timezone.js. Formats published_at by default but will also take a date as a parameter
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const moment = require('moment-timezone');
const _ = require('lodash');

View file

@ -4,7 +4,7 @@
//
// Returns URI encoded string
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
module.exports = function encode(string, options) {
const uri = string || options;

View file

@ -5,7 +5,8 @@
//
// Defaults to words="50"
const {SafeString, metaData} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const {metaData} = require('../services/proxy');
const _ = require('lodash');
const getMetaDataExcerpt = metaData.getMetaDataExcerpt;

View file

@ -2,7 +2,8 @@
// Usage: `{{facebook_url}}` or `{{facebook_url author.facebook}}`
//
// Output a url for a facebook username
const {socialUrls, localUtils} = require('../services/proxy');
const {socialUrls} = require('../services/proxy');
const {localUtils} = require('../services/rendering');
// We use the name facebook_url to match the helper for consistency:
module.exports = function facebook_url(username, options) { // eslint-disable-line camelcase

View file

@ -2,7 +2,8 @@
// Usage: `{{#foreach data}}{{/foreach}}`
//
// Block helper designed for looping through posts
const {hbs, checks} = require('../services/proxy');
const {checks} = require('../services/proxy');
const {hbs} = require('../services/rendering');
const _ = require('lodash');
const logging = require('@tryghost/logging');

View file

@ -1,7 +1,8 @@
// # Get Helper
// Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}`
// Fetches data from the API
const {config, hbs, api, prepareContextResource} = require('../services/proxy');
const {config, api, prepareContextResource} = require('../services/proxy');
const {hbs} = require('../services/rendering');
const logging = require('@tryghost/logging');
const errors = require('@tryghost/errors');

View file

@ -2,7 +2,8 @@
// Usage: `{{ghost_foot}}`
//
// Outputs scripts and other assets at the bottom of a Ghost theme
const {SafeString, settingsCache} = require('../services/proxy');
const {settingsCache} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const _ = require('lodash');
// We use the name ghost_foot to match the helper for consistency:

View file

@ -2,7 +2,8 @@
// Usage: `{{ghost_head}}`
//
// Outputs scripts and other assets at the top of a Ghost theme
const {metaData, escapeExpression, SafeString, settingsCache, config, blogIcon, urlUtils, labs} = require('../services/proxy');
const {metaData, settingsCache, config, blogIcon, urlUtils, labs} = require('../services/proxy');
const {escapeExpression, SafeString} = require('../services/rendering');
const logging = require('@tryghost/logging');
const _ = require('lodash');

View file

@ -12,7 +12,7 @@
// Language tags in HTML and XML
// https://www.w3.org/International/articles/language-tags/
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
module.exports = function lang(options) {
const locale = options.data.site.locale;

View file

@ -1,5 +1,6 @@
// # link helper
const {config, SafeString, localUtils} = require('../services/proxy');
const {config} = require('../services/proxy');
const {SafeString, localUtils} = require('../services/rendering');
const _ = require('lodash');
const errors = require('@tryghost/errors');

View file

@ -1,5 +1,6 @@
// # link_class helper
const {config, SafeString, localUtils} = require('../services/proxy');
const {config} = require('../services/proxy');
const {SafeString, localUtils} = require('../services/rendering');
const _ = require('lodash');
const errors = require('@tryghost/errors');

View file

@ -1,4 +1,5 @@
const {SafeString, labs} = require('../services/proxy');
const {labs} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const logging = require('@tryghost/logging');
const tpl = require('@tryghost/tpl');

View file

@ -1,7 +1,7 @@
// ### Navigation Helper
// `{{navigation}}`
// Outputs navigation menu of static urls
const {SafeString, templates, hbs} = require('../services/proxy');
const {SafeString, templates, hbs} = require('../services/rendering');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

View file

@ -1,7 +1,7 @@
// ### Pagination Helper
// `{{pagination}}`
// Outputs previous and next buttons, along with info about the current page
const {templates, hbs} = require('../services/proxy');
const {templates, hbs} = require('../services/rendering');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

View file

@ -9,7 +9,7 @@
// The 2nd argument is the string that will be output if the variable's value is 0
// The 3rd argument is the string that will be output if the variable's value is 1
// The 4th argument is the string that will be output if the variable's value is 2+
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

View file

@ -2,7 +2,7 @@
// Usage: `{{post_class}}`
//
// Output classes for the body element
const {SafeString} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
// We use the name post_class to match the helper for consistency:
module.exports = function post_class() { // eslint-disable-line camelcase

View file

@ -2,7 +2,8 @@
// Example usages
// `{{#prev_post}}<a href ="{{url}}>previous post</a>{{/prev_post}}'
// `{{#next_post}}<a href ="{{url absolute="true">next post</a>{{/next_post}}'
const {api, hbs, checks} = require('../services/proxy');
const {api, checks} = require('../services/proxy');
const {hbs} = require('../services/rendering');
const logging = require('@tryghost/logging');
const tpl = require('@tryghost/tpl');

View file

@ -3,7 +3,8 @@
//
// Returns a string of the products with access to the post.
// By default, products are separated by commas.
const {SafeString, labs} = require('../services/proxy');
const {labs} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const nql = require('@nexes/nql');
const isString = require('lodash/isString');

View file

@ -10,9 +10,10 @@
//
// Returns estimated reading time for post
const {SafeString, checks} = require('../services/proxy');
const {checks} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const calculateReadingTime = require('@tryghost/helpers').readingTime;
const {readingTime: calculateReadingTime} = require('@tryghost/helpers');
module.exports = function reading_time(options) {// eslint-disable-line camelcase
options = options || {};

View file

@ -10,7 +10,7 @@
// because often other helpers need that (t) returns a string to be able to work as subexpression; e.g.:
// {{tags prefix=(t " on ")}}
const {themeI18n} = require('../services/proxy');
const {themeI18n} = require('../services/rendering');
module.exports = function t(text, options) {
const bindings = {};

View file

@ -5,7 +5,8 @@
// By default, tags are separated by commas.
//
// Note that the standard {{#each tags}} implementation is unaffected by this helper
const {urlService, SafeString, escapeExpression, templates} = require('../services/proxy');
const {urlService} = require('../services/proxy');
const {SafeString, escapeExpression, templates} = require('../services/rendering');
const isString = require('lodash/isString');
const ghostHelperUtils = require('@tryghost/helpers').utils;

View file

@ -3,7 +3,7 @@
//
// Overrides the standard behaviour of `{[title}}` to ensure the content is correctly escaped
const {SafeString, escapeExpression} = require('../services/proxy');
const {SafeString, escapeExpression} = require('../services/rendering');
module.exports = function title() {
return new SafeString(escapeExpression(this.title || ''));

View file

@ -2,7 +2,8 @@
// Usage: `{{twitter_url}}` or `{{twitter_url author.twitter}}`
//
// Output a url for a twitter username
const {socialUrls, localUtils} = require('../services/proxy');
const {socialUrls} = require('../services/proxy');
const {localUtils} = require('../services/rendering');
// We use the name twitter_url to match the helper for consistency:
module.exports = function twitter_url(username, options) { // eslint-disable-line camelcase

View file

@ -4,7 +4,8 @@
// Returns the URL for the current object scope i.e. If inside a post scope will return post permalink
// `absolute` flag outputs absolute URL, else URL is relative
const {SafeString, metaData} = require('../services/proxy');
const {metaData} = require('../services/proxy');
const {SafeString} = require('../services/rendering');
const {getMetaDataUrl} = metaData;

View file

@ -1,25 +1,11 @@
// This file contains everything that the helpers and frontend apps require from the core of Ghost
const hbs = require('./theme-engine/engine');
const settingsCache = require('../../shared/settings-cache');
const config = require('../../shared/config');
// Require from the rendering framework
const {SafeString} = require('./rendering');
module.exports = {
/**
* Section one: Frontend Framework
* These all belong to the frontend rendering layer
*/
hbs: hbs,
SafeString: hbs.SafeString,
escapeExpression: hbs.escapeExpression,
// The local template thing, should this be merged with the channels one?
templates: require('./theme-engine/handlebars/template'),
// Theme i18n is separate to common i18n
themeI18n: require('./theme-engine/i18n'),
// TODO: these need a more sensible home
localUtils: require('./theme-engine/handlebars/utils'),
/**
* Section two: data manipulation
* Stuff that modifies API data (SDK layer)
@ -32,7 +18,7 @@ module.exports = {
(Array.isArray(data) ? data : [data]).forEach((resource) => {
// feature_image_caption contains HTML, making it a SafeString spares theme devs from triple-curlies
if (resource.feature_image_caption) {
resource.feature_image_caption = new hbs.SafeString(resource.feature_image_caption);
resource.feature_image_caption = new SafeString(resource.feature_image_caption);
}
});
},

View file

@ -0,0 +1,24 @@
/**
* 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.
*
* This at least keeps the deep requires in a single place.
*/
const hbs = require('./theme-engine/engine');
module.exports = {
hbs: hbs,
SafeString: hbs.SafeString,
escapeExpression: hbs.escapeExpression,
// The local template thing, should this be merged with the channels one?
templates: require('./theme-engine/handlebars/template'),
// Theme i18n is separate to common i18n
themeI18n: require('./theme-engine/i18n'),
// TODO: these need a more sensible home
localUtils: require('./theme-engine/handlebars/utils')
};

View file

@ -1,7 +1,7 @@
const should = require('should');
const sinon = require('sinon');
const Promise = require('bluebird');
const {SafeString} = require('../../../core/frontend/services/proxy');
const {SafeString} = require('../../../core/frontend/services/rendering');
// Stuff we are testing
const helpers = require('../../../core/frontend/helpers');

View file

@ -1,8 +1,7 @@
const should = require('should');
const sinon = require('sinon');
const helpers = require('../../../core/frontend/helpers');
const proxy = require('../../../core/frontend/services/proxy');
const settingsCache = proxy.settingsCache;
const {settingsCache} = require('../../../core/frontend/services/proxy');
describe('{{ghost_foot}} helper', function () {
let settingsCacheStub;

View file

@ -12,8 +12,7 @@ const imageLib = require('../../../core/server/lib/image');
const routing = require('../../../core/frontend/services/routing');
const urlService = require('../../../core/frontend/services/url');
const helpers = require('../../../core/frontend/helpers');
const proxy = require('../../../core/frontend/services/proxy');
const settingsCache = proxy.settingsCache;
const {settingsCache} = require('../../../core/frontend/services/proxy');
describe('{{ghost_head}} helper', function () {
let posts = [];

View file

@ -1,7 +1,7 @@
const should = require('should');
const testUtils = require('../../../../utils');
const helpers = require('../../../../../core/frontend/services/routing/helpers');
const {SafeString} = require('../../../../../core/frontend/services/proxy');
const {SafeString} = require('../../../../../core/frontend/services/rendering');
describe('Unit - services/routing/helpers/format-response', function () {
let posts;

View file

@ -1,12 +1,12 @@
const should = require('should');
const errors = require('@tryghost/errors');
const proxy = require('../../../../../core/frontend/services/proxy');
const {hbs, templates} = require('../../../../../core/frontend/services/rendering');
describe('Helpers Template', function () {
it('can execute a template', function () {
proxy.hbs.registerPartial('test', '<h1>Hello {{name}}</h1>');
hbs.registerPartial('test', '<h1>Hello {{name}}</h1>');
const safeString = proxy.templates.execute('test', {name: 'world'});
const safeString = templates.execute('test', {name: 'world'});
should.exist(safeString);
safeString.should.have.property('string').and.equal('<h1>Hello world</h1>');
@ -14,7 +14,7 @@ describe('Helpers Template', function () {
it('will throw an IncorrectUsageError if the partial does not exist', function () {
should.throws(() => {
proxy.templates.execute('non-existent');
templates.execute('non-existent');
}, errors.IncorrectUsageError);
});
});