Skip to content

Commit

Permalink
[STRF-9948] - paper-handlebars - resourceHints helper add new early-h…
Browse files Browse the repository at this point in the history
…ints flag param (#188)
  • Loading branch information
rafa-avila-bc authored Aug 22, 2022
1 parent 098eecb commit 9f405b9
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 49 deletions.
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);
};
};

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

const _ = require('lodash');

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

const fontProviders = {
'Google': {
/**
Expand Down Expand Up @@ -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
);
}
},
};

Expand All @@ -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
Expand Down Expand Up @@ -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(''));
Expand Down
51 changes: 34 additions & 17 deletions helpers/lib/resourceHints.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;
Expand All @@ -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': [
Expand All @@ -10,27 +16,38 @@ const fontResources = {
],
};

function format(host) {
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 = [];

// 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(''));
Expand Down
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) {
Expand All @@ -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 = {
Expand Down Expand Up @@ -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 = {
Expand Down
24 changes: 23 additions & 1 deletion spec/helpers/lib/resourceHints.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand All @@ -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();
});
});
});

0 comments on commit 9f405b9

Please sign in to comment.