From 7b888c943767cb062ca663903654b1c2f5350365 Mon Sep 17 00:00:00 2001 From: pimlie Date: Fri, 8 Mar 2019 13:33:24 +0100 Subject: [PATCH] refactor: optimize getMetaInfo by extracting functions this should make it easier for javascript engines to optimize these functions --- src/shared/constants.js | 24 ++++++++ src/shared/getMetaInfo.js | 125 ++++++-------------------------------- 2 files changed, 43 insertions(+), 106 deletions(-) diff --git a/src/shared/constants.js b/src/shared/constants.js index 42bcc0c..135ff92 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -2,6 +2,24 @@ * These are constant variables used throughout the application. */ +// set some sane defaults +export const defaultInfo = { + title: '', + titleChunk: '', + titleTemplate: '%s', + htmlAttrs: {}, + bodyAttrs: {}, + headAttrs: {}, + base: [], + link: [], + meta: [], + style: [], + script: [], + noscript: [], + __dangerouslyDisableSanitizers: [], + __dangerouslyDisableSanitizersByTagID: {} +} + // This is the name of the component option that contains all the information that // gets converted to the various meta tags & attributes for the page. export const keyName = 'metaInfo' @@ -35,6 +53,12 @@ export const metaInfoOptionKeys = [ '__dangerouslyDisableSanitizersByTagID' ] +// The metaInfo property keys which are used to disable escaping +export const disableOptionKeys = [ + '__dangerouslyDisableSanitizers', + '__dangerouslyDisableSanitizersByTagID' +] + // List of metaInfo property keys which only generates attributes and no tags export const metaInfoAttributeKeys = [ 'htmlAttrs', diff --git a/src/shared/getMetaInfo.js b/src/shared/getMetaInfo.js index 08f8eaa..06480a3 100644 --- a/src/shared/getMetaInfo.js +++ b/src/shared/getMetaInfo.js @@ -1,12 +1,9 @@ -import deepmerge from 'deepmerge' -import isPlainObject from 'lodash.isplainobject' -import { isFunction, isString } from './typeof' -import isArray from './isArray' +import applyTemplate from './applyTemplate' +import { defaultInfo, disableOptionKeys } from './constants' +import { ensureIsArray } from './ensure' +import escape from './escape' import getComponentOption from './getComponentOption' -const applyTemplate = (component, template, chunk) => - isFunction(template) ? template.call(component, chunk) : template.replace(/%s/g, chunk) - /** * Returns the correct meta info for the given component * (child components will overwrite parent meta info) @@ -15,74 +12,14 @@ const applyTemplate = (component, template, chunk) => * @return {Object} - returned meta info */ export default function getMetaInfo({ keyName, tagIDKeyName, metaTemplateKeyName, contentKeyName } = {}, component, escapeSequences = []) { - // set some sane defaults - const defaultInfo = { - title: '', - titleChunk: '', - titleTemplate: '%s', - htmlAttrs: {}, - bodyAttrs: {}, - headAttrs: {}, - meta: [], - base: [], - link: [], - style: [], - script: [], - noscript: [], - __dangerouslyDisableSanitizers: [], - __dangerouslyDisableSanitizersByTagID: {} - } - // collect & aggregate all metaInfo $options let info = getComponentOption({ - deep: true, component, keyName, metaTemplateKeyName, tagIDKeyName, - contentKeyName, - arrayMerge(target, source) { - // we concat the arrays without merging objects contained in, - // but we check for a `vmid` property on each object in the array - // using an O(1) lookup associative array exploit - // note the use of "for in" - we are looping through arrays here, not - // plain objects - const destination = [] - - for (const targetIndex in target) { - const targetItem = target[targetIndex] - let shared = false - - for (const sourceIndex in source) { - const sourceItem = source[sourceIndex] - - if (targetItem[tagIDKeyName] && targetItem[tagIDKeyName] === sourceItem[tagIDKeyName]) { - const targetTemplate = targetItem[metaTemplateKeyName] - const sourceTemplate = sourceItem[metaTemplateKeyName] - - if (targetTemplate && !sourceTemplate) { - sourceItem[contentKeyName] = applyTemplate(component, targetTemplate, sourceItem[contentKeyName]) - } - - // If template defined in child but content in parent - if (targetTemplate && sourceTemplate && !sourceItem[contentKeyName]) { - sourceItem[contentKeyName] = applyTemplate(component, sourceTemplate, targetItem[contentKeyName]) - delete sourceItem[metaTemplateKeyName] - } - - shared = true - break - } - } - - if (!shared) { - destination.push(targetItem) - } - } - - return destination.concat(source) - } - }) + contentKeyName + }, defaultInfo) // Remove all "template" tags from meta @@ -92,8 +29,8 @@ export default function getMetaInfo({ keyName, tagIDKeyName, metaTemplateKeyName } // replace title with populated template - if (info.titleTemplate) { - info.title = applyTemplate(component, info.titleTemplate, info.titleChunk || '') + if (info.titleTemplate && info.titleTemplate !== '%s') { + applyTemplate({ component, contentKeyName: 'title' }, info, info.titleTemplate, info.titleChunk || '') } // convert base tag to an array so it can be handled the same way @@ -102,47 +39,23 @@ export default function getMetaInfo({ keyName, tagIDKeyName, metaTemplateKeyName info.base = Object.keys(info.base).length ? [info.base] : [] } - const ref = info.__dangerouslyDisableSanitizers - const refByTagID = info.__dangerouslyDisableSanitizersByTagID - - // sanitizes potentially dangerous characters - const escape = info => Object.keys(info).reduce((escaped, key) => { - let isDisabled = ref && ref.includes(key) - const tagID = info[tagIDKeyName] - - if (!isDisabled && tagID) { - isDisabled = refByTagID && refByTagID[tagID] && refByTagID[tagID].includes(key) + for (const index in disableOptionKeys) { + const disableKey = disableOptionKeys[index] + if (!info[disableKey]) { + continue } - const val = info[key] - escaped[key] = val - - if (key === '__dangerouslyDisableSanitizers' || key === '__dangerouslyDisableSanitizersByTagID') { - return escaped - } - - if (!isDisabled) { - if (isString(val)) { - escaped[key] = escapeSequences.reduce((val, [v, r]) => val.replace(v, r), val) - } else if (isPlainObject(val)) { - escaped[key] = escape(val) - } else if (isArray(val)) { - escaped[key] = val.map(escape) - } else { - escaped[key] = val + if (index === 0) { + ensureIsArray(info, disableKey) + } else if (index === 1) { + for (const key in info[disableKey]) { + ensureIsArray(info[disableKey], key) } - } else { - escaped[key] = val } - - return escaped - }, {}) - - // merge with defaults - info = deepmerge(defaultInfo, info) + } // begin sanitization - info = escape(info) + info = escape(info, { tagIDKeyName }, escapeSequences) return info }