@lomray/react-head-manager 1.0.0

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/context.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ import Manager from "./manager.js";
3
+ interface IMetaManager {
4
+ manager: Manager;
5
+ }
6
+ /**
7
+ * Global application meta manager provider
8
+ * @constructor
9
+ */
10
+ declare const MetaManagerProvider: FC<PropsWithChildren<IMetaManager>>;
11
+ declare const useMetaManager: () => IMetaManager;
12
+ /**
13
+ * Meta tag component
14
+ * @constructor
15
+ */
16
+ declare const Meta: FC<PropsWithChildren>;
17
+ export { Meta, MetaManagerProvider, useMetaManager };
package/context.js ADDED
@@ -0,0 +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;
2
+ //# sourceMappingURL=context.js.map
package/context.js.map ADDED
@@ -0,0 +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"}
package/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./context.js";
2
+ export { default as Manager } from "./manager.js";
package/index.js ADDED
@@ -0,0 +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;
2
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/manager.d.ts ADDED
@@ -0,0 +1,158 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ declare enum TagStatus {
3
+ init = "init",
4
+ synced = "synced",
5
+ drain = "drain"
6
+ }
7
+ interface IMetaManagerTags {
8
+ html: Map<string, Record<string, any>>;
9
+ body: Map<string, Record<string, any>>;
10
+ meta: Map<string, {
11
+ element?: ReactElement;
12
+ domElement?: HTMLElement;
13
+ order: number;
14
+ containerId: string;
15
+ status: TagStatus;
16
+ }>;
17
+ containers: Set<string>;
18
+ }
19
+ interface IMetaManagerTagsDefinitions {
20
+ [type: string]: {
21
+ key?: string;
22
+ order: number;
23
+ };
24
+ }
25
+ /**
26
+ * Meta tags manager
27
+ */
28
+ declare class Manager {
29
+ /**
30
+ * Root container id
31
+ */
32
+ static rootContainerId: string;
33
+ /**
34
+ * Detect server side
35
+ */
36
+ isServer: boolean;
37
+ /**
38
+ * Meta tags state
39
+ */
40
+ protected tags: IMetaManagerTags;
41
+ /**
42
+ * Tags definitions
43
+ */
44
+ protected tagsDefinitions: IMetaManagerTagsDefinitions;
45
+ /**
46
+ * @constructor
47
+ */
48
+ /**
49
+ * @constructor
50
+ */
51
+ constructor(tags?: Partial<Manager['tags']>);
52
+ /**
53
+ * Set tags definitions
54
+ */
55
+ /**
56
+ * Set tags definitions
57
+ */
58
+ setTagsDefinitions(definitions: IMetaManagerTagsDefinitions): void;
59
+ /**
60
+ * Remove children, dangerouslySetInnerHTML etc.
61
+ */
62
+ /**
63
+ * Remove children, dangerouslySetInnerHTML etc.
64
+ */
65
+ static cleanupElementProps(props: Record<string, any>): Record<string, any>;
66
+ /**
67
+ * Get meta tags
68
+ */
69
+ /**
70
+ * Get meta tags
71
+ */
72
+ getTags(): IMetaManagerTags;
73
+ /**
74
+ * Replace react attribute to valid DOM attribute
75
+ */
76
+ /**
77
+ * Replace react attribute to valid DOM attribute
78
+ */
79
+ protected replaceAttribute(tagName: string, attribute: string): string;
80
+ /**
81
+ * Create DOM element from React element
82
+ */
83
+ /**
84
+ * Create DOM element from React element
85
+ */
86
+ protected createDomElement(element: ReactElement): HTMLElement | undefined;
87
+ /**
88
+ * Make element key by element props
89
+ */
90
+ /**
91
+ * Make element key by element props
92
+ */
93
+ protected buildKeyByProps(type: string, props?: Record<string, any>, isSkipProps?: boolean): string | undefined;
94
+ /**
95
+ * Get default tag key for not unique elements
96
+ */
97
+ /**
98
+ * Get default tag key for not unique elements
99
+ */
100
+ protected getDefaultKey(type: string, containerId: string, index: number): string;
101
+ /**
102
+ * Check if tag is not unique
103
+ */
104
+ /**
105
+ * Check if tag is not unique
106
+ */
107
+ protected isNotUniqueTag(key: string): boolean;
108
+ /**
109
+ * Push react elements to meta state
110
+ */
111
+ /**
112
+ * Push react elements to meta state
113
+ */
114
+ protected pushElements(elements: ReactNode, containerId: string, isReplace?: boolean, status?: TagStatus): void;
115
+ /**
116
+ * Get html or body props
117
+ */
118
+ /**
119
+ * Get html or body props
120
+ */
121
+ getRootTagProps(props: Manager['tags']['html']): Record<string, any>;
122
+ /**
123
+ * Apply props to dom element
124
+ */
125
+ /**
126
+ * Apply props to dom element
127
+ */
128
+ protected applyDomElementAttributes(element: Element, props?: Record<string, any>): void;
129
+ /**
130
+ * Synchronize local meta tags with head meta tags
131
+ */
132
+ /**
133
+ * Synchronize local meta tags with head meta tags
134
+ */
135
+ protected syncMeta(containerId?: string): void;
136
+ /**
137
+ * Push new meta tags
138
+ */
139
+ /**
140
+ * Push new meta tags
141
+ */
142
+ pushTags(elements: ReactNode, containerId: string, isReplace?: boolean): void;
143
+ /**
144
+ * Remove meta tags
145
+ */
146
+ /**
147
+ * Remove meta tags
148
+ */
149
+ removeTags(containerId: string): void;
150
+ /**
151
+ * Initial analyze client head meta tags
152
+ */
153
+ /**
154
+ * Initial analyze client head meta tags
155
+ */
156
+ analyzeClientHead(): void;
157
+ }
158
+ export { Manager as default, IMetaManagerTags, IMetaManagerTagsDefinitions };
package/manager.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var e=require("@babel/runtime/helpers/defineProperty"),t=require("html-react-parser"),n=require("react");function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o,r=i(e),s=i(t),a=i(n);!function(e){e.init="init",e.synced="synced",e.drain="drain"}(o||(o={}));class l{constructor(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};r.default(this,"isServer","undefined"==typeof window),r.default(this,"tags",void 0),r.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}}),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:n,...i}=e;return i}getTags(){const{meta:e,body:t,html:n,containers:i}=this.tags;return{body:t,html:n,meta:new Map([...e.entries()].sort(((e,t)=>{let[,n]=e,[,i]=t;return n.order-i.order}))),containers:i}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:n}=e,i=document.createElement(t);return this.applyDomElementAttributes(i,n),i}buildKeyByProps(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],i="";for(const e of["id","name","href","src"])if(t[e]){i="[".concat(e,"='").concat(t[e],"']");break}if(i||n||(i=Object.entries(t).map((e=>{let[t,n]=e;return"string"==typeof n&&"[".concat(t,"]")})).filter(Boolean).join("")),i)return"".concat(e).concat(i)}getDefaultKey(e,t,n){return"".concat(e,"-").concat(t,"-").concat(n,"-not-unique")}isNotUniqueTag(e){return e.endsWith("-not-unique")}pushElements(e,t){let i=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:o.init;n.Children.forEach(e,((e,n)=>{var s,d,u,c,h,m,v;if(!e||"object"!=typeof e||!("type"in e)||!e.type)return;const{type:g,props:f}=e,p=null!=f?f:{};let y=null!==(s=null===(d=this.tagsDefinitions[g])||void 0===d?void 0:d.order)&&void 0!==s?s:1e3,b=null!==(u=null===(c=this.tagsDefinitions[g])||void 0===c?void 0:c.key)&&void 0!==u?u:g,E=e;switch(g){case"title":const{children:o}=p;E=Array.isArray(o)?a.default.cloneElement(e,{children:o.join("")}):e;break;case"meta":const{charSet:r,httpEquiv:s}=p;b=r?"meta[charset]":s?"meta[httpEquiv]":this.buildKeyByProps(g,p);break;case"html":case"body":if(!i&&this.tags[g].has(b))return;return void this.tags[g].set(t,l.cleanupElementProps(p));default:b=null!==(h=this.buildKeyByProps(g,p,!0))&&void 0!==h?h:this.getDefaultKey(g,t,n)}if((i||!this.tags.meta.has(b))&&(!this.isNotUniqueTag(b)||(null===(m=this.tags.meta.get(b))||void 0===m?void 0:m.status)!==o.synced)){var T;if(null!==(v=this.tagsDefinitions[b])&&void 0!==v&&v.order)y=null===(T=this.tagsDefinitions[b])||void 0===T?void 0:T.order;this.tags.meta.set(b,{element:this.isServer?E:void 0,domElement:t===l.rootContainerId?void 0:this.createDomElement(E),order:y,containerId:t,status:r})}})),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 n=e.tagName.toLowerCase();Object.entries(t).forEach((t=>{let[i,o]=t;if("children"!==i)return"style"===i?Object.entries(o).forEach((t=>{let[n,i]=t;e.style[n]=i})):void e.setAttribute(this.replaceAttribute(n,i),o);e.innerHTML=o}))}syncMeta(e){var t;const{meta:n,html:i,body:r}=this.getTags();for(const{name:e,value:t}of[{name:"html",value:i},{name:"body",value:r}]){var s;const n=this.getRootTagProps(t),i=document.querySelector(e);(null!==(s=null==i?void 0:i.getAttributeNames())&&void 0!==s?s:[]).forEach((e=>{n[e]||i.removeAttribute(e)})),Object.keys(n).length&&i&&this.applyDomElementAttributes(i,n)}if(!n.size||this.isServer)return;const a=null===(t=document.getElementsByTagName("head"))||void 0===t?void 0:t[0];let l;n.forEach(((t,i)=>{const{status:r,domElement:s}=t;if(r===o.synced||!s||e&&e!==t.containerId)return void(l=s);if(r===o.drain)return l=s.previousSibling,null==s||s.remove(),void n.delete(i);if(t.status=o.synced,!this.isNotUniqueTag(i)){const e=document.querySelector(i);if(e)return l=s,void e.replaceWith(s)}const d=l&&l.nextSibling;d?a.insertBefore(s,d):a.append(s),l=s}))}pushTags(e,t){let n=!(arguments.length>2&&void 0!==arguments[2])||arguments[2];const i=this.tags.containers.has(t);this.pushElements(e,t,n,i?o.synced:o.init),this.isServer||i||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,n)=>{if(t.containerId===e&&t.containerId!==l.rootContainerId){var i;if(this.isNotUniqueTag(n))return null===(i=t.domElement)||void 0===i||i.remove(),void this.tags.meta.delete(n);t.status=o.drain}})),e!==l.rootContainerId&&(this.tags.body.delete(e),this.tags.html.delete(e),this.tags.containers.delete(e))}analyzeClientHead(){var e,t;if(this.isServer)return;for(const e of["html","body"]){var n,i;const t=null===(n=document.getElementsByTagName(e))||void 0===n||null===(i=n[0].cloneNode(!1))||void 0===i?void 0:i.outerHTML;this.pushElements(s.default(t),l.rootContainerId,!1)}const r=null===(e=document.getElementsByTagName("head"))||void 0===e?void 0:e[0],a=s.default(null!==(t=null==r?void 0:r.innerHTML)&&void 0!==t?t:"");this.pushElements(a,l.rootContainerId,!1,o.synced);const d=[...this.tags.meta.values()];r.childNodes.forEach(((e,t)=>{const n=d[t];n&&(n.domElement=e)}))}}r.default(l,"rootContainerId","root"),module.exports=l;
2
+ //# sourceMappingURL=manager.js.map
package/manager.js.map ADDED
@@ -0,0 +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 * @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 sortedMeta = new Map(\n [...meta.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n );\n\n return {\n body,\n html,\n meta: sortedMeta,\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 * 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, props } = child;\n const elementProps: Record<string, any> = props ?? {};\n\n let order = this.tagsDefinitions[type as string]?.order ?? 1000;\n let key = this.tagsDefinitions[type as string]?.key ?? (type as string);\n let element = child;\n\n switch (type) {\n case 'title':\n const { children } = elementProps;\n\n // fix multiple nodes\n element = Array.isArray(children)\n ? React.cloneElement(child, { children: children.join('') })\n : child;\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\n // apply attributes\n Object.entries(props).forEach(([name, value]) => {\n if (name === 'children') {\n element.innerHTML = value;\n\n return;\n }\n\n if (name === 'style') {\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n element['style'][styleName] = styleValue;\n },\n );\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 // 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 }\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","pushElements","elements","isReplace","status","init","Children","forEach","child","_this$tagsDefinitions","_this$tagsDefinitions2","_this$tagsDefinitions3","_this$tagsDefinitions4","_this$buildKeyByProps","_this$tags$meta$get","_this$tagsDefinitions5","elementProps","Array","isArray","React","cloneElement","charSet","httpEquiv","has","set","cleanupElementProps","get","synced","_this$tagsDefinitions6","rootContainerId","add","getRootTagProps","size","values","reduce","res","val","_ref4","name","value","_ref5","styleName","styleValue","setAttribute","innerHTML","syncMeta","_document$getElements","_domElement$getAttrib","tagProps","querySelector","getAttributeNames","attrName","removeAttribute","keys","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","pushTags","isAdded","removeTags","_metaTag$domElement","analyzeClientHead","_document$getElements4","_head$innerHTML","_document$getElements2","_document$getElements3","htmlTag","cloneNode","htmlParser","reactElements","childNodes","node","i","existedElem"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@lomray/react-head-manager",
3
+ "version": "1.0.0",
4
+ "description": "React meta tags manager with SSR and Suspense support.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "keywords": [
8
+ "js",
9
+ "meta",
10
+ "tags",
11
+ "react",
12
+ "ssr",
13
+ "suspense",
14
+ "head",
15
+ "manager",
16
+ "title"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/Lomray-Software/react-head-manager.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/Lomray-Software/react-head-manager/issues"
27
+ },
28
+ "homepage": "https://github.com/Lomray-Software/react-head-manager",
29
+ "author": "Mikhail Yarmaliuk",
30
+ "license": "MIT",
31
+ "scripts": {
32
+ "build": "rollup -c",
33
+ "build:watch": "rollup -c -w",
34
+ "release": "npm run build && cd lib && npm publish",
35
+ "lint:check": "eslint --ext \".ts,.tsx\" \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
36
+ "lint:format": "eslint --fix --ext \".ts,.tsx\" \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
37
+ "ts:check": "tsc --project ./tsconfig.json --skipLibCheck --noemit"
38
+ },
39
+ "dependencies": {
40
+ "@babel/runtime": "^7.19.0",
41
+ "@lomray/consistent-suspense": "^1.2.2",
42
+ "html-react-parser": "^4.0.0"
43
+ },
44
+ "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",
59
+ "@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",
65
+ "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",
70
+ "react": "^18.2.0",
71
+ "rollup": "2.79.1",
72
+ "rollup-plugin-copy": "^3.4.0",
73
+ "rollup-plugin-folder-input": "^1.0.1",
74
+ "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"
79
+ },
80
+ "peerDependencies": {
81
+ "react": ">=17.0.2",
82
+ "react-dom": ">=17.0.2",
83
+ "tslib": "^2.5.2"
84
+ }
85
+ }
@@ -0,0 +1,30 @@
1
+ import Manager from "../manager.js";
2
+ interface IMetaManagerState {
3
+ html: [string, Record<string, any>][];
4
+ body: [string, Record<string, any>][];
5
+ containers: string[];
6
+ }
7
+ /**
8
+ * Helpers for server side
9
+ */
10
+ declare class ServerManager {
11
+ /**
12
+ * Parse existing head tags from html and merge with tags received at the render stage (from manager)
13
+ *
14
+ * @return new html with actual meta tags
15
+ */
16
+ /**
17
+ * Parse existing head tags from html and merge with tags received at the render stage (from manager)
18
+ *
19
+ * @return new html with actual meta tags
20
+ */
21
+ static inject(htmlStr: string, manager: Manager): string;
22
+ /**
23
+ * Get meta manager state to pass on client
24
+ */
25
+ /**
26
+ * Get meta manager state to pass on client
27
+ */
28
+ static getState(manager: Manager): IMetaManagerState;
29
+ }
30
+ export { ServerManager as default };
@@ -0,0 +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]}}};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}