@lomray/react-head-manager 1.1.1 → 2.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Lomray Software
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # React meta tags manager with SSR and Suspense support
2
+
3
+ ![npm](https://img.shields.io/npm/v/@lomray/react-head-manager)
4
+ ![GitHub](https://img.shields.io/github/license/Lomray-Software/react-head-manager)
5
+
6
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=react-head-manager&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=react-head-manager)
7
+ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=react-head-manager&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=react-head-manager)
8
+ [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=react-head-manager&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=react-head-manager)
9
+ [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=react-head-manager&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=react-head-manager)
10
+ [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=react-head-manager&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=react-head-manager)
11
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=react-head-manager&metric=coverage)](https://sonarcloud.io/summary/new_code?id=react-head-manager)
12
+
13
+ ## Getting started
14
+
15
+ The package is distributed using [npm](https://www.npmjs.com/), the node package manager.
16
+
17
+ ```
18
+ npm i --save @lomray/react-head-manager
19
+ ```
20
+
21
+ __WARNING:__ this package use [@lomray/consistent-suspense](https://github.com/Lomray-Software/consistent-suspense) for generate stable id's inside Suspense.
22
+
23
+ ## Usage
24
+ ```typescript jsx
25
+ import { ConsistentSuspenseProvider } from '@lomray/consistent-suspense';
26
+ import { MetaManagerProvider, Manager, Meta } from '@lomray/react-head-manager';
27
+
28
+ const manager = new Manager();
29
+
30
+ /**
31
+ * Root component container
32
+ */
33
+ const App = ({ children }) => {
34
+ const [state] = useState();
35
+
36
+ return (
37
+ <ConsistentSuspenseProvider> {/** required, see warning above **/}
38
+ <MetaManagerProvider manager={manager}>
39
+ <MyComponent />
40
+ </MetaManagerProvider>
41
+ </ConsistentSuspenseProvider>
42
+ )
43
+ }
44
+
45
+ /**
46
+ * Some component
47
+ */
48
+ const MyComponent = () => {
49
+ return (
50
+ <>
51
+ <Meta>
52
+ <title>Example</title>
53
+ <meta name="description" content="Description example" />
54
+ <meta name="keywords" content="test,key" />
55
+ <body data-id="test" />
56
+ </Meta>
57
+ <div>Some component....</div>
58
+ </>
59
+ )
60
+ }
61
+ ```
62
+
63
+ Change tags order:
64
+ ```typescript jsx
65
+ /**
66
+ * Way 1
67
+ */
68
+ const manager = new Manager();
69
+ manager.setTagsDefinitions({
70
+ title: 1, // change order for title tag
71
+ "meta[name='viewport']": 2, // change order for meta viewport tag
72
+ meta: 100, // change for all meta tags
73
+ script: 200, // change for all script tags
74
+ });
75
+
76
+ /**
77
+ * Way 2
78
+ */
79
+ <Meta>
80
+ <title data-order={1}>Example</title>
81
+ <meta data-order={3} name="description" content="Description example" />
82
+ <meta data-order={2} name="keywords" content="test,key" />
83
+ </Meta>
84
+
85
+ /**
86
+ * You can also use both...
87
+ */
88
+ ```
89
+
90
+ Explore [demo app](https://github.com/Lomray-Software/vite-template) to more understand.
91
+
92
+ ## Bugs and feature requests
93
+
94
+ Bug or a feature request, [please open a new issue](https://github.com/Lomray-Software/react-head-manager/issues/new).
95
+
96
+ ## License
97
+ Made with 💚
98
+
99
+ Published under [MIT License](./LICENSE).
package/context.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@lomray/consistent-suspense"),r=require("react"),t=require("./manager.js");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=a(r);const s=n.default.createContext({manager:new t}),u=()=>r.useContext(s);exports.Meta=t=>{let{children:a}=t;const{manager:n}=u(),s=e.useId();return n.isServer&&n.pushTags(a,s),r.useEffect((()=>(n.pushTags(a,s),()=>n.removeTags(s))),[n,a]),null},exports.MetaManagerProvider=e=>{let{children:t,manager:a}=e;r.useEffect((()=>{a.analyzeClientHead()}),[]);const u=r.useMemo((()=>({manager:a})),[a]);return n.default.createElement(s.Provider,{value:u,children:t})},exports.useMetaManager=u;
1
+ import{useId as e}from"@lomray/consistent-suspense";import r,{useEffect as n,useMemo as a,useContext as t}from"react";import o from"./manager.js";const s=r.createContext({manager:new o}),m=({children:e,manager:t})=>{n((()=>{t.analyzeClientHead()}),[]);const o=a((()=>({manager:t})),[t]);return r.createElement(s.Provider,{value:o,children:e})},c=()=>t(s),i=({children:r})=>{const{manager:a}=c(),t=e();return a.isServer&&a.pushTags(r,t),n((()=>(a.pushTags(r,t),()=>a.removeTags(t))),[a,r]),null};export{i as Meta,m as MetaManagerProvider,c as useMetaManager};
2
2
  //# sourceMappingURL=context.js.map
package/context.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sources":["../src/context.tsx"],"sourcesContent":["import { useId } from '@lomray/consistent-suspense';\nimport type { FC, PropsWithChildren } from 'react';\nimport React, { useContext, useEffect, useMemo } from 'react';\nimport Manager from './manager';\n\ninterface IMetaManager {\n manager: Manager;\n}\n\n/**\n * Global meta manager context\n */\nconst MetaManagerContext = React.createContext<IMetaManager>({ manager: new Manager() });\n\n/**\n * Global application meta manager provider\n * @constructor\n */\nconst MetaManagerProvider: FC<PropsWithChildren<IMetaManager>> = ({ children, manager }) => {\n /**\n * Analyze client default meta tags\n */\n useEffect(() => {\n manager.analyzeClientHead();\n }, []);\n\n const value = useMemo(() => ({ manager }), [manager]);\n\n return <MetaManagerContext.Provider value={value} children={children} />;\n};\n\nconst useMetaManager = () => useContext(MetaManagerContext);\n\n/**\n * Meta tag component\n * @constructor\n */\nconst Meta: FC<PropsWithChildren> = ({ children }) => {\n const { manager } = useMetaManager();\n const containerId = useId();\n\n /**\n * Server side push\n */\n if (manager.isServer) {\n manager.pushTags(children, containerId);\n }\n\n /**\n * Client side push\n */\n useEffect(() => {\n manager.pushTags(children, containerId);\n\n return () => manager.removeTags(containerId);\n }, [manager, children]);\n\n return null;\n};\n\nexport { Meta, MetaManagerProvider, useMetaManager };\n"],"names":["MetaManagerContext","React","createContext","manager","Manager","useMetaManager","useContext","_ref2","children","containerId","useId","isServer","pushTags","useEffect","removeTags","_ref","analyzeClientHead","value","useMemo","createElement","Provider"],"mappings":"iPAYA,MAAMA,EAAqBC,EAAK,QAACC,cAA4B,CAAEC,QAAS,IAAIC,IAmBtEC,EAAiBA,IAAMC,EAAUA,WAACN,gBAMJO,IAAiB,IAAhBC,SAAEA,GAAUD,EAC/C,MAAMJ,QAAEA,GAAYE,IACdI,EAAcC,EAAAA,QAkBpB,OAbIP,EAAQQ,UACVR,EAAQS,SAASJ,EAAUC,GAM7BI,EAAAA,WAAU,KACRV,EAAQS,SAASJ,EAAUC,GAEpB,IAAMN,EAAQW,WAAWL,KAC/B,CAACN,EAASK,IAEN,IAAI,8BAvCoDO,IAA0B,IAAzBP,SAAEA,EAAQL,QAAEA,GAASY,EAIrFF,EAAAA,WAAU,KACRV,EAAQa,mBAAmB,GAC1B,IAEH,MAAMC,EAAQC,EAAAA,SAAQ,KAAO,CAAEf,aAAY,CAACA,IAE5C,OAAOF,UAAAkB,cAACnB,EAAmBoB,SAAQ,CAACH,MAAOA,EAAOT,SAAUA,GAAY"}
1
+ {"version":3,"file":"context.js","sources":["../src/context.tsx"],"sourcesContent":["import { useId } from '@lomray/consistent-suspense';\nimport type { FC, PropsWithChildren } from 'react';\nimport React, { useContext, useEffect, useMemo } from 'react';\nimport Manager from './manager';\n\ninterface IMetaManager {\n manager: Manager;\n}\n\n/**\n * Global meta manager context\n */\nconst MetaManagerContext = React.createContext<IMetaManager>({ manager: new Manager() });\n\n/**\n * Global application meta manager provider\n * @constructor\n */\nconst MetaManagerProvider: FC<PropsWithChildren<IMetaManager>> = ({ children, manager }) => {\n /**\n * Analyze client default meta tags\n */\n useEffect(() => {\n manager.analyzeClientHead();\n }, []);\n\n const value = useMemo(() => ({ manager }), [manager]);\n\n return <MetaManagerContext.Provider value={value} children={children} />;\n};\n\nconst useMetaManager = () => useContext(MetaManagerContext);\n\n/**\n * Meta tag component\n * @constructor\n */\nconst Meta: FC<PropsWithChildren> = ({ children }) => {\n const { manager } = useMetaManager();\n const containerId = useId();\n\n /**\n * Server side push\n */\n if (manager.isServer) {\n manager.pushTags(children, containerId);\n }\n\n /**\n * Client side push\n */\n useEffect(() => {\n manager.pushTags(children, containerId);\n\n return () => manager.removeTags(containerId);\n }, [manager, children]);\n\n return null;\n};\n\nexport { Meta, MetaManagerProvider, useMetaManager };\n"],"names":["MetaManagerContext","React","createContext","manager","Manager","MetaManagerProvider","children","useEffect","analyzeClientHead","value","useMemo","createElement","Provider","useMetaManager","useContext","Meta","containerId","useId","isServer","pushTags","removeTags"],"mappings":"kJAYA,MAAMA,EAAqBC,EAAMC,cAA4B,CAAEC,QAAS,IAAIC,IAMtEC,EAA2D,EAAGC,WAAUH,cAI5EI,GAAU,KACRJ,EAAQK,mBAAmB,GAC1B,IAEH,MAAMC,EAAQC,GAAQ,KAAO,CAAEP,aAAY,CAACA,IAE5C,OAAOF,EAACU,cAAAX,EAAmBY,SAAS,CAAAH,MAAOA,EAAOH,SAAUA,GAAY,EAGpEO,EAAiB,IAAMC,EAAWd,GAMlCe,EAA8B,EAAGT,eACrC,MAAMH,QAAEA,GAAYU,IACdG,EAAcC,IAkBpB,OAbId,EAAQe,UACVf,EAAQgB,SAASb,EAAUU,GAM7BT,GAAU,KACRJ,EAAQgB,SAASb,EAAUU,GAEpB,IAAMb,EAAQiB,WAAWJ,KAC/B,CAACb,EAASG,IAEN,IAAI"}
package/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./context.js"),r=require("./manager.js");exports.Meta=e.Meta,exports.MetaManagerProvider=e.MetaManagerProvider,exports.useMetaManager=e.useMetaManager,exports.Manager=r;
1
+ export{Meta,MetaManagerProvider,useMetaManager}from"./context.js";export{default as Manager}from"./manager.js";
2
2
  //# sourceMappingURL=index.js.map
package/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var e=require("@babel/runtime/helpers/defineProperty"),t=require("html-react-parser"),i=require("react");function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n,s=r(e),o=r(t),a=r(i);!function(e){e.init="init",e.synced="synced",e.drain="drain"}(n||(n={}));class l{constructor(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s.default(this,"isServer","undefined"==typeof window),s.default(this,"tags",void 0),s.default(this,"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}}),s.default(this,"reservedAttributes",{order:"data-order"}),s.default(this,"syncTimerId",null),this.tags={html:new Map(null==e?void 0:e.html),body:new Map(null==e?void 0:e.body),meta:new Map(null==e?void 0:e.meta),containers:new Set(null==e?void 0:e.containers)}}setTagsDefinitions(e){this.tagsDefinitions={...this.tagsDefinitions,...e}}static cleanupElementProps(e){const{children:t,dangerouslySetInnerHTML:i,...r}=e;return r}getTags(){const{meta:e,body:t,html:i,containers:r}=this.tags;return{body:t,html:i,meta:new Map([...e.entries()].sort(((e,t)=>{let[,i]=e,[,r]=t;return i.order-r.order}))),containers:r}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:i}=e,r=document.createElement(t);return this.applyDomElementAttributes(r,i),r}buildKeyByProps(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r="";for(const e of["id","name","href","src"])if(t[e]){r="[".concat(e,"='").concat(t[e],"']");break}if(r||i||(r=Object.entries(t).map((e=>{let[t,i]=e;return"string"==typeof i&&"[".concat(t,"]")})).filter(Boolean).join("")),r)return"".concat(e).concat(r)}getDefaultKey(e,t,i){return"".concat(e,"-").concat(t,"-").concat(i,"-not-unique")}isNotUniqueTag(e){return e.endsWith("-not-unique")}cloneElement(e){var t;const{type:i}=e,r={...null!==(t=null==e?void 0:e.props)&&void 0!==t?t:{}};return Object.values(this.reservedAttributes).forEach((e=>{r[e]&&delete r[e]})),"title"===i&&Array.isArray(r.children)&&(r.children=r.children.join("")),{element:a.default.createElement(i,r),elementProps:r}}pushElements(e,t){let r=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:n.init;i.Children.forEach(e,((e,i)=>{var o,a,d,u,c,h,m;if(!e||"object"!=typeof e||!("type"in e)||!e.type)return;const{type:v}=e,{element:f,elementProps:g}=this.cloneElement(e),y=g[this.reservedAttributes.order]?Number(g[this.reservedAttributes.order]):void 0;let p=null!==(o=null!=y?y:null===(a=this.tagsDefinitions[v])||void 0===a?void 0:a.order)&&void 0!==o?o:1e3,b=null!==(d=null===(u=this.tagsDefinitions[v])||void 0===u?void 0:u.key)&&void 0!==d?d:v;switch(v){case"title":break;case"meta":const{charSet:e,httpEquiv:n}=g;b=e?"meta[charset]":n?"meta[httpEquiv]":this.buildKeyByProps(v,g);break;case"html":case"body":if(!r&&this.tags[v].has(b))return;return void this.tags[v].set(t,l.cleanupElementProps(g));default:b=null!==(c=this.buildKeyByProps(v,g,!0))&&void 0!==c?c:this.getDefaultKey(v,t,i)}if((r||!this.tags.meta.has(b))&&(!this.isNotUniqueTag(b)||(null===(h=this.tags.meta.get(b))||void 0===h?void 0:h.status)!==n.synced)){var E;if(null!==(m=this.tagsDefinitions[b])&&void 0!==m&&m.order)p=null===(E=this.tagsDefinitions[b])||void 0===E?void 0:E.order;this.tags.meta.set(b,{element:this.isServer?f:void 0,domElement:t===l.rootContainerId?void 0:this.createDomElement(f),order:p,containerId:t,status:s})}})),this.tags.containers.add(t)}getRootTagProps(e){return e.size?[...e.values()].reduce(((e,t)=>({...e,...t})),{}):{}}applyDomElementAttributes(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const i=e.tagName.toLowerCase(),r=Object.values(this.reservedAttributes);Object.entries(t).forEach((t=>{let[n,s]=t;switch(n){case"children":return void(e.innerHTML=s);case"style":return Object.entries(s).forEach((t=>{let[i,r]=t;e.style[i]=r}))}r[n]||e.setAttribute(this.replaceAttribute(i,n),s)}))}syncMeta(e){var t;const{meta:i,html:r,body:s}=this.getTags();this.syncTimerId&&clearTimeout(this.syncTimerId);for(const{name:e,value:t}of[{name:"html",value:r},{name:"body",value:s}]){var o;const i=this.getRootTagProps(t),r=document.querySelector(e);(null!==(o=null==r?void 0:r.getAttributeNames())&&void 0!==o?o:[]).forEach((e=>{i[e]||r.removeAttribute(e)})),Object.keys(i).length&&r&&this.applyDomElementAttributes(r,i)}if(!i.size||this.isServer)return;const a=null===(t=document.getElementsByTagName("head"))||void 0===t?void 0:t[0];let l;i.forEach(((t,r)=>{const{status:s,domElement:o}=t;if(s===n.synced||!o||e&&e!==t.containerId)return void(l=o);if(s===n.drain)return l=o.previousSibling,null==o||o.remove(),void i.delete(r);if(t.status=n.synced,!this.isNotUniqueTag(r)){const e=document.querySelector(r);if(e)return l=o,void e.replaceWith(o)}const d=l&&l.nextSibling;d?a.insertBefore(o,d):a.append(o),l=o}))}pushTags(e,t){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];const r=this.tags.containers.has(t);this.pushElements(e,t,i,r?n.synced:n.init),this.isServer||r||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,i)=>{if(t.containerId===e&&t.containerId!==l.rootContainerId){var r;if(this.isNotUniqueTag(i))return null===(r=t.domElement)||void 0===r||r.remove(),void this.tags.meta.delete(i);t.status=n.drain}})),e!==l.rootContainerId&&(this.tags.body.delete(e),this.tags.html.delete(e),this.tags.containers.delete(e),this.syncTimerId=setTimeout((()=>this.syncMeta()),500))}analyzeClientHead(){var e,t;if(this.isServer)return;for(const e of["html","body"]){var i,r;const t=null===(i=document.getElementsByTagName(e))||void 0===i||null===(r=i[0].cloneNode(!1))||void 0===r?void 0:r.outerHTML;this.pushElements(o.default(t),l.rootContainerId,!1)}const s=null===(e=document.getElementsByTagName("head"))||void 0===e?void 0:e[0],a=o.default(null!==(t=null==s?void 0:s.innerHTML)&&void 0!==t?t:"");this.pushElements(a,l.rootContainerId,!1,n.synced);const d=[...this.tags.meta.values()];s.childNodes.forEach(((e,t)=>{const i=d[t];i&&(i.domElement=e)}))}}s.default(l,"rootContainerId","root"),module.exports=l;
1
+ import e from"html-react-parser";import t,{Children as s}from"react";var r;!function(e){e.init="init",e.synced="synced",e.drain="drain"}(r||(r={}));class i{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:s,...r}=e;return r}getTags(){const{meta:e,body:t,html:s,containers:r}=this.tags;return{body:t,html:s,meta:new Map([...e.entries()].sort((([,e],[,t])=>e.order-t.order))),containers:r}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:s}=e,r=document.createElement(t);return this.applyDomElementAttributes(r,s),r}buildKeyByProps(e,t={},s=!1){let r="";for(const e of["id","name","href","src"])if(t[e]){r=`[${e}='${t[e]}']`;break}if(r||s||(r=Object.entries(t).map((([e,t])=>"string"==typeof t&&`[${e}]`)).filter(Boolean).join("")),r)return`${e}${r}`}getDefaultKey(e,t,s){return`${e}-${t}-${s}-not-unique`}isNotUniqueTag(e){return e.endsWith("-not-unique")}cloneElement(e){const{type:s}=e,r={...e?.props??{}};return Object.values(this.reservedAttributes).forEach((e=>{r[e]&&delete r[e]})),"title"===s&&Array.isArray(r.children)&&(r.children=r.children.join("")),{element:t.createElement(s,r),elementProps:r}}pushElements(e,t,n=!0,o=r.init){s.forEach(e,((e,s)=>{if(!e||"object"!=typeof e||!("type"in e)||!e.type)return;const{type:a}=e,{element:c,elementProps:d}=this.cloneElement(e);let h=(d[this.reservedAttributes.order]?Number(d[this.reservedAttributes.order]):void 0)??this.tagsDefinitions[a]?.order??1e3,l=this.tagsDefinitions[a]?.key??a;switch(a){case"title":break;case"meta":const{charSet:e,httpEquiv:r}=d;l=e?"meta[charset]":r?"meta[httpEquiv]":this.buildKeyByProps(a,d);break;case"html":case"body":if(!n&&this.tags[a].has(l))return;return void this.tags[a].set(t,i.cleanupElementProps(d));default:l=this.buildKeyByProps(a,d,!0)??this.getDefaultKey(a,t,s)}!n&&this.tags.meta.has(l)||this.isNotUniqueTag(l)&&this.tags.meta.get(l)?.status===r.synced||(this.tagsDefinitions[l]?.order&&(h=this.tagsDefinitions[l]?.order),this.tags.meta.set(l,{element:this.isServer?c:void 0,domElement:t===i.rootContainerId?void 0:this.createDomElement(c),order:h,containerId:t,status:o}))})),this.tags.containers.add(t)}getRootTagProps(e){return e.size?[...e.values()].reduce(((e,t)=>({...e,...t})),{}):{}}applyDomElementAttributes(e,t={}){const s=e.tagName.toLowerCase(),r=Object.values(this.reservedAttributes);Object.entries(t).forEach((([t,i])=>{switch(t){case"children":return void(e.innerHTML=i);case"style":return Object.entries(i).forEach((([t,s])=>{e.style[t]=s}))}r[t]||e.setAttribute(this.replaceAttribute(s,t),i)}))}syncMeta(e){const{meta:t,html:s,body:i}=this.getTags();this.syncTimerId&&clearTimeout(this.syncTimerId);for(const{name:e,value:t}of[{name:"html",value:s},{name:"body",value:i}]){const s=this.getRootTagProps(t),r=document.querySelector(e);(r?.getAttributeNames()??[]).forEach((e=>{s[e]||r.removeAttribute(e)})),Object.keys(s).length&&r&&this.applyDomElementAttributes(r,s)}if(!t.size||this.isServer)return;const n=document.getElementsByTagName("head")?.[0];let o;t.forEach(((s,i)=>{const{status:a,domElement:c}=s;if(a===r.synced||!c||e&&e!==s.containerId)return void(o=c);if(a===r.drain)return o=c.previousSibling,c?.remove(),void t.delete(i);if(s.status=r.synced,!this.isNotUniqueTag(i)){const e=document.querySelector(i);if(e)return o=c,void e.replaceWith(c)}const d=o&&o.nextSibling;d?n.insertBefore(c,d):n.append(c),o=c}))}pushTags(e,t,s=!0){const i=this.tags.containers.has(t);this.pushElements(e,t,s,i?r.synced:r.init),this.isServer||i||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,s)=>{if(t.containerId===e&&t.containerId!==i.rootContainerId)return this.isNotUniqueTag(s)?(t.domElement?.remove(),void this.tags.meta.delete(s)):void(t.status=r.drain)})),e!==i.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 t of["html","body"]){const s=document.getElementsByTagName(t)?.[0].cloneNode(!1)?.outerHTML;this.pushElements(e(s),i.rootContainerId,!1)}const t=document.getElementsByTagName("head")?.[0],s=e(t?.innerHTML??"");this.pushElements(s,i.rootContainerId,!1,r.synced);const n=[...this.tags.meta.values()];t.childNodes.forEach(((e,t)=>{const s=n[t];s&&(s.domElement=e)}))}}export{i 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 htmlParser from 'html-react-parser';\nimport type { ReactElement, ReactNode } from 'react';\nimport React, { Children } from 'react';\n\nenum TagStatus {\n init = 'init',\n synced = 'synced',\n drain = 'drain',\n}\n\nexport interface IMetaManagerTags {\n html: Map<string, Record<string, any>>; // containerId => styles\n body: Map<string, Record<string, any>>; // 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 * Get meta tags\n */\n public getTags(): IMetaManagerTags {\n const { meta, body, html, containers } = this.tags;\n const sortedTags = new Map(\n [...meta.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n );\n\n return {\n body,\n html,\n meta: sortedTags,\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 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 * 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 Children.forEach(elements, (child, index) => {\n // skip unsupported elements\n if (!child || typeof child !== 'object' || !('type' in child) || !child.type) {\n return;\n }\n\n const { type } = child;\n const { element, elementProps } = this.cloneElement(child);\n const elementOrder = elementProps[this.reservedAttributes.order]\n ? Number(elementProps[this.reservedAttributes.order])\n : undefined;\n\n let order = elementOrder ?? this.tagsDefinitions[type as string]?.order ?? 1000;\n let key = this.tagsDefinitions[type as string]?.key ?? (type as string);\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, Manager.cleanupElementProps(elementProps));\n\n return;\n\n case 'link':\n case 'script':\n case 'noscript':\n case 'style':\n default:\n key =\n this.buildKeyByProps(type as string, elementProps, true) ??\n this.getDefaultKey(type as string, 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 // custom order\n if (this.tagsDefinitions[key]?.order) {\n order = this.tagsDefinitions[key]?.order;\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,\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,\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;\n\n return;\n\n case 'style':\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n element['style'][styleName] = styleValue;\n },\n );\n }\n\n if (reservedAttributes[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\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 // 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 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":["TagStatus","Manager","constructor","tags","arguments","length","undefined","_defineProperty","this","window","order","title","base","meta","link","script","noscript","style","html","Map","body","containers","Set","setTagsDefinitions","definitions","tagsDefinitions","static","props","children","_","dangerouslySetInnerHTML","__","restProps","getTags","entries","sort","_ref","_ref2","tagA","tagB","replaceAttribute","tagName","attribute","toLowerCase","createDomElement","element","isServer","type","domElement","document","createElement","applyDomElementAttributes","buildKeyByProps","isSkipProps","key","uniqueAttr","concat","Object","map","_ref3","k","v","filter","Boolean","join","getDefaultKey","containerId","index","isNotUniqueTag","endsWith","cloneElement","_element$props","values","reservedAttributes","forEach","attrName","Array","isArray","React","elementProps","pushElements","elements","isReplace","status","init","Children","child","_ref4","_this$tagsDefinitions","_this$tagsDefinitions2","_this$tagsDefinitions3","_this$buildKeyByProps","_this$tags$meta$get","_this$tagsDefinitions4","elementOrder","Number","charSet","httpEquiv","has","set","cleanupElementProps","get","synced","_this$tagsDefinitions5","rootContainerId","add","getRootTagProps","size","reduce","res","val","_ref5","name","value","innerHTML","_ref6","styleName","styleValue","setAttribute","syncMeta","_document$getElements","syncTimerId","clearTimeout","_domElement$getAttrib","tagProps","querySelector","getAttributeNames","removeAttribute","keys","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","pushTags","isAdded","removeTags","_metaTag$domElement","setTimeout","analyzeClientHead","_document$getElements4","_head$innerHTML","_document$getElements2","_document$getElements3","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 htmlParser from 'html-react-parser';\nimport type { ReactElement, ReactNode } from 'react';\nimport React, { Children } from 'react';\n\nenum TagStatus {\n init = 'init',\n synced = 'synced',\n drain = 'drain',\n}\n\nexport interface IMetaManagerTags {\n html: Map<string, Record<string, any>>; // containerId => styles\n body: Map<string, Record<string, any>>; // 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 * Get meta tags\n */\n public getTags(): IMetaManagerTags {\n const { meta, body, html, containers } = this.tags;\n const sortedTags = new Map(\n [...meta.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n );\n\n return {\n body,\n html,\n meta: sortedTags,\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 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 * 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 Children.forEach(elements, (child, index) => {\n // skip unsupported elements\n if (!child || typeof child !== 'object' || !('type' in child) || !child.type) {\n return;\n }\n\n const { type } = child;\n const { element, elementProps } = this.cloneElement(child);\n const elementOrder = elementProps[this.reservedAttributes.order]\n ? Number(elementProps[this.reservedAttributes.order])\n : undefined;\n\n let order = elementOrder ?? this.tagsDefinitions[type as string]?.order ?? 1000;\n let key = this.tagsDefinitions[type as string]?.key ?? (type as string);\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, Manager.cleanupElementProps(elementProps));\n\n return;\n\n case 'link':\n case 'script':\n case 'noscript':\n case 'style':\n default:\n key =\n this.buildKeyByProps(type as string, elementProps, true) ??\n this.getDefaultKey(type as string, 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 // custom order\n if (this.tagsDefinitions[key]?.order) {\n order = this.tagsDefinitions[key]?.order;\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,\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,\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;\n\n return;\n\n case 'style':\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n element['style'][styleName] = styleValue;\n },\n );\n }\n\n if (reservedAttributes[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\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 // 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 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":["TagStatus","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","getTags","entries","sort","tagA","tagB","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","pushElements","elements","isReplace","status","init","Children","child","Number","undefined","charSet","httpEquiv","has","set","cleanupElementProps","get","synced","rootContainerId","add","getRootTagProps","size","reduce","res","val","name","value","innerHTML","styleName","styleValue","setAttribute","syncMeta","clearTimeout","tagProps","querySelector","getAttributeNames","removeAttribute","keys","length","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","pushTags","isAdded","removeTags","setTimeout","analyzeClientHead","htmlTag","cloneNode","htmlParser","reactElements","childNodes","node","i","existedElem"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@lomray/react-head-manager",
3
- "version": "1.1.1",
3
+ "version": "2.0.1",
4
4
  "description": "React meta tags manager with SSR and Suspense support.",
5
+ "type": "module",
5
6
  "main": "index.js",
6
7
  "types": "index.d.ts",
7
8
  "keywords": [
@@ -37,49 +38,39 @@
37
38
  "ts:check": "tsc --project ./tsconfig.json --skipLibCheck --noemit"
38
39
  },
39
40
  "dependencies": {
40
- "@babel/runtime": "^7.19.0",
41
- "@lomray/consistent-suspense": "^1.2.2",
42
- "html-react-parser": "^4.0.0"
41
+ "html-react-parser": "^5.0.11"
43
42
  },
44
43
  "devDependencies": {
45
- "@babel/core": "^7.19.3",
46
- "@babel/plugin-proposal-class-properties": "^7.18.6",
47
- "@babel/plugin-transform-runtime": "^7.19.1",
48
- "@babel/preset-env": "^7.19.4",
49
- "@babel/preset-react": "^7.18.6",
50
- "@commitlint/cli": "^17.6.5",
51
- "@commitlint/config-conventional": "^17.6.5",
52
- "@lomray/eslint-config": "^3.0.0",
53
- "@lomray/prettier-config": "^1.2.0",
54
- "@rollup/plugin-json": "^6.0.0",
55
- "@rollup/plugin-terser": "^0.4.3",
56
- "@types/react": "^18.2.7",
57
- "@types/react-dom": "^18.2.6",
58
- "@typescript-eslint/eslint-plugin": "^5.59.7",
44
+ "@commitlint/cli": "^18.4.4",
45
+ "@commitlint/config-conventional": "^18.4.4",
46
+ "@lomray/eslint-config": "^4.0.1",
47
+ "@lomray/prettier-config": "^2.0.1",
48
+ "@rollup/plugin-json": "^6.1.0",
49
+ "@rollup/plugin-terser": "^0.4.4",
50
+ "@types/react": "^18.2.47",
51
+ "@types/react-dom": "^18.2.18",
52
+ "@typescript-eslint/eslint-plugin": "^6.18.1",
59
53
  "@zerollup/ts-transform-paths": "^1.7.18",
60
- "eslint": "^8.41.0",
61
- "eslint-config-prettier": "^8.8.0",
62
- "eslint-plugin-import": "^2.27.5",
63
- "eslint-plugin-jsx-a11y": "^6.7.1",
64
- "eslint-plugin-prettier": "^4.2.1",
54
+ "eslint": "^8.56.0",
55
+ "eslint-config-prettier": "^9.1.0",
56
+ "eslint-plugin-import": "^2.29.1",
57
+ "eslint-plugin-jsx-a11y": "^6.8.0",
58
+ "eslint-plugin-prettier": "^5.1.3",
65
59
  "husky": "^8.0.3",
66
- "i": "^0.3.7",
67
- "lint-staged": "^13.2.2",
68
- "npm": "^9.7.2",
69
- "prettier": "^2.8.8",
60
+ "lint-staged": "^15.2.0",
61
+ "prettier": "^3.1.1",
70
62
  "react": "^18.2.0",
71
- "rollup": "2.79.1",
72
- "rollup-plugin-copy": "^3.4.0",
63
+ "rollup": "^4.9.4",
64
+ "rollup-plugin-copy": "^3.5.0",
73
65
  "rollup-plugin-folder-input": "^1.0.1",
74
66
  "rollup-plugin-peer-deps-external": "^2.2.4",
75
- "rollup-plugin-ts": "^3.2.0",
76
- "semantic-release": "^21.0.2",
77
- "ttypescript": "^1.5.15",
78
- "typescript": "^4.7.4"
67
+ "rollup-plugin-ts": "^3.4.5",
68
+ "semantic-release": "^21.1.2",
69
+ "typescript": "^4.9.5"
79
70
  },
80
71
  "peerDependencies": {
72
+ "@lomray/consistent-suspense": ">=2.0.1",
81
73
  "react": ">=17.0.2",
82
- "react-dom": ">=17.0.2",
83
- "tslib": "^2.5.2"
74
+ "react-dom": ">=17.0.2"
84
75
  }
85
76
  }
package/server/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var e=require("@babel/runtime/helpers/inherits"),t=require("@babel/runtime/helpers/setPrototypeOf"),r=require("html-react-parser"),a=require("react"),n=require("react-dom/server"),o=require("../manager.js");function l(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=l(e),u=l(t),s=l(r),c=l(a),p=l(n);function d(){d=function(e,t){return new r(e,void 0,t)};var e=RegExp.prototype,t=new WeakMap;function r(e,a,n){var o=new RegExp(e,a);return t.set(o,n||t.get(e)),u.default(o,r.prototype)}function a(e,r){var a=t.get(r);return Object.keys(a).reduce((function(t,r){var n=a[r];if("number"==typeof n)t[r]=e[n];else{for(var o=0;void 0===e[n[o]]&&o+1<n.length;)o++;t[r]=e[n[o]]}return t}),Object.create(null))}return i.default(r,RegExp),r.prototype.exec=function(t){var r=e.exec.call(this,t);if(r){r.groups=a(r,this);var n=r.indices;n&&(n.groups=a(n,this))}return r},r.prototype[Symbol.replace]=function(r,n){if("string"==typeof n){var o=t.get(this);return e[Symbol.replace].call(this,r,n.replace(/\$<([^>]+)>/g,(function(e,t){var r=o[t];return"$"+(Array.isArray(r)?r.join("$"):r)})))}if("function"==typeof n){var l=this;return e[Symbol.replace].call(this,r,(function(){var e=arguments;return"object"!=typeof e[e.length-1]&&(e=[].slice.call(e)).push(a(e,l)),n.apply(this,e)}))}return e[Symbol.replace].call(this,r,n)},d.apply(this,arguments)}module.exports=class{static inject(e,t){var r,a;const n=e.match(d(/<head[^/][\s\S]+?>?([\s\S]+)<\/head>/,{meta:1})),l=e.match(/<html[^>]*?>/),i=e.match(/<body[^>]*?>/),u=s.default(null!==(r=null==n||null===(a=n.groups)||void 0===a?void 0:a.meta.trim())&&void 0!==r?r:""),h=s.default(null!=l&&l[0]?"".concat(null==l?void 0:l[0].trim(),"</html>"):""),f=s.default(null!=i&&i[0]?"".concat(null==i?void 0:i[0].trim(),"</body>"):"");t.pushTags(h,o.rootContainerId,!1),t.pushTags(f,o.rootContainerId,!1),t.pushTags(u,o.rootContainerId,!1);const{meta:m,html:g,body:y}=t.getTags(),v=p.default.renderToString([...m.values()].map((e=>{let{element:t}=e;return t}))),[b]=p.default.renderToString(c.default.createElement("html",t.getRootTagProps(g))).split("</html>"),[S]=p.default.renderToString(c.default.createElement("body",t.getRootTagProps(y))).split("</body>");return e.replace(d(/<head[^/][\s\S]+?>?([\s\S]+)<\/head>/,{meta:1}),"<head>".concat(v,"</head>")).replace(/<html[^>]*?>/,b).replace(/<body[^>]*?>/,S)}static getState(e){const{html:t,body:r,containers:a}=e.getTags();return{html:[...t.entries()],body:[...r.entries()],containers:[...a]}}};
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};
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":["static","htmlStr","manager","_matchedMetaHtml$grou","_matchedMetaHtml$grou2","matchedMetaHtml","match","_wrapRegExp","meta","matchedHtmlAttr","matchedBodyAttr","rootTags","htmlParser","groups","trim","htmlTag","concat","bodyTag","pushTags","Manager","rootContainerId","html","body","getTags","htmlMeta","ReactDOMServer","renderToString","values","map","_ref","element","htmlTagWithProps","React","createElement","getRootTagProps","split","bodyTagWithProps","replace","containers","entries"],"mappings":"m2CAiBA,MAMSA,cAAcC,EAAiBC,GAAgB,IAAAC,EAAAC,EACpD,MAAMC,EAAkBJ,EAAQK,MAAKC,EAAC,uCAAoC,CAAAC,KAAA,KACpEC,EAAkBR,EAAQK,MAAM,gBAChCI,EAAkBT,EAAQK,MAAM,gBAChCK,EAAWC,EAAU,QAAqC,QAArCT,EAACE,SAAuBD,QAARA,EAAfC,EAAiBQ,cAAjBT,IAAuBA,OAAR,EAAfA,EAAyBI,KAAKM,cAAM,IAAAX,EAAAA,EAAI,IAC9DY,EAAUH,EAAAA,QAAWH,SAAAA,EAAkB,GAAEO,GAAAA,OAAMP,aAAAA,EAAAA,EAAkB,GAAGK,OAAkB,WAAA,IACtFG,EAAUL,EAAAA,QAAWF,SAAAA,EAAkB,GAAEM,GAAAA,OAAMN,aAAAA,EAAAA,EAAkB,GAAGI,OAAkB,WAAA,IAG5FZ,EAAQgB,SAASH,EAASI,EAAQC,iBAAiB,GAEnDlB,EAAQgB,SAASD,EAASE,EAAQC,iBAAiB,GAEnDlB,EAAQgB,SAASP,EAAUQ,EAAQC,iBAAiB,GAEpD,MAAMZ,KAAEA,EAAIa,KAAEA,EAAIC,KAAEA,GAASpB,EAAQqB,UAC/BC,EAAWC,UAAeC,eAC9B,IAAIlB,EAAKmB,UAAUC,KAAIC,IAAA,IAACC,QAAEA,GAASD,EAAA,OAAKC,CAAO,MAE1CC,GAAoBN,EAAAA,QAAeC,eACxCM,EAAAA,QAAMC,cAAc,OAAQ/B,EAAQgC,gBAAgBb,KACpDc,MAAM,YACDC,GAAoBX,EAAAA,QAAeC,eACxCM,EAAAA,QAAMC,cAAc,OAAQ/B,EAAQgC,gBAAgBZ,KACpDa,MAAM,WAER,OAAOlC,EACJoC,QAAO9B,EAAC,uCAAoC,CAAAC,KAAA,IAAA,SAAAQ,OAAWQ,cACvDa,QAAQ,eAAiBN,GACzBM,QAAQ,eAAiBD,EAC9B,CAKOpC,gBAAgBE,GACrB,MAAMmB,KAAEA,EAAIC,KAAEA,EAAIgB,WAAEA,GAAepC,EAAQqB,UAE3C,MAAO,CACLF,KAAM,IAAIA,EAAKkB,WACfjB,KAAM,IAAIA,EAAKiB,WACfD,WAAY,IAAIA,GAEpB"}
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"}