UNPKG

@dr.pogodin/react-helmet

Version:

Thread-safe Helmet for React 19+ and friends

217 lines (210 loc) 7.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _compilerRuntime = require("react/compiler-runtime"); var _react = require("react"); var _Provider = require("./Provider"); var _constants = require("./constants"); var _utils = require("./utils"); function assertChildType(childType, nestedChildren) { if (typeof childType !== 'string') { throw Error('You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.'); } if (!_constants.VALID_TAG_NAMES.includes(childType)) { throw Error(`Only elements types ${_constants.VALID_TAG_NAMES.join(', ')} are allowed. Helmet does not support rendering <${childType}> elements. Refer to our API for more information.`); } if (!nestedChildren || typeof nestedChildren === 'string' || Array.isArray(nestedChildren) // TODO: This piece of the check is wrong when parent is a fragment, // and thus children may not be an array of strings. // && nestedChildren.every((item) => typeof item === 'string') ) return; throw Error(`Helmet expects a string as a child of <${childType}>. Did you forget to wrap your children in braces? ( <${childType}>{\`\`}</${childType}> ) Refer to our API for more information.`); } /** * Given a string key, it checks it against the legacy mapping between supported * HTML attribute names and their corresponding React prop names (for the names * that are different). If found in the mapping, it prints a warning to console * and returns the mapped prop name. Otherwise, it just returns the key as is, * assuming it is already a valid React prop name. */ function getPropName(key) { const res = _constants.REACT_TAG_MAP[key]; if (res) { // eslint-disable-next-line no-console console.warn(`"${key}" is not a valid JSX prop, replace it by "${res}"`); } return res ?? key; } /** * Given children and props of a <Helmet> component, it reduces them to a single * props object. * * TODO: I guess, it should be further refactored, to make it cleaner... * though, it should perfectly work as is, so not a huge priority for now. */ function reduceChildrenAndProps(props) { // NOTE: `props` are clonned, thus it is safe to push additional items to // array values of `res`, and to re-assign non-array values of `res`, without // the risk to mutate the original `props` object. const res = (0, _utils.cloneProps)(props); // TODO: This is a temporary block, for compatibility with legacy library. for (const item of Object.values(props)) { if (Array.isArray(item)) { for (const it of item) { // TODO: This condition is actually needed to prevent some test failures, // I guess, something is messed up with related types? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (it) { for (const key of Object.keys(it)) { const p = getPropName(key); if (p !== key) { it[p] = it[key]; delete it[key]; } } } } } else if (item && typeof item === 'object') { const it = item; for (const key of Object.keys(it)) { const p = getPropName(key); if (p !== key) { it[p] = it[key]; delete it[key]; } } } } // eslint-disable-next-line complexity _react.Children.forEach(props.children, child => { if (child === undefined || child === null) return; if (typeof child !== 'object' || !('props' in child)) { throw Error(`"${typeof child}" is not a valid <Helmet> descendant`); } let nestedChildren; const childProps = {}; if (child.props) { for (const [key, value] of Object.entries(child.props)) { if (key === 'children') nestedChildren = value;else childProps[getPropName(key)] = value; } } let { type } = child; // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion if (typeof type === 'symbol') type = type.toString(); assertChildType(type, nestedChildren); function assertStringChild(child2) { if (typeof child2 !== 'string') { // TODO: We want to throw, but the legacy code did not, so we won't for // now. // eslint-disable-next-line no-console console.error(`child of ${type} element should be a string`); /* throw Error( // NOTE: assertChildType() above guarantees that `type` is a string, // although it is not expressed in a way TypeScript can automatically // pick up. ); */ } } switch (type) { case _constants.TAG_NAMES.BASE: res.base = childProps; break; case _constants.TAG_NAMES.BODY: res.bodyAttributes = childProps; break; case _constants.TAG_NAMES.FRAGMENT: (0, _utils.mergeProps)(res, reduceChildrenAndProps({ children: nestedChildren })); break; case _constants.TAG_NAMES.HTML: res.htmlAttributes = childProps; break; case _constants.TAG_NAMES.LINK: case _constants.TAG_NAMES.META: if (nestedChildren) { throw Error(`<${type} /> elements are self-closing and can not contain children. Refer to our API for more information.`); } (0, _utils.pushToPropArray)(res, type, childProps); break; case _constants.TAG_NAMES.NOSCRIPT: case _constants.TAG_NAMES.SCRIPT: if (nestedChildren !== undefined) { assertStringChild(nestedChildren); childProps.innerHTML = nestedChildren; } (0, _utils.pushToPropArray)(res, type, childProps); break; case _constants.TAG_NAMES.STYLE: assertStringChild(nestedChildren); childProps.cssText = nestedChildren; (0, _utils.pushToPropArray)(res, type, childProps); break; case _constants.TAG_NAMES.TITLE: res.titleAttributes = childProps; if (typeof nestedChildren === 'string') res.title = nestedChildren; // When title contains {} expressions the children are an array of // strings, and other values. else if (Array.isArray(nestedChildren)) res.title = nestedChildren.join(''); break; case _constants.TAG_NAMES.HEAD: default: { // TODO: Perhaps, we should remove HEAD entry from TAG_NAMES? // eslint-disable-next-line @typescript-eslint/no-unused-vars const bad = type; } } }); delete res.children; return res; } const Helmet = props => { const $ = (0, _compilerRuntime.c)(8); const context = (0, _react.use)(_Provider.Context); if (!context) { throw Error("<Helmet> component must be within a <HelmetProvider> children tree"); } const id = (0, _react.useId)(); context.update(id, reduceChildrenAndProps(props)); let t0; if ($[0] !== context || $[1] !== id || $[2] !== props) { t0 = () => { context.update(id, reduceChildrenAndProps(props)); context.clientApply(); }; $[0] = context; $[1] = id; $[2] = props; $[3] = t0; } else { t0 = $[3]; } (0, _react.useEffect)(t0); let t1; let t2; if ($[4] !== context || $[5] !== id) { t1 = () => () => { context.update(id, undefined); context.clientApply(); }; t2 = [context, id]; $[4] = context; $[5] = id; $[6] = t1; $[7] = t2; } else { t1 = $[6]; t2 = $[7]; } (0, _react.useEffect)(t1, t2); return null; }; var _default = exports.default = Helmet; //# sourceMappingURL=Helmet.js.map