UNPKG

react-helmet-async

Version:

Thread-safe Helmet for React 16+ and friends

159 lines (139 loc) 4.99 kB
import React from 'react'; import { HELMET_ATTRIBUTE, TAG_NAMES, REACT_TAG_MAP, TAG_PROPERTIES, ATTRIBUTE_NAMES, } from './constants'; import { flattenArray } from './utils'; const SELF_CLOSING_TAGS = [TAG_NAMES.NOSCRIPT, TAG_NAMES.SCRIPT, TAG_NAMES.STYLE]; const encodeSpecialCharacters = (str, encode = true) => { if (encode === false) { return String(str); } return String(str) .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#x27;'); }; const generateElementAttributesAsString = attributes => Object.keys(attributes).reduce((str, key) => { const attr = typeof attributes[key] !== 'undefined' ? `${key}="${attributes[key]}"` : `${key}`; return str ? `${str} ${attr}` : attr; }, ''); const generateTitleAsString = (type, title, attributes, encode) => { const attributeString = generateElementAttributesAsString(attributes); const flattenedTitle = flattenArray(title); return attributeString ? `<${type} ${HELMET_ATTRIBUTE}="true" ${attributeString}>${encodeSpecialCharacters( flattenedTitle, encode )}</${type}>` : `<${type} ${HELMET_ATTRIBUTE}="true">${encodeSpecialCharacters( flattenedTitle, encode )}</${type}>`; }; const generateTagsAsString = (type, tags, encode) => tags.reduce((str, tag) => { const attributeHtml = Object.keys(tag) .filter( attribute => !(attribute === TAG_PROPERTIES.INNER_HTML || attribute === TAG_PROPERTIES.CSS_TEXT) ) .reduce((string, attribute) => { const attr = typeof tag[attribute] === 'undefined' ? attribute : `${attribute}="${encodeSpecialCharacters(tag[attribute], encode)}"`; return string ? `${string} ${attr}` : attr; }, ''); const tagContent = tag.innerHTML || tag.cssText || ''; const isSelfClosing = SELF_CLOSING_TAGS.indexOf(type) === -1; return `${str}<${type} ${HELMET_ATTRIBUTE}="true" ${attributeHtml}${ isSelfClosing ? `/>` : `>${tagContent}</${type}>` }`; }, ''); const convertElementAttributesToReactProps = (attributes, initProps = {}) => Object.keys(attributes).reduce((obj, key) => { // eslint-disable-next-line no-param-reassign obj[REACT_TAG_MAP[key] || key] = attributes[key]; return obj; }, initProps); const generateTitleAsReactComponent = (type, title, attributes) => { // assigning into an array to define toString function on it const initProps = { key: title, [HELMET_ATTRIBUTE]: true, }; const props = convertElementAttributesToReactProps(attributes, initProps); return [React.createElement(TAG_NAMES.TITLE, props, title)]; }; const generateTagsAsReactComponent = (type, tags) => tags.map((tag, i) => { const mappedTag = { key: i, [HELMET_ATTRIBUTE]: true, }; Object.keys(tag).forEach(attribute => { const mappedAttribute = REACT_TAG_MAP[attribute] || attribute; if ( mappedAttribute === TAG_PROPERTIES.INNER_HTML || mappedAttribute === TAG_PROPERTIES.CSS_TEXT ) { const content = tag.innerHTML || tag.cssText; mappedTag.dangerouslySetInnerHTML = { __html: content }; } else { mappedTag[mappedAttribute] = tag[attribute]; } }); return React.createElement(type, mappedTag); }); const getMethodsForTag = (type, tags, encode) => { switch (type) { case TAG_NAMES.TITLE: return { toComponent: () => generateTitleAsReactComponent(type, tags.title, tags.titleAttributes, encode), toString: () => generateTitleAsString(type, tags.title, tags.titleAttributes, encode), }; case ATTRIBUTE_NAMES.BODY: case ATTRIBUTE_NAMES.HTML: return { toComponent: () => convertElementAttributesToReactProps(tags), toString: () => generateElementAttributesAsString(tags), }; default: return { toComponent: () => generateTagsAsReactComponent(type, tags), toString: () => generateTagsAsString(type, tags, encode), }; } }; const mapStateOnServer = ({ baseTag, bodyAttributes, encode, htmlAttributes, linkTags, metaTags, noscriptTags, scriptTags, styleTags, title = '', titleAttributes, }) => ({ base: getMethodsForTag(TAG_NAMES.BASE, baseTag, encode), bodyAttributes: getMethodsForTag(ATTRIBUTE_NAMES.BODY, bodyAttributes, encode), htmlAttributes: getMethodsForTag(ATTRIBUTE_NAMES.HTML, htmlAttributes, encode), link: getMethodsForTag(TAG_NAMES.LINK, linkTags, encode), meta: getMethodsForTag(TAG_NAMES.META, metaTags, encode), noscript: getMethodsForTag(TAG_NAMES.NOSCRIPT, noscriptTags, encode), script: getMethodsForTag(TAG_NAMES.SCRIPT, scriptTags, encode), style: getMethodsForTag(TAG_NAMES.STYLE, styleTags, encode), title: getMethodsForTag(TAG_NAMES.TITLE, { title, titleAttributes }, encode), }); export default mapStateOnServer;