@lomray/react-head-manager 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import e from"@lomray/event-manager";import t from"html-react-parser";import r,{Fragment as s,Children as n}from"react";import i from"./events.js";import o from"./tag-status.js";class a{static rootContainerId="root";isServer="undefined"==typeof window;tags;tagsDefinitions={"meta[charset]":{order:10},"meta[name='viewport']":{order:20},title:{order:100},base:{order:110},meta:{order:200},link:{order:300},script:{order:400},noscript:{order:500},style:{order:600}};reservedAttributes={order:"data-order"};syncTimerId=null;constructor(e={}){this.tags={html:new Map(e?.html),body:new Map(e?.body),meta:new Map(e?.meta),containers:new Set(e?.containers)}}setTagsDefinitions(e){this.tagsDefinitions={...this.tagsDefinitions,...e}}static cleanupElementProps(e){const{children:t,dangerouslySetInnerHTML:r,...s}=e;return s}sortTags(e){return new Map([...e.entries()].sort((([,e],[,t])=>e.order-t.order)))}getTags(){const{meta:e,body:t,html:r,containers:s}=this.tags;return{html:this.sortTags(r),body:this.sortTags(t),meta:this.sortTags(e),containers:s}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:r}=e,s=document.createElement(t);return this.applyDomElementAttributes(s,r),s}buildKeyByProps(e,t={},r=!1){let s="";for(const e of["id","name","href","src"])if(t[e]){s=`[${e}='${t[e]}']`;break}if(s||r||(s=Object.entries(t).map((([e,t])=>"string"==typeof t&&`[${e}]`)).filter(Boolean).join("")),s)return`${e}${s}`}getDefaultKey(e,t,r){return`${e}-${t}-${r}-not-unique`}isNotUniqueTag(e){return e.endsWith("-not-unique")}cloneElement(e){const{type:t}=e,s={...e?.props??{}};return Object.values(this.reservedAttributes).forEach((e=>{s[e]&&delete s[e]})),"title"===t&&Array.isArray(s.children)&&(s.children=s.children.join("")),{element:r.createElement(t,s),elementProps:s}}getElementOrder(e,t,r="unknown"){return(e[this.reservedAttributes.order]?Number(e[this.reservedAttributes.order]):void 0)??this.tagsDefinitions[r]?.order??this.tagsDefinitions[t]?.order??1e3}pushElements(e,t,r=!0,i=o.init){const d=e&&"object"==typeof e&&"type"in e&&e.type===s?e.props.children:e;n.forEach(d,((e,s)=>{if(!e||"object"!=typeof e||!("type"in e)||!e.type||"string"!=typeof e.type)return;const{type:n}=e,{element:d,elementProps:c}=this.cloneElement(e);let m=this.tagsDefinitions[n]?.key??n;switch(n){case"title":break;case"meta":const{charSet:e,httpEquiv:i}=c;m=e?"meta[charset]":i?"meta[httpEquiv]":this.buildKeyByProps(n,c);break;case"html":case"body":if(!r&&this.tags[n].has(m))return;return void this.tags[n].set(t,{props:a.cleanupElementProps(c),order:t===a.rootContainerId?1:this.getElementOrder(c,n,m)});default:m=this.buildKeyByProps(n,c,!0)??this.getDefaultKey(n,t,s)}!r&&this.tags.meta.has(m)||this.isNotUniqueTag(m)&&this.tags.meta.get(m)?.status===o.synced||this.tags.meta.set(m,{element:this.isServer?d:void 0,domElement:t===a.rootContainerId?void 0:this.createDomElement(d),order:this.getElementOrder(c,n,m),containerId:t,status:i})})),this.tags.containers.add(t)}getRootTagProps(e){return e.size?[...e.values()].reduce(((e,t)=>({...e,...t.props})),{}):{}}applyDomElementAttributes(e,t={}){const r=e.tagName.toLowerCase(),s=Object.values(this.reservedAttributes);Object.entries(t).forEach((([t,n])=>{switch(t){case"children":return void(e.innerHTML=n);case"style":return Object.entries(n).forEach((([t,r])=>{e.style[t]=r}))}s.includes(t)||e.setAttribute(this.replaceAttribute(r,t),n)}))}syncMeta(t){const{meta:r,html:s,body:n}=this.getTags();this.syncTimerId&&clearTimeout(this.syncTimerId);for(const{name:e,value:t}of[{name:"html",value:s},{name:"body",value:n}]){const r=this.getRootTagProps(t),s=document.querySelector(e);(s?.getAttributeNames()??[]).forEach((e=>{r[e]||s.removeAttribute(e)})),Object.keys(r).length&&s&&this.applyDomElementAttributes(s,r)}if(!r.size||this.isServer)return;const a=document.getElementsByTagName("head")?.[0];let d;r.forEach(((e,s)=>{const{status:n,domElement:i}=e;if(n===o.synced||!i||t&&t!==e.containerId)return void(d=i);if(n===o.drain)return d=i.previousSibling,i?.remove(),void r.delete(s);if(e.status=o.synced,!this.isNotUniqueTag(s)){const e=document.querySelector(s);if(e)return d=i,void e.replaceWith(i)}const c=d&&d.nextSibling;c?a.insertBefore(i,c):a.append(i),d=i})),e.publish(i.SYNC_META,{})}pushTags(t,r,s=!0){const n=this.tags.containers.has(r);this.pushElements(t,r,s,n?o.synced:o.init),e.publish(i.PUSH_TAGS,{elements:t,containerId:r}),this.isServer||n||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,r)=>{if(t.containerId===e&&t.containerId!==a.rootContainerId)return this.isNotUniqueTag(r)?(t.domElement?.remove(),void this.tags.meta.delete(r)):void(t.status=o.drain)})),e!==a.rootContainerId&&(this.tags.body.delete(e),this.tags.html.delete(e),this.tags.containers.delete(e),this.syncTimerId=setTimeout((()=>this.syncMeta()),500))}analyzeClientHead(){if(this.isServer)return;for(const e of["html","body"]){const r=document.getElementsByTagName(e)?.[0].cloneNode(!1)?.outerHTML;this.pushElements(t(r),a.rootContainerId,!1)}const e=document.getElementsByTagName("head")?.[0],r=t(e?.innerHTML??"");this.pushElements(r,a.rootContainerId,!1,o.synced);const s=[...this.tags.meta.values()];e.childNodes.forEach(((e,t)=>{const r=s[t];r&&(r.domElement=e)}))}}export{a as default};
1
+ import e from"@lomray/event-manager";import t from"html-react-parser";import r,{Fragment as s,Children as n}from"react";import i from"./events.js";import o from"./tag-status.js";class a{static rootContainerId="root";isServer="undefined"==typeof window;tags;tagsDefinitions={"meta[charset]":{order:10},"meta[name='viewport']":{order:20},title:{order:100},base:{order:110},meta:{order:200},link:{order:300},script:{order:400},noscript:{order:500},style:{order:600}};reservedAttributes={order:"data-order"};syncTimerId=null;constructor(e={}){this.tags={html:new Map(e?.html),body:new Map(e?.body),meta:new Map(e?.meta),containers:new Set(e?.containers)}}setTagsDefinitions(e){this.tagsDefinitions={...this.tagsDefinitions,...e}}static cleanupElementProps(e){const{children:t,dangerouslySetInnerHTML:r,...s}=e;return s}sortTags(e){return new Map([...e.entries()].sort((([,e],[,t])=>e.order-t.order)))}getTags(){const{meta:e,body:t,html:r,containers:s}=this.tags;return{html:this.sortTags(r),body:this.sortTags(t),meta:this.sortTags(e),containers:s}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:r}=e,s=document.createElement(t);return this.applyDomElementAttributes(s,r),s}buildKeyByProps(e,t={},r=!1){let s="";for(const e of["id","name","property","href","src"])if(t[e]){s=`[${e}='${t[e]}']`;break}if(s||r||(s=Object.entries(t).map((([e,t])=>"string"==typeof t&&`[${e}]`)).filter(Boolean).join("")),s)return`${e}${s}`}getDefaultKey(e,t,r){return`${e}-${t}-${r}-not-unique`}isNotUniqueTag(e){return e.endsWith("-not-unique")}cloneElement(e){const{type:t}=e,s={...e?.props??{}};return Object.values(this.reservedAttributes).forEach((e=>{s[e]&&delete s[e]})),"title"===t&&Array.isArray(s.children)&&(s.children=s.children.join("")),{element:r.createElement(t,s),elementProps:s}}getElementOrder(e,t,r="unknown"){return(e[this.reservedAttributes.order]?Number(e[this.reservedAttributes.order]):void 0)??this.tagsDefinitions[r]?.order??this.tagsDefinitions[t]?.order??1e3}pushElements(e,t,r=!0,i=o.init){const d=e&&"object"==typeof e&&"type"in e&&e.type===s?e.props.children:e;n.forEach(d,((e,s)=>{if(!e||"object"!=typeof e||!("type"in e)||!e.type||"string"!=typeof e.type)return;const{type:n}=e,{element:d,elementProps:c}=this.cloneElement(e);let m=this.tagsDefinitions[n]?.key??n;switch(n){case"title":break;case"meta":const{charSet:e,httpEquiv:i}=c;m=e?"meta[charset]":i?"meta[httpEquiv]":this.buildKeyByProps(n,c);break;case"html":case"body":if(!r&&this.tags[n].has(m))return;return void this.tags[n].set(t,{props:a.cleanupElementProps(c),order:t===a.rootContainerId?1:this.getElementOrder(c,n,m)});default:m=this.buildKeyByProps(n,c,!0)??this.getDefaultKey(n,t,s)}!r&&this.tags.meta.has(m)||this.isNotUniqueTag(m)&&this.tags.meta.get(m)?.status===o.synced||this.tags.meta.set(m,{element:this.isServer?d:void 0,domElement:t===a.rootContainerId?void 0:this.createDomElement(d),order:this.getElementOrder(c,n,m),containerId:t,status:i})})),this.tags.containers.add(t)}getRootTagProps(e){return e.size?[...e.values()].reduce(((e,t)=>({...e,...t.props})),{}):{}}applyDomElementAttributes(e,t={}){const r=e.tagName.toLowerCase(),s=Object.values(this.reservedAttributes);Object.entries(t).forEach((([t,n])=>{switch(t){case"children":return void(e.innerHTML=n);case"style":return Object.entries(n).forEach((([t,r])=>{e.style[t]=r}))}s.includes(t)||e.setAttribute(this.replaceAttribute(r,t),n)}))}syncMeta(t){const{meta:r,html:s,body:n}=this.getTags();this.syncTimerId&&clearTimeout(this.syncTimerId);for(const{name:e,value:t}of[{name:"html",value:s},{name:"body",value:n}]){const r=this.getRootTagProps(t),s=document.querySelector(e);(s?.getAttributeNames()??[]).forEach((e=>{r[e]||s.removeAttribute(e)})),Object.keys(r).length&&s&&this.applyDomElementAttributes(s,r)}if(!r.size||this.isServer)return;const a=document.getElementsByTagName("head")?.[0];let d;r.forEach(((e,s)=>{const{status:n,domElement:i}=e;if(n===o.synced||!i||t&&t!==e.containerId)return void(d=i);if(n===o.drain)return d=i.previousSibling,i?.remove(),void r.delete(s);if(e.status=o.synced,!this.isNotUniqueTag(s)){const e=document.querySelector(s);if(e)return d=i,void e.replaceWith(i)}const c=d&&d.nextSibling;c?a.insertBefore(i,c):a.append(i),d=i})),e.publish(i.SYNC_META,{})}pushTags(t,r,s=!0){const n=this.tags.containers.has(r);this.pushElements(t,r,s,n?o.synced:o.init),e.publish(i.PUSH_TAGS,{elements:t,containerId:r}),this.isServer||n||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,r)=>{if(t.containerId===e&&t.containerId!==a.rootContainerId)return this.isNotUniqueTag(r)?(t.domElement?.remove(),void this.tags.meta.delete(r)):void(t.status=o.drain)})),e!==a.rootContainerId&&(this.tags.body.delete(e),this.tags.html.delete(e),this.tags.containers.delete(e),this.syncTimerId=setTimeout((()=>this.syncMeta()),500))}analyzeClientHead(){if(this.isServer)return;for(const e of["html","body"]){const r=document.getElementsByTagName(e)?.[0].cloneNode(!1)?.outerHTML;this.pushElements(t(r),a.rootContainerId,!1)}const e=document.getElementsByTagName("head")?.[0],r=t(e?.innerHTML??"");this.pushElements(r,a.rootContainerId,!1,o.synced);const s=[...this.tags.meta.values()];e.childNodes.forEach(((e,t)=>{const r=s[t];r&&(r.domElement=e)}))}}export{a as default};
2
2
  //# sourceMappingURL=manager.js.map
package/manager.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sources":["../src/manager.ts"],"sourcesContent":["import EventManager from '@lomray/event-manager';\nimport htmlParser from 'html-react-parser';\nimport type { ReactElement, ReactNode } from 'react';\nimport React, { Fragment, Children } from 'react';\nimport Events from './events';\nimport TagStatus from './tag-status';\n\nexport interface IMetaManagerTags {\n html: Map<\n string,\n {\n props: Record<string, any>;\n order: number;\n }\n >;\n body: Map<\n string,\n {\n props: Record<string, any>;\n order: number;\n }\n >; // containerId => styles\n meta: Map<\n string,\n {\n element?: ReactElement;\n domElement?: HTMLElement;\n order: number;\n containerId: string;\n status: TagStatus;\n }\n >;\n containers: Set<string>;\n}\n\nexport interface IMetaManagerTagsDefinitions {\n [type: string]: {\n key?: string;\n order: number;\n };\n}\n\n/**\n * Meta tags manager\n */\nclass Manager {\n /**\n * Root container id\n */\n public static rootContainerId = 'root';\n\n /**\n * Detect server side\n */\n public isServer = typeof window === 'undefined';\n\n /**\n * Meta tags state\n */\n protected tags: IMetaManagerTags;\n\n /**\n * Tags definitions\n */\n protected tagsDefinitions: IMetaManagerTagsDefinitions = {\n 'meta[charset]': {\n order: 10,\n },\n \"meta[name='viewport']\": {\n order: 20,\n },\n title: {\n order: 100,\n },\n base: {\n order: 110,\n },\n meta: {\n order: 200,\n },\n link: {\n order: 300,\n },\n script: {\n order: 400,\n },\n noscript: {\n order: 500,\n },\n style: {\n order: 600,\n },\n };\n\n /**\n * System tag attributes\n */\n protected reservedAttributes = {\n order: 'data-order',\n };\n\n /**\n * Scheduled synchronization\n */\n protected syncTimerId: null | NodeJS.Timeout = null;\n\n /**\n * @constructor\n */\n constructor(tags: Partial<Manager['tags']> = {}) {\n this.tags = {\n html: new Map(tags?.html),\n body: new Map(tags?.body),\n meta: new Map(tags?.meta),\n containers: new Set(tags?.containers),\n };\n }\n\n /**\n * Set tags definitions\n */\n public setTagsDefinitions(definitions: IMetaManagerTagsDefinitions): void {\n this.tagsDefinitions = { ...this.tagsDefinitions, ...definitions };\n }\n\n /**\n * Remove children, dangerouslySetInnerHTML etc.\n */\n public static cleanupElementProps(props: Record<string, any>): Record<string, any> {\n const { children: _, dangerouslySetInnerHTML: __, ...restProps } = props;\n\n return restProps;\n }\n\n /**\n * Sort elements or props\n */\n protected sortTags<T extends Map<string, { order: number }>>(elements: T): T {\n return new Map(\n [...elements.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n ) as T;\n }\n\n /**\n * Get meta tags\n */\n public getTags(): IMetaManagerTags {\n const { meta, body, html, containers } = this.tags;\n\n return {\n html: this.sortTags(html),\n body: this.sortTags(body),\n meta: this.sortTags(meta),\n containers,\n };\n }\n\n /**\n * Replace react attribute to valid DOM attribute\n */\n protected replaceAttribute(tagName: string, attribute: string): string {\n if (tagName === 'meta' && attribute.toLowerCase() === 'httpequiv') {\n return 'http-equiv';\n }\n\n return attribute;\n }\n\n /**\n * Create DOM element from React element\n */\n protected createDomElement(element: ReactElement): HTMLElement | undefined {\n if (this.isServer) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { type, props } = element;\n const domElement = document.createElement(type as string);\n\n this.applyDomElementAttributes(domElement, props as Record<string, string>);\n\n return domElement;\n }\n\n /**\n * Make element key by element props\n */\n protected buildKeyByProps(\n type: string,\n props: Record<string, any> = {},\n isSkipProps = false,\n ): string | undefined {\n let key = '';\n\n // try to build unique key by unique props\n for (const uniqueAttr of ['id', 'name', 'href', 'src']) {\n if (props[uniqueAttr]) {\n key = `[${uniqueAttr}='${props[uniqueAttr] as string}']`;\n\n break;\n }\n }\n\n // try to build by props\n if (!key && !isSkipProps) {\n key = Object.entries(props)\n .map(([k, v]) => {\n if (typeof v === 'string') {\n return `[${k}]`;\n }\n\n return false;\n })\n .filter(Boolean)\n .join('');\n }\n\n if (!key) {\n return undefined;\n }\n\n return `${type}${key}`;\n }\n\n /**\n * Get default tag key for not unique elements\n */\n protected getDefaultKey(type: string, containerId: string, index: number): string {\n return `${type}-${containerId}-${index}-not-unique`;\n }\n\n /**\n * Check if tag is not unique\n */\n protected isNotUniqueTag(key: string): boolean {\n return key.endsWith('-not-unique');\n }\n\n /**\n * Clone react element\n */\n protected cloneElement(element: ReactElement): {\n element: ReactElement;\n elementProps: Record<string, any>;\n } {\n const { type } = element;\n const props = { ...(element?.props ?? {}) } as Record<string, any>;\n\n // remove system attributes\n Object.values(this.reservedAttributes).forEach((attrName) => {\n if (props[attrName]) {\n delete props[attrName];\n }\n });\n\n // fix multiple nodes for title\n if (type === 'title' && Array.isArray(props.children)) {\n props.children = props.children.join('');\n }\n\n return {\n element: React.createElement(type, props),\n elementProps: props,\n };\n }\n\n /**\n * Get element order\n */\n protected getElementOrder(\n elementProps: Record<string, any>,\n type: string,\n key: string = 'unknown',\n ): number {\n const elementOrder = elementProps[this.reservedAttributes.order]\n ? Number(elementProps[this.reservedAttributes.order])\n : undefined;\n\n return (\n elementOrder ?? this.tagsDefinitions[key]?.order ?? this.tagsDefinitions[type]?.order ?? 1000\n );\n }\n\n /**\n * Push react elements to meta state\n */\n protected pushElements(\n elements: ReactNode,\n containerId: string,\n isReplace = true,\n status = TagStatus.init,\n ): void {\n // unwrap fragment\n const clearElements: ReactNode =\n elements && typeof elements === 'object' && 'type' in elements && elements.type === Fragment\n ? ((elements.props as Record<string, any>).children as ReactNode)\n : elements;\n\n Children.forEach(clearElements, (child, index) => {\n // skip unsupported elements\n if (\n !child ||\n typeof child !== 'object' ||\n !('type' in child) ||\n !child.type ||\n typeof child.type !== 'string'\n ) {\n return;\n }\n\n const { type } = child;\n const { element, elementProps } = this.cloneElement(child);\n let key = this.tagsDefinitions[type]?.key ?? type;\n\n switch (type) {\n case 'title':\n break;\n\n case 'meta':\n const { charSet, httpEquiv } = elementProps;\n\n if (charSet) {\n key = `meta[charset]`;\n } else if (httpEquiv) {\n key = `meta[httpEquiv]`;\n } else {\n key = this.buildKeyByProps(type, elementProps)!;\n }\n\n break;\n\n case 'html':\n case 'body':\n if (!isReplace && this.tags[type].has(key)) {\n return;\n }\n\n this.tags[type].set(containerId, {\n props: Manager.cleanupElementProps(elementProps),\n order:\n containerId === Manager.rootContainerId\n ? 1\n : this.getElementOrder(elementProps, type, key),\n });\n\n return;\n\n case 'link':\n case 'script':\n case 'noscript':\n case 'style':\n default:\n key =\n this.buildKeyByProps(type, elementProps, true) ??\n this.getDefaultKey(type, containerId, index);\n break;\n }\n\n // skip replace existed meta tag (e.g. for push existed tags from parsed html, @see ServerManager)\n if (!isReplace && this.tags.meta.has(key)) {\n return;\n }\n\n // skip push already existed in head not unique tags\n if (this.isNotUniqueTag(key) && this.tags.meta.get(key)?.status === TagStatus.synced) {\n return;\n }\n\n this.tags.meta.set(key, {\n element: this.isServer ? element : undefined, // keep element only for server render\n domElement:\n /**\n * create DOM element only for client side\n * generate DOM element for root container inside @see this.analyzeClientHead\n */\n containerId === Manager.rootContainerId ? undefined : this.createDomElement(element),\n order: this.getElementOrder(elementProps, type, key),\n containerId,\n status,\n });\n });\n\n this.tags.containers.add(containerId);\n }\n\n /**\n * Get html or body props\n */\n public getRootTagProps(props: Manager['tags']['html']): Record<string, any> {\n if (!props.size) {\n return {};\n }\n\n return [...props.values()].reduce(\n (res, val) => ({\n ...res,\n ...val.props,\n }),\n {},\n );\n }\n\n /**\n * Apply props to dom element\n */\n protected applyDomElementAttributes(element: Element, props: Record<string, any> = {}): void {\n const tagName = element.tagName.toLowerCase();\n const reservedAttributes = Object.values(this.reservedAttributes);\n\n // apply attributes\n Object.entries(props).forEach(([name, value]) => {\n switch (name) {\n case 'children':\n element.innerHTML = value as string;\n\n return;\n\n case 'style':\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n // @ts-ignore\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n element['style'][styleName] = styleValue;\n },\n );\n }\n\n if (reservedAttributes.includes(name)) {\n return;\n }\n\n element.setAttribute(this.replaceAttribute(tagName, name), value as string);\n });\n }\n\n /**\n * Synchronize local meta tags with head meta tags\n */\n protected syncMeta(containerId?: string): void {\n const { meta, html, body } = this.getTags();\n\n if (this.syncTimerId) {\n clearTimeout(this.syncTimerId);\n }\n\n // apply html, body props\n for (const { name, value } of [\n { name: 'html', value: html },\n { name: 'body', value: body },\n ]) {\n const tagProps = this.getRootTagProps(value);\n const domElement = document.querySelector(name);\n const existAttributes = domElement?.getAttributeNames() ?? [];\n\n // remove drained props\n existAttributes.forEach((attrName) => {\n if (tagProps[attrName]) {\n return;\n }\n\n domElement!.removeAttribute(attrName);\n });\n\n if (!Object.keys(tagProps).length || !domElement) {\n continue;\n }\n\n this.applyDomElementAttributes(domElement, tagProps);\n }\n\n if (!meta.size || this.isServer) {\n return;\n }\n\n const head = document.getElementsByTagName('head')?.[0];\n let prevElement: HTMLElement | ChildNode | null | undefined;\n\n meta.forEach((metaTag, key) => {\n const { status, domElement } = metaTag;\n\n if (\n status === TagStatus.synced ||\n !domElement ||\n (containerId && containerId !== metaTag.containerId)\n ) {\n prevElement = domElement;\n\n return;\n }\n\n // remove tags\n if (status === TagStatus.drain) {\n prevElement = domElement.previousSibling;\n domElement?.remove();\n meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.synced;\n\n if (!this.isNotUniqueTag(key)) {\n const exist = document.querySelector(key);\n\n if (exist) {\n prevElement = domElement;\n exist.replaceWith(domElement);\n\n return;\n }\n }\n\n const beforeElement = prevElement && prevElement.nextSibling;\n\n if (beforeElement) {\n head.insertBefore(domElement, beforeElement);\n } else {\n head.append(domElement);\n }\n\n prevElement = domElement;\n });\n\n EventManager.publish(Events.SYNC_META, {});\n }\n\n /**\n * Push new meta tags\n */\n public pushTags(elements: ReactNode, containerId: string, isReplace = true): void {\n const isAdded = this.tags.containers.has(containerId);\n\n this.pushElements(\n elements,\n containerId,\n isReplace,\n isAdded ? TagStatus.synced : TagStatus.init,\n );\n\n EventManager.publish(Events.PUSH_TAGS, { elements, containerId });\n\n // skip sync already synced tags\n if (this.isServer || isAdded) {\n return;\n }\n\n this.syncMeta();\n }\n\n /**\n * Remove meta tags\n */\n public removeTags(containerId: string): void {\n this.tags.meta.forEach((metaTag, key) => {\n if (metaTag.containerId !== containerId || metaTag.containerId === Manager.rootContainerId) {\n return;\n }\n\n // remove immediately not unique tags\n if (this.isNotUniqueTag(key)) {\n metaTag.domElement?.remove();\n this.tags.meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.drain;\n });\n\n if (containerId === Manager.rootContainerId) {\n return;\n }\n\n this.tags.body.delete(containerId);\n this.tags.html.delete(containerId);\n this.tags.containers.delete(containerId);\n this.syncTimerId = setTimeout(() => this.syncMeta(), 500);\n }\n\n /**\n * Initial analyze client head meta tags\n */\n public analyzeClientHead(): void {\n if (this.isServer) {\n return;\n }\n\n // parse default attributes for root tags\n for (const tagName of ['html', 'body']) {\n // @ts-ignore\n const htmlTag = document.getElementsByTagName(tagName)?.[0].cloneNode(false)?.[\n 'outerHTML'\n ] as string;\n\n this.pushElements(htmlParser(htmlTag), Manager.rootContainerId, false);\n }\n\n // parse default meta tags\n const head = document.getElementsByTagName('head')?.[0];\n const reactElements = htmlParser(head?.innerHTML ?? '');\n\n this.pushElements(reactElements, Manager.rootContainerId, false, TagStatus.synced);\n\n const meta = [...this.tags.meta.values()];\n\n // attach real dom node to virtual tags\n head.childNodes.forEach((node, i) => {\n const existedElem = meta[i];\n\n if (!existedElem) {\n return;\n }\n\n existedElem.domElement = node as HTMLElement;\n });\n }\n}\n\nexport default Manager;\n"],"names":["Manager","static","isServer","window","tags","tagsDefinitions","order","title","base","meta","link","script","noscript","style","reservedAttributes","syncTimerId","constructor","this","html","Map","body","containers","Set","setTagsDefinitions","definitions","props","children","_","dangerouslySetInnerHTML","__","restProps","sortTags","elements","entries","sort","tagA","tagB","getTags","replaceAttribute","tagName","attribute","toLowerCase","createDomElement","element","type","domElement","document","createElement","applyDomElementAttributes","buildKeyByProps","isSkipProps","key","uniqueAttr","Object","map","k","v","filter","Boolean","join","getDefaultKey","containerId","index","isNotUniqueTag","endsWith","cloneElement","values","forEach","attrName","Array","isArray","React","elementProps","getElementOrder","Number","undefined","pushElements","isReplace","status","TagStatus","init","clearElements","Fragment","Children","child","charSet","httpEquiv","has","set","cleanupElementProps","rootContainerId","get","synced","add","getRootTagProps","size","reduce","res","val","name","value","innerHTML","styleName","styleValue","includes","setAttribute","syncMeta","clearTimeout","tagProps","querySelector","getAttributeNames","removeAttribute","keys","length","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","EventManager","publish","Events","SYNC_META","pushTags","isAdded","PUSH_TAGS","removeTags","setTimeout","analyzeClientHead","htmlTag","cloneNode","htmlParser","reactElements","childNodes","node","i","existedElem"],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","sources":["../src/manager.ts"],"sourcesContent":["import EventManager from '@lomray/event-manager';\nimport htmlParser from 'html-react-parser';\nimport type { ReactElement, ReactNode } from 'react';\nimport React, { Fragment, Children } from 'react';\nimport Events from './events';\nimport TagStatus from './tag-status';\n\nexport interface IMetaManagerTags {\n html: Map<\n string,\n {\n props: Record<string, any>;\n order: number;\n }\n >;\n body: Map<\n string,\n {\n props: Record<string, any>;\n order: number;\n }\n >; // containerId => styles\n meta: Map<\n string,\n {\n element?: ReactElement;\n domElement?: HTMLElement;\n order: number;\n containerId: string;\n status: TagStatus;\n }\n >;\n containers: Set<string>;\n}\n\nexport interface IMetaManagerTagsDefinitions {\n [type: string]: {\n key?: string;\n order: number;\n };\n}\n\n/**\n * Meta tags manager\n */\nclass Manager {\n /**\n * Root container id\n */\n public static rootContainerId = 'root';\n\n /**\n * Detect server side\n */\n public isServer = typeof window === 'undefined';\n\n /**\n * Meta tags state\n */\n protected tags: IMetaManagerTags;\n\n /**\n * Tags definitions\n */\n protected tagsDefinitions: IMetaManagerTagsDefinitions = {\n 'meta[charset]': {\n order: 10,\n },\n \"meta[name='viewport']\": {\n order: 20,\n },\n title: {\n order: 100,\n },\n base: {\n order: 110,\n },\n meta: {\n order: 200,\n },\n link: {\n order: 300,\n },\n script: {\n order: 400,\n },\n noscript: {\n order: 500,\n },\n style: {\n order: 600,\n },\n };\n\n /**\n * System tag attributes\n */\n protected reservedAttributes = {\n order: 'data-order',\n };\n\n /**\n * Scheduled synchronization\n */\n protected syncTimerId: null | NodeJS.Timeout = null;\n\n /**\n * @constructor\n */\n constructor(tags: Partial<Manager['tags']> = {}) {\n this.tags = {\n html: new Map(tags?.html),\n body: new Map(tags?.body),\n meta: new Map(tags?.meta),\n containers: new Set(tags?.containers),\n };\n }\n\n /**\n * Set tags definitions\n */\n public setTagsDefinitions(definitions: IMetaManagerTagsDefinitions): void {\n this.tagsDefinitions = { ...this.tagsDefinitions, ...definitions };\n }\n\n /**\n * Remove children, dangerouslySetInnerHTML etc.\n */\n public static cleanupElementProps(props: Record<string, any>): Record<string, any> {\n const { children: _, dangerouslySetInnerHTML: __, ...restProps } = props;\n\n return restProps;\n }\n\n /**\n * Sort elements or props\n */\n protected sortTags<T extends Map<string, { order: number }>>(elements: T): T {\n return new Map(\n [...elements.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n ) as T;\n }\n\n /**\n * Get meta tags\n */\n public getTags(): IMetaManagerTags {\n const { meta, body, html, containers } = this.tags;\n\n return {\n html: this.sortTags(html),\n body: this.sortTags(body),\n meta: this.sortTags(meta),\n containers,\n };\n }\n\n /**\n * Replace react attribute to valid DOM attribute\n */\n protected replaceAttribute(tagName: string, attribute: string): string {\n if (tagName === 'meta' && attribute.toLowerCase() === 'httpequiv') {\n return 'http-equiv';\n }\n\n return attribute;\n }\n\n /**\n * Create DOM element from React element\n */\n protected createDomElement(element: ReactElement): HTMLElement | undefined {\n if (this.isServer) {\n return;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { type, props } = element;\n const domElement = document.createElement(type as string);\n\n this.applyDomElementAttributes(domElement, props as Record<string, string>);\n\n return domElement;\n }\n\n /**\n * Make element key by element props\n */\n protected buildKeyByProps(\n type: string,\n props: Record<string, any> = {},\n isSkipProps = false,\n ): string | undefined {\n let key = '';\n\n // try to build unique key by unique props\n for (const uniqueAttr of ['id', 'name', 'property', 'href', 'src']) {\n if (props[uniqueAttr]) {\n key = `[${uniqueAttr}='${props[uniqueAttr] as string}']`;\n\n break;\n }\n }\n\n // try to build by props\n if (!key && !isSkipProps) {\n key = Object.entries(props)\n .map(([k, v]) => {\n if (typeof v === 'string') {\n return `[${k}]`;\n }\n\n return false;\n })\n .filter(Boolean)\n .join('');\n }\n\n if (!key) {\n return undefined;\n }\n\n return `${type}${key}`;\n }\n\n /**\n * Get default tag key for not unique elements\n */\n protected getDefaultKey(type: string, containerId: string, index: number): string {\n return `${type}-${containerId}-${index}-not-unique`;\n }\n\n /**\n * Check if tag is not unique\n */\n protected isNotUniqueTag(key: string): boolean {\n return key.endsWith('-not-unique');\n }\n\n /**\n * Clone react element\n */\n protected cloneElement(element: ReactElement): {\n element: ReactElement;\n elementProps: Record<string, any>;\n } {\n const { type } = element;\n const props = { ...(element?.props ?? {}) } as Record<string, any>;\n\n // remove system attributes\n Object.values(this.reservedAttributes).forEach((attrName) => {\n if (props[attrName]) {\n delete props[attrName];\n }\n });\n\n // fix multiple nodes for title\n if (type === 'title' && Array.isArray(props.children)) {\n props.children = props.children.join('');\n }\n\n return {\n element: React.createElement(type, props),\n elementProps: props,\n };\n }\n\n /**\n * Get element order\n */\n protected getElementOrder(\n elementProps: Record<string, any>,\n type: string,\n key: string = 'unknown',\n ): number {\n const elementOrder = elementProps[this.reservedAttributes.order]\n ? Number(elementProps[this.reservedAttributes.order])\n : undefined;\n\n return (\n elementOrder ?? this.tagsDefinitions[key]?.order ?? this.tagsDefinitions[type]?.order ?? 1000\n );\n }\n\n /**\n * Push react elements to meta state\n */\n protected pushElements(\n elements: ReactNode,\n containerId: string,\n isReplace = true,\n status = TagStatus.init,\n ): void {\n // unwrap fragment\n const clearElements: ReactNode =\n elements && typeof elements === 'object' && 'type' in elements && elements.type === Fragment\n ? ((elements.props as Record<string, any>).children as ReactNode)\n : elements;\n\n Children.forEach(clearElements, (child, index) => {\n // skip unsupported elements\n if (\n !child ||\n typeof child !== 'object' ||\n !('type' in child) ||\n !child.type ||\n typeof child.type !== 'string'\n ) {\n return;\n }\n\n const { type } = child;\n const { element, elementProps } = this.cloneElement(child);\n let key = this.tagsDefinitions[type]?.key ?? type;\n\n switch (type) {\n case 'title':\n break;\n\n case 'meta':\n const { charSet, httpEquiv } = elementProps;\n\n if (charSet) {\n key = `meta[charset]`;\n } else if (httpEquiv) {\n key = `meta[httpEquiv]`;\n } else {\n key = this.buildKeyByProps(type, elementProps)!;\n }\n\n break;\n\n case 'html':\n case 'body':\n if (!isReplace && this.tags[type].has(key)) {\n return;\n }\n\n this.tags[type].set(containerId, {\n props: Manager.cleanupElementProps(elementProps),\n order:\n containerId === Manager.rootContainerId\n ? 1\n : this.getElementOrder(elementProps, type, key),\n });\n\n return;\n\n case 'link':\n case 'script':\n case 'noscript':\n case 'style':\n default:\n key =\n this.buildKeyByProps(type, elementProps, true) ??\n this.getDefaultKey(type, containerId, index);\n break;\n }\n\n // skip replace existed meta tag (e.g. for push existed tags from parsed html, @see ServerManager)\n if (!isReplace && this.tags.meta.has(key)) {\n return;\n }\n\n // skip push already existed in head not unique tags\n if (this.isNotUniqueTag(key) && this.tags.meta.get(key)?.status === TagStatus.synced) {\n return;\n }\n\n this.tags.meta.set(key, {\n element: this.isServer ? element : undefined, // keep element only for server render\n domElement:\n /**\n * create DOM element only for client side\n * generate DOM element for root container inside @see this.analyzeClientHead\n */\n containerId === Manager.rootContainerId ? undefined : this.createDomElement(element),\n order: this.getElementOrder(elementProps, type, key),\n containerId,\n status,\n });\n });\n\n this.tags.containers.add(containerId);\n }\n\n /**\n * Get html or body props\n */\n public getRootTagProps(props: Manager['tags']['html']): Record<string, any> {\n if (!props.size) {\n return {};\n }\n\n return [...props.values()].reduce(\n (res, val) => ({\n ...res,\n ...val.props,\n }),\n {},\n );\n }\n\n /**\n * Apply props to dom element\n */\n protected applyDomElementAttributes(element: Element, props: Record<string, any> = {}): void {\n const tagName = element.tagName.toLowerCase();\n const reservedAttributes = Object.values(this.reservedAttributes);\n\n // apply attributes\n Object.entries(props).forEach(([name, value]) => {\n switch (name) {\n case 'children':\n element.innerHTML = value as string;\n\n return;\n\n case 'style':\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n // @ts-ignore\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n element['style'][styleName] = styleValue;\n },\n );\n }\n\n if (reservedAttributes.includes(name)) {\n return;\n }\n\n element.setAttribute(this.replaceAttribute(tagName, name), value as string);\n });\n }\n\n /**\n * Synchronize local meta tags with head meta tags\n */\n protected syncMeta(containerId?: string): void {\n const { meta, html, body } = this.getTags();\n\n if (this.syncTimerId) {\n clearTimeout(this.syncTimerId);\n }\n\n // apply html, body props\n for (const { name, value } of [\n { name: 'html', value: html },\n { name: 'body', value: body },\n ]) {\n const tagProps = this.getRootTagProps(value);\n const domElement = document.querySelector(name);\n const existAttributes = domElement?.getAttributeNames() ?? [];\n\n // remove drained props\n existAttributes.forEach((attrName) => {\n if (tagProps[attrName]) {\n return;\n }\n\n domElement!.removeAttribute(attrName);\n });\n\n if (!Object.keys(tagProps).length || !domElement) {\n continue;\n }\n\n this.applyDomElementAttributes(domElement, tagProps);\n }\n\n if (!meta.size || this.isServer) {\n return;\n }\n\n const head = document.getElementsByTagName('head')?.[0];\n let prevElement: HTMLElement | ChildNode | null | undefined;\n\n meta.forEach((metaTag, key) => {\n const { status, domElement } = metaTag;\n\n if (\n status === TagStatus.synced ||\n !domElement ||\n (containerId && containerId !== metaTag.containerId)\n ) {\n prevElement = domElement;\n\n return;\n }\n\n // remove tags\n if (status === TagStatus.drain) {\n prevElement = domElement.previousSibling;\n domElement?.remove();\n meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.synced;\n\n if (!this.isNotUniqueTag(key)) {\n const exist = document.querySelector(key);\n\n if (exist) {\n prevElement = domElement;\n exist.replaceWith(domElement);\n\n return;\n }\n }\n\n const beforeElement = prevElement && prevElement.nextSibling;\n\n if (beforeElement) {\n head.insertBefore(domElement, beforeElement);\n } else {\n head.append(domElement);\n }\n\n prevElement = domElement;\n });\n\n EventManager.publish(Events.SYNC_META, {});\n }\n\n /**\n * Push new meta tags\n */\n public pushTags(elements: ReactNode, containerId: string, isReplace = true): void {\n const isAdded = this.tags.containers.has(containerId);\n\n this.pushElements(\n elements,\n containerId,\n isReplace,\n isAdded ? TagStatus.synced : TagStatus.init,\n );\n\n EventManager.publish(Events.PUSH_TAGS, { elements, containerId });\n\n // skip sync already synced tags\n if (this.isServer || isAdded) {\n return;\n }\n\n this.syncMeta();\n }\n\n /**\n * Remove meta tags\n */\n public removeTags(containerId: string): void {\n this.tags.meta.forEach((metaTag, key) => {\n if (metaTag.containerId !== containerId || metaTag.containerId === Manager.rootContainerId) {\n return;\n }\n\n // remove immediately not unique tags\n if (this.isNotUniqueTag(key)) {\n metaTag.domElement?.remove();\n this.tags.meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.drain;\n });\n\n if (containerId === Manager.rootContainerId) {\n return;\n }\n\n this.tags.body.delete(containerId);\n this.tags.html.delete(containerId);\n this.tags.containers.delete(containerId);\n this.syncTimerId = setTimeout(() => this.syncMeta(), 500);\n }\n\n /**\n * Initial analyze client head meta tags\n */\n public analyzeClientHead(): void {\n if (this.isServer) {\n return;\n }\n\n // parse default attributes for root tags\n for (const tagName of ['html', 'body']) {\n // @ts-ignore\n const htmlTag = document.getElementsByTagName(tagName)?.[0].cloneNode(false)?.[\n 'outerHTML'\n ] as string;\n\n this.pushElements(htmlParser(htmlTag), Manager.rootContainerId, false);\n }\n\n // parse default meta tags\n const head = document.getElementsByTagName('head')?.[0];\n const reactElements = htmlParser(head?.innerHTML ?? '');\n\n this.pushElements(reactElements, Manager.rootContainerId, false, TagStatus.synced);\n\n const meta = [...this.tags.meta.values()];\n\n // attach real dom node to virtual tags\n head.childNodes.forEach((node, i) => {\n const existedElem = meta[i];\n\n if (!existedElem) {\n return;\n }\n\n existedElem.domElement = node as HTMLElement;\n });\n }\n}\n\nexport default Manager;\n"],"names":["Manager","static","isServer","window","tags","tagsDefinitions","order","title","base","meta","link","script","noscript","style","reservedAttributes","syncTimerId","constructor","this","html","Map","body","containers","Set","setTagsDefinitions","definitions","props","children","_","dangerouslySetInnerHTML","__","restProps","sortTags","elements","entries","sort","tagA","tagB","getTags","replaceAttribute","tagName","attribute","toLowerCase","createDomElement","element","type","domElement","document","createElement","applyDomElementAttributes","buildKeyByProps","isSkipProps","key","uniqueAttr","Object","map","k","v","filter","Boolean","join","getDefaultKey","containerId","index","isNotUniqueTag","endsWith","cloneElement","values","forEach","attrName","Array","isArray","React","elementProps","getElementOrder","Number","undefined","pushElements","isReplace","status","TagStatus","init","clearElements","Fragment","Children","child","charSet","httpEquiv","has","set","cleanupElementProps","rootContainerId","get","synced","add","getRootTagProps","size","reduce","res","val","name","value","innerHTML","styleName","styleValue","includes","setAttribute","syncMeta","clearTimeout","tagProps","querySelector","getAttributeNames","removeAttribute","keys","length","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","EventManager","publish","Events","SYNC_META","pushTags","isAdded","PUSH_TAGS","removeTags","setTimeout","analyzeClientHead","htmlTag","cloneNode","htmlParser","reactElements","childNodes","node","i","existedElem"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lomray/react-head-manager",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "React meta tags manager with SSR and Suspense support.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/server/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import t from"html-react-parser";import e from"react";import r from"react-dom/server";import a from"../manager.js";class o{static inject(o,s){const m=o.match(/<head[^/].+?>?(?<meta>.+)<\/head>/s),n=o.match(/<html[^>]*?>/s),d=o.match(/<body[^>]*?>/s),i=t(m?.groups?.meta.trim()??""),h=t(n?.[0]?`${n?.[0].trim()}</html>`:""),c=t(d?.[0]?`${d?.[0].trim()}</body>`:"");s.pushTags(h,a.rootContainerId,!1),s.pushTags(c,a.rootContainerId,!1),s.pushTags(i,a.rootContainerId,!1);const{meta:l,html:p,body:g}=s.getTags(),T=r.renderToString([...l.values()].map((({element:t})=>t))),[b]=r.renderToString(e.createElement("html",s.getRootTagProps(p))).split("</html>"),[u]=r.renderToString(e.createElement("body",s.getRootTagProps(g))).split("</body>");return o.replace(/<head[^/].+?>?(?<meta>.+)<\/head>/s,`<head>${T}</head>`).replace(/<html[^>]*?>/s,b).replace(/<body[^>]*?>/s,u)}static getState(t){const{html:e,body:r,containers:a}=t.getTags();return{html:[...e.entries()],body:[...r.entries()],containers:[...a]}}}export{o as default};
1
+ import t from"html-react-parser";import e from"react";import r from"react-dom/server";import a from"../manager.js";class o{static inject(o,s){const m=o.match(/<head[^/].+?>?(?<meta>.+)<\/head>/s),i=o.match(/<html[^>]*?>/s),d=o.match(/<body[^>]*?>/s),n=t(m?.groups?.meta.trim()??""),c=t(i?.[0]?`${i?.[0].trim()}</html>`:""),p=t(d?.[0]?`${d?.[0].trim()}</body>`:"");s.pushTags(c,a.rootContainerId,!1),s.pushTags(p,a.rootContainerId,!1),s.pushTags(n,a.rootContainerId,!1);const{meta:l,html:h,body:g}=s.getTags(),u=r.renderToString([...l.values()].map((({element:t})=>t))),[T]=r.renderToStaticMarkup(e.createElement("div",s.getRootTagProps(h))).replace(/^<div/,"<html").split("</div>"),[v]=r.renderToStaticMarkup(e.createElement("div",s.getRootTagProps(g))).replace(/^<div/,"<body").split("</div>");return o.replace(/<head[^/].+?>?(?<meta>.+)<\/head>/s,`<head>${u}</head>`).replace(/<html[^>]*?>/s,T).replace(/<body[^>]*?>/s,v)}static getState(t){const{html:e,body:r,containers:a}=t.getTags();return{html:[...e.entries()],body:[...r.entries()],containers:[...a]}}}export{o as default};
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/server/index.ts"],"sourcesContent":["// noinspection HtmlRequiredTitleElement\n\nimport htmlParser from 'html-react-parser';\nimport type { ReactElement } from 'react';\nimport React from 'react';\nimport ReactDOMServer from 'react-dom/server';\nimport Manager from '../manager';\n\ninterface IMetaManagerState {\n html: [string, Record<string, any>][];\n body: [string, Record<string, any>][];\n containers: string[];\n}\n\n/**\n * Helpers for server side\n */\nclass ServerManager {\n /**\n * Parse existing head tags from html and merge with tags received at the render stage (from manager)\n *\n * @return new html with actual meta tags\n */\n public static inject(htmlStr: string, manager: Manager): string {\n const matchedMetaHtml = htmlStr.match(/<head[^/].+?>?(?<meta>.+)<\\/head>/s);\n const matchedHtmlAttr = htmlStr.match(/<html[^>]*?>/s);\n const matchedBodyAttr = htmlStr.match(/<body[^>]*?>/s);\n const rootTags = htmlParser(matchedMetaHtml?.groups?.meta.trim() ?? '');\n const htmlTag = htmlParser(matchedHtmlAttr?.[0] ? `${matchedHtmlAttr?.[0].trim()}</html>` : '');\n const bodyTag = htmlParser(matchedBodyAttr?.[0] ? `${matchedBodyAttr?.[0].trim()}</body>` : '');\n\n // add root html props\n manager.pushTags(htmlTag, Manager.rootContainerId, false);\n // add root body props\n manager.pushTags(bodyTag, Manager.rootContainerId, false);\n // add root meta tags to manager\n manager.pushTags(rootTags, Manager.rootContainerId, false);\n\n const { meta, html, body } = manager.getTags();\n const htmlMeta = ReactDOMServer.renderToString(\n [...meta.values()].map(({ element }) => element) as unknown as ReactElement,\n );\n const [htmlTagWithProps] = ReactDOMServer.renderToString(\n React.createElement('html', manager.getRootTagProps(html)),\n ).split('</html>');\n const [bodyTagWithProps] = ReactDOMServer.renderToString(\n React.createElement('body', manager.getRootTagProps(body)),\n ).split('</body>');\n\n return htmlStr\n .replace(/<head[^/].+?>?(?<meta>.+)<\\/head>/s, `<head>${htmlMeta}</head>`)\n .replace(/<html[^>]*?>/s, htmlTagWithProps)\n .replace(/<body[^>]*?>/s, bodyTagWithProps);\n }\n\n /**\n * Get meta manager state to pass on client\n */\n public static getState(manager: Manager): IMetaManagerState {\n const { html, body, containers } = manager.getTags();\n\n return {\n html: [...html.entries()],\n body: [...body.entries()],\n containers: [...containers],\n };\n }\n}\n\nexport default ServerManager;\n"],"names":["ServerManager","static","htmlStr","manager","matchedMetaHtml","match","matchedHtmlAttr","matchedBodyAttr","rootTags","htmlParser","groups","meta","trim","htmlTag","bodyTag","pushTags","Manager","rootContainerId","html","body","getTags","htmlMeta","ReactDOMServer","renderToString","values","map","element","htmlTagWithProps","React","createElement","getRootTagProps","split","bodyTagWithProps","replace","containers","entries"],"mappings":"mHAiBA,MAAMA,EAMGC,cAAcC,EAAiBC,GACpC,MAAMC,EAAkBF,EAAQG,MAAM,sCAChCC,EAAkBJ,EAAQG,MAAM,iBAChCE,EAAkBL,EAAQG,MAAM,iBAChCG,EAAWC,EAAWL,GAAiBM,QAAQC,KAAKC,QAAU,IAC9DC,EAAUJ,EAAWH,IAAkB,GAAK,GAAGA,IAAkB,GAAGM,gBAAkB,IACtFE,EAAUL,EAAWF,IAAkB,GAAK,GAAGA,IAAkB,GAAGK,gBAAkB,IAG5FT,EAAQY,SAASF,EAASG,EAAQC,iBAAiB,GAEnDd,EAAQY,SAASD,EAASE,EAAQC,iBAAiB,GAEnDd,EAAQY,SAASP,EAAUQ,EAAQC,iBAAiB,GAEpD,MAAMN,KAAEA,EAAIO,KAAEA,EAAIC,KAAEA,GAAShB,EAAQiB,UAC/BC,EAAWC,EAAeC,eAC9B,IAAIZ,EAAKa,UAAUC,KAAI,EAAGC,aAAcA,MAEnCC,GAAoBL,EAAeC,eACxCK,EAAMC,cAAc,OAAQ1B,EAAQ2B,gBAAgBZ,KACpDa,MAAM,YACDC,GAAoBV,EAAeC,eACxCK,EAAMC,cAAc,OAAQ1B,EAAQ2B,gBAAgBX,KACpDY,MAAM,WAER,OAAO7B,EACJ+B,QAAQ,qCAAsC,SAASZ,YACvDY,QAAQ,gBAAiBN,GACzBM,QAAQ,gBAAiBD,EAC7B,CAKM/B,gBAAgBE,GACrB,MAAMe,KAAEA,EAAIC,KAAEA,EAAIe,WAAEA,GAAe/B,EAAQiB,UAE3C,MAAO,CACLF,KAAM,IAAIA,EAAKiB,WACfhB,KAAM,IAAIA,EAAKgB,WACfD,WAAY,IAAIA,GAEnB"}
1
+ {"version":3,"file":"index.js","sources":["../../src/server/index.ts"],"sourcesContent":["// noinspection HtmlRequiredTitleElement\n\nimport htmlParser from 'html-react-parser';\nimport type { ReactElement } from 'react';\nimport React from 'react';\nimport ReactDOMServer from 'react-dom/server';\nimport Manager from '../manager';\n\ninterface IMetaManagerState {\n html: [string, Record<string, any>][];\n body: [string, Record<string, any>][];\n containers: string[];\n}\n\n/**\n * Helpers for server side\n */\nclass ServerManager {\n /**\n * Parse existing head tags from html and merge with tags received at the render stage (from manager)\n *\n * @return new html with actual meta tags\n */\n public static inject(htmlStr: string, manager: Manager): string {\n const matchedMetaHtml = htmlStr.match(/<head[^/].+?>?(?<meta>.+)<\\/head>/s);\n const matchedHtmlAttr = htmlStr.match(/<html[^>]*?>/s);\n const matchedBodyAttr = htmlStr.match(/<body[^>]*?>/s);\n const rootTags = htmlParser(matchedMetaHtml?.groups?.meta.trim() ?? '');\n const htmlTag = htmlParser(matchedHtmlAttr?.[0] ? `${matchedHtmlAttr?.[0].trim()}</html>` : '');\n const bodyTag = htmlParser(matchedBodyAttr?.[0] ? `${matchedBodyAttr?.[0].trim()}</body>` : '');\n\n // add root html props\n manager.pushTags(htmlTag, Manager.rootContainerId, false);\n // add root body props\n manager.pushTags(bodyTag, Manager.rootContainerId, false);\n // add root meta tags to manager\n manager.pushTags(rootTags, Manager.rootContainerId, false);\n\n const { meta, html, body } = manager.getTags();\n const htmlMeta = ReactDOMServer.renderToString(\n [...meta.values()].map(({ element }) => element) as unknown as ReactElement,\n );\n // Render a neutral element so React 19 does not insert document structure.\n const [htmlTagWithProps] = ReactDOMServer.renderToStaticMarkup(\n React.createElement('div', manager.getRootTagProps(html)),\n )\n .replace(/^<div/, '<html')\n .split('</div>');\n const [bodyTagWithProps] = ReactDOMServer.renderToStaticMarkup(\n React.createElement('div', manager.getRootTagProps(body)),\n )\n .replace(/^<div/, '<body')\n .split('</div>');\n\n return htmlStr\n .replace(/<head[^/].+?>?(?<meta>.+)<\\/head>/s, `<head>${htmlMeta}</head>`)\n .replace(/<html[^>]*?>/s, htmlTagWithProps)\n .replace(/<body[^>]*?>/s, bodyTagWithProps);\n }\n\n /**\n * Get meta manager state to pass on client\n */\n public static getState(manager: Manager): IMetaManagerState {\n const { html, body, containers } = manager.getTags();\n\n return {\n html: [...html.entries()],\n body: [...body.entries()],\n containers: [...containers],\n };\n }\n}\n\nexport default ServerManager;\n"],"names":["ServerManager","static","htmlStr","manager","matchedMetaHtml","match","matchedHtmlAttr","matchedBodyAttr","rootTags","htmlParser","groups","meta","trim","htmlTag","bodyTag","pushTags","Manager","rootContainerId","html","body","getTags","htmlMeta","ReactDOMServer","renderToString","values","map","element","htmlTagWithProps","renderToStaticMarkup","React","createElement","getRootTagProps","replace","split","bodyTagWithProps","containers","entries"],"mappings":"mHAiBA,MAAMA,EAMGC,cAAcC,EAAiBC,GACpC,MAAMC,EAAkBF,EAAQG,MAAM,sCAChCC,EAAkBJ,EAAQG,MAAM,iBAChCE,EAAkBL,EAAQG,MAAM,iBAChCG,EAAWC,EAAWL,GAAiBM,QAAQC,KAAKC,QAAU,IAC9DC,EAAUJ,EAAWH,IAAkB,GAAK,GAAGA,IAAkB,GAAGM,gBAAkB,IACtFE,EAAUL,EAAWF,IAAkB,GAAK,GAAGA,IAAkB,GAAGK,gBAAkB,IAG5FT,EAAQY,SAASF,EAASG,EAAQC,iBAAiB,GAEnDd,EAAQY,SAASD,EAASE,EAAQC,iBAAiB,GAEnDd,EAAQY,SAASP,EAAUQ,EAAQC,iBAAiB,GAEpD,MAAMN,KAAEA,EAAIO,KAAEA,EAAIC,KAAEA,GAAShB,EAAQiB,UAC/BC,EAAWC,EAAeC,eAC9B,IAAIZ,EAAKa,UAAUC,KAAI,EAAGC,aAAcA,MAGnCC,GAAoBL,EAAeM,qBACxCC,EAAMC,cAAc,MAAO3B,EAAQ4B,gBAAgBb,KAElDc,QAAQ,QAAS,SACjBC,MAAM,WACFC,GAAoBZ,EAAeM,qBACxCC,EAAMC,cAAc,MAAO3B,EAAQ4B,gBAAgBZ,KAElDa,QAAQ,QAAS,SACjBC,MAAM,UAET,OAAO/B,EACJ8B,QAAQ,qCAAsC,SAASX,YACvDW,QAAQ,gBAAiBL,GACzBK,QAAQ,gBAAiBE,EAC7B,CAKMjC,gBAAgBE,GACrB,MAAMe,KAAEA,EAAIC,KAAEA,EAAIgB,WAAEA,GAAehC,EAAQiB,UAE3C,MAAO,CACLF,KAAM,IAAIA,EAAKkB,WACfjB,KAAM,IAAIA,EAAKiB,WACfD,WAAY,IAAIA,GAEnB"}