gatsby
Version:
Blazing fast modern site generator for React
117 lines (97 loc) • 3.5 kB
JavaScript
;
exports.__esModule = true;
exports.diffNodes = diffNodes;
exports.filterHeadProps = filterHeadProps;
exports.headExportValidator = headExportValidator;
exports.isEqualNode = isEqualNode;
exports.warnForInvalidTags = warnForInvalidTags;
var _constants = require("./constants");
/**
* Filter the props coming from a page down to just the ones that are relevant for head.
* This e.g. filters out properties that are undefined during SSR.
*/
function filterHeadProps(input) {
return {
location: {
pathname: input.location.pathname
},
params: input.params,
data: input.data || {},
pageContext: input.pageContext
};
}
/**
* Throw error if Head export is not a valid
*/
function headExportValidator(head) {
if (typeof head !== `function`) throw new Error(`Expected "Head" export to be a function got "${typeof head}".`);
}
/**
* Warn once for same messsage
*/
let warnOnce = _ => {};
if (process.env.NODE_ENV !== `production`) {
const warnings = new Set();
warnOnce = msg => {
if (!warnings.has(msg)) {
console.warn(msg);
}
warnings.add(msg);
};
}
/**
* Warn for invalid tags in head.
* @param {string} tagName
*/
function warnForInvalidTags(tagName) {
if (process.env.NODE_ENV !== `production`) {
const warning = `<${tagName}> is not a valid head element. Please use one of the following: ${_constants.VALID_NODE_NAMES.join(`, `)}`;
warnOnce(warning);
}
}
/**
* When a `nonce` is present on an element, browsers such as Chrome and Firefox strip it out of the
* actual HTML attributes for security reasons *when the element is added to the document*. Thus,
* given two equivalent elements that have nonces, `Element,isEqualNode()` will return false if one
* of those elements gets added to the document. Although the `element.nonce` property will be the
* same for both elements, the one that was added to the document will return an empty string for
* its nonce HTML attribute value.
*
* This custom `isEqualNode()` function therefore removes the nonce value from the `newTag` before
* comparing it to `oldTag`, restoring it afterwards.
*
* For more information, see:
* https://bugs.chromium.org/p/chromium/issues/detail?id=1211471#c12
*/
function isEqualNode(oldTag, newTag) {
if (oldTag instanceof HTMLElement && newTag instanceof HTMLElement) {
const nonce = newTag.getAttribute(`nonce`); // Only strip the nonce if `oldTag` has had it stripped. An element's nonce attribute will not
// be stripped if there is no content security policy response header that includes a nonce.
if (nonce && !oldTag.getAttribute(`nonce`)) {
const cloneTag = newTag.cloneNode(true);
cloneTag.setAttribute(`nonce`, ``);
cloneTag.nonce = nonce;
return nonce === oldTag.nonce && oldTag.isEqualNode(cloneTag);
}
}
return oldTag.isEqualNode(newTag);
}
function diffNodes({
oldNodes,
newNodes,
onStale,
onNew
}) {
for (const existingHeadElement of oldNodes) {
const indexInNewNodes = newNodes.findIndex(e => isEqualNode(e, existingHeadElement));
if (indexInNewNodes === -1) {
onStale(existingHeadElement);
} else {
// this node is re-created as-is, so we keep old node, and remove it from list of new nodes (as we handled it already here)
newNodes.splice(indexInNewNodes, 1);
}
} // remaing new nodes didn't have matching old node, so need to be added
for (const newNode of newNodes) {
onNew(newNode);
}
}