UNPKG

@dr.pogodin/react-helmet

Version:

Thread-safe Helmet for React 19+ and friends

218 lines (216 loc) 7.48 kB
import { createElement } from 'react'; import { HELMET_ATTRIBUTE, HTML_TAG_MAP, REACT_TAG_MAP, TAG_NAMES, TAG_PROPERTIES } from './constants'; import { calcAggregatedState, flattenArray, propToAttr } from './utils'; import { jsx as _jsx } from "react/jsx-runtime"; const SELF_CLOSING_TAGS = [TAG_NAMES.NOSCRIPT, TAG_NAMES.SCRIPT, TAG_NAMES.STYLE]; const encodeSpecialCharacters = (str, encode = true) => { if (!encode) return str; return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;'); }; function generateElementAttributesAsString(attrs) { let res = ''; for (const [name, value] of Object.entries(attrs)) { const attr = propToAttr(name); const neu = value === undefined ? attr : `${attr}="${value}"`; if (neu && res) res += ' '; res += neu; } return res; } const generateTitleAsString = (title, attrs, encode) => { let attrsStr = generateElementAttributesAsString(attrs); if (attrsStr) attrsStr = ` ${attrsStr}`; const flattenedTitle = flattenArray(title); return `<title ${HELMET_ATTRIBUTE}="true"${attrsStr}>${encodeSpecialCharacters(flattenedTitle, encode)}</title>`; }; function generateTagsAsString(type, tags, encode) { let res = ''; for (const tag of tags) { let attributeHtml = ''; const entries = Object.entries(tag); for (const [name, value] of entries) { if (!(name === TAG_PROPERTIES.INNER_HTML || name === TAG_PROPERTIES.CSS_TEXT)) { const attrName = HTML_TAG_MAP[name] ?? name; let attr; if (value === undefined) attr = attrName;else { // Perhaps, if `value` is not a string we should just do // "attr = attrName", as in the case just above. Playing // it safe now, to avoid changes from the original code. const valStr = typeof value === 'string' ? value : String(value); attr = `${attrName}="${encodeSpecialCharacters(valStr, encode)}"`; } if (attributeHtml) attributeHtml += ` ${attr}`;else attributeHtml = attr; } } const tagContent = tag.innerHTML ?? tag.cssText ?? ''; const isSelfClosing = !SELF_CLOSING_TAGS.includes(type); res += `<${type} ${HELMET_ATTRIBUTE}="true" ${attributeHtml}${isSelfClosing ? '/>' : `>${tagContent}</${type}>`}`; } return res; } /** * Given a map of element attribute names & values it returns the corresponding * map of element properties & values (i.e. replacing some attribute names by * their corresponding property names). */ function mapElementAttributesToProps(attributes, ops = {}) { const res = {}; if (ops.addHelmetDataAttr) res[HELMET_ATTRIBUTE] = true; if (ops.addKey !== undefined) res.key = ops.addKey; for (const [attrName, value] of Object.entries(attributes)) { const propName = REACT_TAG_MAP[attrName] ?? attrName; switch (propName) { // cssText and innerHTML props get a special treatment to avoid that React // escapes their values. case 'cssText': case 'innerHTML': res.dangerouslySetInnerHTML = { __html: value }; break; default: res[propName] = value; } } return res; } function renderTitle(title, attrs) { // NOTE: Rendered as array to match legacy behavior. return [/*#__PURE__*/_jsx("title", { ...mapElementAttributesToProps(attrs, { addHelmetDataAttr: true }), children: title }, title)]; } function renderElement(type, attrs, key) { return /*#__PURE__*/createElement(type, mapElementAttributesToProps(attrs, { addHelmetDataAttr: true, addKey: key })); } function renderElements(type, attrs) { const res = []; for (let i = 0; i < attrs.length; ++i) { res.push(renderElement(type, attrs[i], i)); } return res; } export function newServerState(heap) { // TODO: Should this function to be attached to the heap itself? const getState = () => { // eslint-disable-next-line no-param-reassign heap.state ??= calcAggregatedState(heap.helmets); return heap.state; }; return { base: { toComponent() { const props = getState().base; return props ? renderElements('base', [props]) : []; }, toString() { const s = getState(); return s.base ? generateTagsAsString('base', [s.base], s.encodeSpecialCharacters) : ''; } }, bodyAttributes: { toComponent() { const props = getState().bodyAttributes; return mapElementAttributesToProps(props ?? {}); }, toString() { const props = getState().bodyAttributes; return generateElementAttributesAsString(props ?? {}); } }, htmlAttributes: { toComponent() { const props = getState().htmlAttributes; return mapElementAttributesToProps(props ?? {}); }, toString() { const props = getState().htmlAttributes; return generateElementAttributesAsString(props ?? {}); } }, link: { toComponent() { return renderElements('link', getState().links ?? []); }, toString() { const s = getState(); return generateTagsAsString('link', s.links ?? [], s.encodeSpecialCharacters); } }, meta: { toComponent() { return renderElements('meta', getState().meta ?? []); }, toString() { const s = getState(); return generateTagsAsString('meta', s.meta ?? [], s.encodeSpecialCharacters); } }, noscript: { toComponent() { return renderElements('noscript', getState().noscript ?? []); }, toString() { const s = getState(); return generateTagsAsString('noscript', s.noscript ?? [], s.encodeSpecialCharacters); } }, priority: { toComponent() { const s = getState(); return [...renderElements('meta', s.priority?.meta ?? []), ...renderElements('link', s.priority?.links ?? []), ...renderElements('script', s.priority?.script ?? [])]; }, toString() { const s = getState(); const meta = generateTagsAsString('meta', s.priority?.meta ?? [], s.encodeSpecialCharacters); const link = generateTagsAsString('link', s.priority?.links ?? [], s.encodeSpecialCharacters); const script = generateTagsAsString('script', s.priority?.script ?? [], s.encodeSpecialCharacters); let res = meta; if (link) { if (res) res += ' '; res += link; } if (script) { if (res) res += ' '; res += script; } return res; } }, script: { toComponent() { return renderElements('script', getState().script ?? []); }, toString() { const s = getState(); return generateTagsAsString('script', s.script ?? [], s.encodeSpecialCharacters); } }, style: { toComponent() { return renderElements('style', getState().style ?? []); }, toString() { const s = getState(); return generateTagsAsString('style', s.style ?? [], s.encodeSpecialCharacters); } }, title: { toComponent() { const s = getState(); return renderTitle(s.title ?? '', s.titleAttributes ?? {}); }, toString() { const s = getState(); return generateTitleAsString(s.title ?? '', s.titleAttributes ?? {}, s.encodeSpecialCharacters); } } }; } //# sourceMappingURL=server.js.map