Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[STRF-9948] - paper-handlebars - resourceHints helper add new early-hints flag param #188

Merged
merged 9 commits into from
Aug 22, 2022
10 changes: 9 additions & 1 deletion helpers/getFontsCollection.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
'use strict';

const getFonts = require('./lib/fonts');
const utils = require('handlebars-utils');

const factory = globals => {
return function() {
const options = arguments[arguments.length - 1];
const fontDisplay = options.hash['font-display'];
return getFonts('linkElements', globals.getThemeSettings(), globals.handlebars, {fontDisplay});

const getFontsOptions = utils.isString(options.hash.resourceHint) ? {
globals,
state: options.hash.resourceHint,
fontDisplay
} : {fontDisplay};

return getFonts('linkElements', globals.getThemeSettings(), globals.handlebars, getFontsOptions);
};
};

21 changes: 19 additions & 2 deletions helpers/lib/fonts.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

const _ = require('lodash');

const {resourceHintAllowedTypes, addResourceHint} = require('../lib/resourceHints');

const fontProviders = {
'Google': {
/**
@@ -68,6 +70,18 @@ const fontProviders = {
}
};
},

generateResourceHints: function (globals, state, fonts, fontDisplay) {
const displayTypes = ['auto', 'block', 'swap', 'fallback', 'optional'];
fontDisplay = displayTypes.includes(fontDisplay) ? fontDisplay : 'swap';
const path = `https://fonts.googleapis.com/css?family=${fonts.join('|')}&display=${fontDisplay}`;
addResourceHint(
globals,
path,
state,
resourceHintAllowedTypes.resourceHintFontType
);
}
},
};

@@ -84,7 +98,7 @@ const fontProviders = {
* @returns {Object.<string, Array>|string}
*/
module.exports = function(format, themeSettings, handlebars, options) {

const collectedFonts = {};
_.each(themeSettings, function(value, key) {
//check that -font is on end of string but not start of string
@@ -115,8 +129,11 @@ module.exports = function(format, themeSettings, handlebars, options) {
// Format output based on requested format
switch(format) {
case 'linkElements':

const formattedFonts = _.mapValues(parsedFonts, function(value, key) {
if (options.globals && options.state) {
fontProviders[key].generateResourceHints(options.globals, options.state, value, options.fontDisplay);
}
return fontProviders[key].buildLink(value, options.fontDisplay);
});
return new handlebars.SafeString(_.values(formattedFonts).join(''));
51 changes: 34 additions & 17 deletions helpers/lib/resourceHints.js
Original file line number Diff line number Diff line change
@@ -2,21 +2,29 @@ const utils = require("handlebars-utils");

const resourceHintsLimit = 50;

const defaultResourceHintState = 'preload';

const resourceHintStates = [defaultResourceHintState, 'preconnect', 'prefetch'];
const preloadResourceHintState = 'preload';
const preconnectResourceHintState = 'preconnect';
const prerenderResourceHintState = 'prerender';
const dnsPrefetchResourceHintState = 'dns-prefetch';
const resourceHintStates = [preloadResourceHintState, preconnectResourceHintState, prerenderResourceHintState, dnsPrefetchResourceHintState];

const resourceHintFontType = 'font';
const resourceHintStyleType = 'style';
const resourceHintScriptType = 'script';
const resourceHintAllowedTypes = [resourceHintStyleType, resourceHintFontType, resourceHintScriptType];
const resourceHintTypes = [resourceHintStyleType, resourceHintFontType, resourceHintScriptType];

const noCors = 'no';
const anonymousCors = 'anonymous';
const useCredentialsCors = 'use-credentials';
const allowedCors = [noCors, anonymousCors, useCredentialsCors];

/**
* @param {string} path - The uri to the resource.
* @param {string} state - 'preload' or 'preconnect'
* @param {string} type - any of [style, font, script]
* @param {string} state - any of [preload, preconnect, prerender, dns-prefetch]
* @param {string} type? - any of [style, font, script] If an invalid value is provided, property won't be included
* @param {string} cors? - any of [no, anonymous, use-credentials] defaults to no when no value is provided
*/
function addResourceHint(globals, path, state, type) {
function addResourceHint(globals, path, state, type, cors) {

if (!utils.isString(path)) {
throw new Error('Invalid path provided. path should be a non empty string');
@@ -31,7 +39,6 @@ function addResourceHint(globals, path, state, type) {
globals.resourceHints = [];
}

path = utils.escapeExpression(path);
let index = globals.resourceHints.findIndex(({src}) => path === src);
if (index >= 0) {
return;
@@ -41,22 +48,32 @@ function addResourceHint(globals, path, state, type) {
return;
}

let value = {src: path, state};
let hint = {src: path, state};

if (typeof type !== 'string') {
type = '';
if (utils.isString(type) && resourceHintTypes.includes(type)) {
hint.type = type;
}
type = type.trim();
if (type !== '' && resourceHintAllowedTypes.includes(type)) {
value.type = type;

if (utils.isString(cors) && !allowedCors.includes(cors)) {
throw new Error(`Invalid cors value provided. Valid values are: ${allowedCors}`);
} else if (!utils.isString(cors)) {
cors = noCors;
}
hint.cors = cors;

globals.resourceHints.push(value);
globals.resourceHints.push(hint);
}

module.exports = {
resourceHintsLimit,
defaultResourceHintState,
defaultResourceHintState: preloadResourceHintState,
addResourceHint,
resourceHintAllowedTypes: {resourceHintStyleType, resourceHintFontType, resourceHintScriptType}
resourceHintAllowedTypes: {resourceHintStyleType, resourceHintFontType, resourceHintScriptType},
resourceHintAllowedCors: {noCors, anonymousCors, useCredentialsCors},
resourceHintAllowedStates: {
preloadResourceHintState,
preconnectResourceHintState,
prerenderResourceHintState,
dnsPrefetchResourceHintState
}
};
35 changes: 26 additions & 9 deletions helpers/resourceHints.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
'use strict';

const _ = require('lodash');
const utils = require('handlebars-utils');
const getFonts = require('./lib/fonts');
const {
addResourceHint,
resourceHintAllowedStates,
resourceHintAllowedTypes,
resourceHintAllowedCors
} = require('./lib/resourceHints');

const fontResources = {
'Google': [
@@ -10,27 +16,38 @@ const fontResources = {
],
};

function format(host) {
rafa-avila-bc marked this conversation as resolved.
Show resolved Hide resolved
return `<link rel="dns-prefetch preconnect" href="${host}" crossorigin>`;
}

const factory = globals => {
return function() {
function format(host) {
return `<link rel="dns-prefetch preconnect" href="${host}" crossorigin>`;
}
return function () {

var hosts = [];
let hosts = [];
rafa-avila-bc marked this conversation as resolved.
Show resolved Hide resolved

// Add cdn
const siteSettings = globals.getSiteSettings();
const cdnUrl = siteSettings['cdn_url'] || '';
if (cdnUrl != '') {
if (utils.isString(cdnUrl)) {
hosts.push(cdnUrl);
}

// Add font providers
const fontProviders = _.keys(getFonts('providerLists', globals.getThemeSettings(), globals.handlebars));
_.each(fontProviders, function(provider) {
const fonts = getFonts('providerLists', globals.getThemeSettings(), globals.handlebars);
for (const provider in fonts) {
if (typeof fontResources[provider] !== 'undefined') {
hosts = hosts.concat(fontResources[provider]);
}
}

hosts.forEach(host => {
addResourceHint(
globals,
host,
resourceHintAllowedStates.dnsPrefetchResourceHintState,
resourceHintAllowedTypes.resourceHintFontType,
resourceHintAllowedCors.noCors
);
});

return new globals.handlebars.SafeString(hosts.map(host => format(host)).join(''));
41 changes: 28 additions & 13 deletions spec/helpers/getFontsCollection.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const Lab = require('lab'),
lab = exports.lab = Lab.script(),
describe = lab.experiment,
it = lab.it,
testRunner = require('../spec-helpers').testRunner;
lab = exports.lab = Lab.script(),
describe = lab.experiment,
it = lab.it,
{testRunner, buildRenderer} = require('../spec-helpers');
const {expect} = require("code");
const {resourceHintAllowedTypes, resourceHintAllowedStates} = require('../../helpers/lib/resourceHints');

describe('getFontsCollection', function () {
it('should return a font link with fonts from theme settings and &display=swap when no font-display value is passed', function (done) {
@@ -17,14 +19,22 @@ describe('getFontsCollection', function () {
'test8-font': 'Google_Crimson+Text_400,700_sans',
'random-property': 'not a font'
};

const runTestCases = testRunner({themeSettings});
const renderer = buildRenderer({}, themeSettings);
const runTestCases = testRunner({renderer});
const href = "https://fonts.googleapis.com/css?family=Open+Sans:,400italic,700|Karla:700|Lora:400|Volkron:|Droid:400,700|Crimson+Text:400,700&display=swap";
runTestCases([
{
input: '{{getFontsCollection}}',
output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:,400italic,700|Karla:700|Lora:400|Volkron:|Droid:400,700|Crimson+Text:400,700&display=swap" rel="stylesheet">',
input: '{{getFontsCollection resourceHint="preload"}}',
output: `<link href="${href}" rel="stylesheet">`,
},
], done);
], () => {
const hints = renderer.getResourceHints();
expect(hints).to.have.length(1);
expect(hints[0].src).to.equals(href);
expect(hints[0].state).to.equals(resourceHintAllowedStates.preloadResourceHintState);
expect(hints[0].type).to.equals(resourceHintAllowedTypes.resourceHintFontType);
done();
});
});
it('should return a font link with fonts from theme settings and &display=swap when font-display value passed is invalid', function (done) {
const themeSettings = {
@@ -74,14 +84,19 @@ describe('getFontsCollection', function () {
'test2-font': 'Google_',
'test3-font': 'Google'
};

const runTestCases = testRunner({themeSettings});
const renderer = buildRenderer({}, themeSettings);
const runTestCases = testRunner({renderer});
runTestCases([
{
input: '{{getFontsCollection}}',
input: '{{getFontsCollection resourceHint="preconnect"}}',
output: '<link href="https://fonts.googleapis.com/css?family=Open+Sans:&display=swap" rel="stylesheet">',
},
], done);
], () => {
const hints = renderer.getResourceHints();
expect(hints).to.have.length(1);
expect(hints[0].state).to.equals(resourceHintAllowedStates.preconnectResourceHintState);
done();
});
});
it('should not crash if a malformed Google font is passed when valid font-display value is passed', function (done) {
const themeSettings = {
24 changes: 23 additions & 1 deletion spec/helpers/lib/resourceHints.js
Original file line number Diff line number Diff line change
@@ -20,7 +20,24 @@ describe('resource hints', function () {
const src = '/my/styles.css';
const type = 'style';
const state = 'preload';
const expected = {src, state, type};
const cors = 'anonymous';
const expected = {src, state, type, cors};

addResourceHint(globals, src, state, type, cors);

expect(globals.resourceHints).to.have.length(1);
expect(globals.resourceHints[0]).to.equals(expected);

done();
});

it("creates resource hints when valid params are provided defaulting cors to no when no provided", (done) => {
const globals = {};

const src = '/my/styles.css';
const type = 'style';
const state = 'preload';
const expected = {src, state, type, cors: 'no'};

addResourceHint(globals, src, state, type);

@@ -119,5 +136,10 @@ describe('resource hints', function () {
done();
});

it('should throw when invalid cors value is provided', done => {
const f = () => addResourceHint({}, '/theme.css', 'style', 'preload', 'invalid-cors');
expect(f).to.throw();
done();
});
});
});
28 changes: 22 additions & 6 deletions spec/helpers/resourceHints.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const Lab = require('lab'),
lab = exports.lab = Lab.script(),
describe = lab.experiment,
it = lab.it,
testRunner = require('../spec-helpers').testRunner;
lab = exports.lab = Lab.script(),
describe = lab.experiment,
it = lab.it,
{testRunner, buildRenderer} = require('../spec-helpers');
const {expect} = require("code");
const {
resourceHintAllowedCors,
resourceHintAllowedStates,
resourceHintAllowedTypes
} = require('../../helpers/lib/resourceHints');

describe('resourceHints', function () {
it('should return the expected resource links', function (done) {
@@ -18,13 +24,23 @@ describe('resourceHints', function () {
'random-property': 'not a font'
};

const runTestCases = testRunner({themeSettings});
const renderer = buildRenderer({}, themeSettings);
const runTestCases = testRunner({renderer});

runTestCases([
{
input: '{{resourceHints}}',
output: '<link rel="dns-prefetch preconnect" href="https://fonts.googleapis.com" crossorigin><link rel="dns-prefetch preconnect" href="https://fonts.gstatic.com" crossorigin>',
},
], done);
], () => {
const hints = renderer.getResourceHints();
expect(hints).to.have.length(2);
hints.forEach(hint => {
expect(hint.cors).to.equals(resourceHintAllowedCors.noCors);
expect(hint.type).to.equals(resourceHintAllowedTypes.resourceHintFontType);
expect(hint.state).to.equals(resourceHintAllowedStates.dnsPrefetchResourceHintState);
});
done();
});
});
});