@bleedingdev/modern-js-runtime 3.2.0-ultramodern.83 → 3.2.0-ultramodern.85

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.
Files changed (28) hide show
  1. package/dist/cjs/core/react/wrapper.js +9 -3
  2. package/dist/cjs/core/server/helmet.js +6 -0
  3. package/dist/cjs/core/server/stream/beforeTemplate.js +1 -12
  4. package/dist/cjs/core/server/stream/beforeTemplate.worker.js +1 -12
  5. package/dist/cjs/core/server/stream/createReadableStream.js +3 -0
  6. package/dist/cjs/core/server/string/index.js +8 -10
  7. package/dist/cjs/exports/head.js +196 -5
  8. package/dist/cjs/ssr/serverRender/renderToString/entry.js +9 -8
  9. package/dist/esm/core/react/wrapper.mjs +9 -3
  10. package/dist/esm/core/server/helmet.mjs +4 -1
  11. package/dist/esm/core/server/stream/beforeTemplate.mjs +2 -3
  12. package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -3
  13. package/dist/esm/core/server/stream/createReadableStream.mjs +3 -0
  14. package/dist/esm/core/server/string/index.mjs +9 -10
  15. package/dist/esm/exports/head.mjs +189 -4
  16. package/dist/esm/ssr/serverRender/renderToString/entry.mjs +9 -6
  17. package/dist/esm-node/core/react/wrapper.mjs +9 -3
  18. package/dist/esm-node/core/server/helmet.mjs +4 -1
  19. package/dist/esm-node/core/server/stream/beforeTemplate.mjs +2 -3
  20. package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -3
  21. package/dist/esm-node/core/server/stream/createReadableStream.mjs +3 -0
  22. package/dist/esm-node/core/server/string/index.mjs +9 -10
  23. package/dist/esm-node/exports/head.mjs +189 -4
  24. package/dist/esm-node/ssr/serverRender/renderToString/entry.mjs +9 -6
  25. package/dist/types/core/context/runtime.d.ts +4 -0
  26. package/dist/types/core/server/helmet.d.ts +5 -3
  27. package/dist/types/exports/head.d.ts +10 -3
  28. package/package.json +10 -11
@@ -1,5 +1,190 @@
1
1
  "use client";
2
- import react_helmet, { Helmet } from "react-helmet";
3
- const head = react_helmet;
4
- export default head;
5
- export { Helmet };
2
+ import react from "react";
3
+ import { Helmet, HelmetData, HelmetProvider } from "react-helmet-async";
4
+ import { InternalRuntimeContext } from "../core/context/index.mjs";
5
+ const ATTRIBUTE_NAME_MAP = {
6
+ charSet: 'charset',
7
+ className: 'class',
8
+ contentEditable: 'contenteditable',
9
+ httpEquiv: 'http-equiv',
10
+ itemProp: 'itemprop',
11
+ tabIndex: 'tabindex'
12
+ };
13
+ const escapeHtml = (value)=>String(value).replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
14
+ const toHtmlAttributeName = (name)=>ATTRIBUTE_NAME_MAP[name] ?? name;
15
+ const attributesToString = (attributes, includeHelmetAttribute = false)=>{
16
+ const pairs = [];
17
+ if (includeHelmetAttribute) pairs.push('data-rh="true"');
18
+ for (const [name, value] of Object.entries(attributes ?? {})){
19
+ if (false === value || null == value) continue;
20
+ const htmlName = toHtmlAttributeName(name);
21
+ if (true === value) pairs.push(htmlName);
22
+ else pairs.push(`${htmlName}="${escapeHtml(value)}"`);
23
+ }
24
+ return pairs.join(' ');
25
+ };
26
+ const createDatum = (tagName, tags)=>({
27
+ toComponent: ()=>[],
28
+ toString: ()=>tags.map((tag)=>{
29
+ const attrs = attributesToString(tag, true);
30
+ if ("script" === tagName && 'string' == typeof tag.innerHTML) return `<script ${attrs}>${tag.innerHTML}</script>`;
31
+ if ('style' === tagName && 'string' == typeof tag.cssText) return `<style ${attrs}>${tag.cssText}</style>`;
32
+ if ("noscript" === tagName && 'string' == typeof tag.innerHTML) return `<noscript ${attrs}>${tag.innerHTML}</noscript>`;
33
+ return `<${tagName} ${attrs}>`;
34
+ }).join('')
35
+ });
36
+ const createAttributeDatum = (attributes)=>({
37
+ toComponent: ()=>attributes,
38
+ toString: ()=>attributesToString(attributes)
39
+ });
40
+ const createTitleDatum = (title, attributes)=>({
41
+ toComponent: ()=>[],
42
+ toString: ()=>{
43
+ if (!title) return '';
44
+ const attrs = attributesToString(attributes, true);
45
+ return `<title ${attrs}>${escapeHtml(title)}</title>`;
46
+ }
47
+ });
48
+ const createEmptyHelmetState = ()=>({
49
+ base: createDatum('base', []),
50
+ bodyAttributes: createAttributeDatum({}),
51
+ htmlAttributes: createAttributeDatum({}),
52
+ link: createDatum('link', []),
53
+ meta: createDatum('meta', []),
54
+ noscript: createDatum("noscript", []),
55
+ priority: createDatum('meta', []),
56
+ script: createDatum("script", []),
57
+ style: createDatum('style', []),
58
+ title: createTitleDatum(void 0, {})
59
+ });
60
+ const mergeAttributes = (current, next)=>({
61
+ ...current,
62
+ ...next ?? {}
63
+ });
64
+ const collectChildren = (children, draft)=>{
65
+ react.Children.forEach(children, (child)=>{
66
+ if (!react.isValidElement(child)) return;
67
+ if (child.type === react.Fragment) return void collectChildren(child.props.children, draft);
68
+ if ('string' != typeof child.type) return;
69
+ const { children: nestedChildren, ...props } = child.props;
70
+ if ('title' === child.type) {
71
+ draft.title = react.Children.toArray(nestedChildren).join('');
72
+ draft.titleAttributes = mergeAttributes(draft.titleAttributes, props);
73
+ return;
74
+ }
75
+ if ('html' === child.type || 'body' === child.type) return;
76
+ if ('base' === child.type || 'link' === child.type || 'meta' === child.type || "noscript" === child.type || "script" === child.type || 'style' === child.type) {
77
+ const tag = {
78
+ ...props
79
+ };
80
+ if (("script" === child.type || 'style' === child.type || "noscript" === child.type) && 'string' == typeof nestedChildren) tag['style' === child.type ? 'cssText' : 'innerHTML'] = nestedChildren;
81
+ draft[child.type].push(tag);
82
+ }
83
+ });
84
+ };
85
+ const collectHelmetProps = (current, props)=>{
86
+ const baseState = current ?? createEmptyHelmetState();
87
+ const draft = {
88
+ base: [
89
+ ...props.base ? [
90
+ props.base
91
+ ] : []
92
+ ],
93
+ bodyAttributes: props.bodyAttributes,
94
+ htmlAttributes: props.htmlAttributes,
95
+ link: [
96
+ ...props.link ?? []
97
+ ],
98
+ meta: [
99
+ ...props.meta ?? []
100
+ ],
101
+ noscript: [
102
+ ...props.noscript ?? []
103
+ ],
104
+ script: [
105
+ ...props.script ?? []
106
+ ],
107
+ style: [
108
+ ...props.style ?? []
109
+ ],
110
+ title: 'string' == typeof props.title ? props.title : Array.isArray(props.title) ? props.title.join('') : void 0,
111
+ titleAttributes: props.titleAttributes ?? {}
112
+ };
113
+ collectChildren(props.children, draft);
114
+ const title = draft.title && props.titleTemplate ? props.titleTemplate.replaceAll('%s', draft.title) : draft.title ?? props.defaultTitle;
115
+ return {
116
+ base: createDatum('base', [
117
+ ...baseState.__baseTags ?? [],
118
+ ...draft.base
119
+ ]),
120
+ bodyAttributes: createAttributeDatum(mergeAttributes(baseState.__bodyAttributes ?? {}, draft.bodyAttributes)),
121
+ htmlAttributes: createAttributeDatum(mergeAttributes(baseState.__htmlAttributes ?? {}, draft.htmlAttributes)),
122
+ link: createDatum('link', [
123
+ ...baseState.__linkTags ?? [],
124
+ ...draft.link
125
+ ]),
126
+ meta: createDatum('meta', [
127
+ ...baseState.__metaTags ?? [],
128
+ ...draft.meta
129
+ ]),
130
+ noscript: createDatum("noscript", [
131
+ ...baseState.__noscriptTags ?? [],
132
+ ...draft.noscript
133
+ ]),
134
+ priority: createDatum('meta', []),
135
+ script: createDatum("script", [
136
+ ...baseState.__scriptTags ?? [],
137
+ ...draft.script
138
+ ]),
139
+ style: createDatum('style', [
140
+ ...baseState.__styleTags ?? [],
141
+ ...draft.style
142
+ ]),
143
+ title: createTitleDatum(title ?? baseState.__title, mergeAttributes(baseState.__titleAttributes ?? {}, draft.titleAttributes)),
144
+ __baseTags: [
145
+ ...baseState.__baseTags ?? [],
146
+ ...draft.base
147
+ ],
148
+ __bodyAttributes: mergeAttributes(baseState.__bodyAttributes ?? {}, draft.bodyAttributes),
149
+ __htmlAttributes: mergeAttributes(baseState.__htmlAttributes ?? {}, draft.htmlAttributes),
150
+ __linkTags: [
151
+ ...baseState.__linkTags ?? [],
152
+ ...draft.link
153
+ ],
154
+ __metaTags: [
155
+ ...baseState.__metaTags ?? [],
156
+ ...draft.meta
157
+ ],
158
+ __noscriptTags: [
159
+ ...baseState.__noscriptTags ?? [],
160
+ ...draft.noscript
161
+ ],
162
+ __scriptTags: [
163
+ ...baseState.__scriptTags ?? [],
164
+ ...draft.script
165
+ ],
166
+ __styleTags: [
167
+ ...baseState.__styleTags ?? [],
168
+ ...draft.style
169
+ ],
170
+ __title: title ?? baseState.__title,
171
+ __titleAttributes: mergeAttributes(baseState.__titleAttributes ?? {}, draft.titleAttributes)
172
+ };
173
+ };
174
+ const head_Helmet = (props)=>{
175
+ const runtimeContext = react.useContext(InternalRuntimeContext);
176
+ if (runtimeContext && !runtimeContext.isBrowser) {
177
+ runtimeContext._helmetContext ??= {};
178
+ runtimeContext._helmetContext.helmet = collectHelmetProps(runtimeContext._helmetContext.helmet ?? void 0, props);
179
+ return null;
180
+ }
181
+ return react.createElement(Helmet, props);
182
+ };
183
+ const head = {
184
+ Helmet: head_Helmet,
185
+ HelmetData: HelmetData,
186
+ HelmetProvider: HelmetProvider
187
+ };
188
+ const exports_head = head;
189
+ export default exports_head;
190
+ export { HelmetData, HelmetProvider, head_Helmet as Helmet };
@@ -1,10 +1,10 @@
1
1
  import { sanitizeSSRPayload, serializeJson } from "@modern-js/runtime-utils/node";
2
2
  import { time } from "@modern-js/runtime-utils/time";
3
3
  import react from "react";
4
- import react_helmet from "react-helmet";
4
+ import { HelmetProvider } from "react-helmet-async";
5
+ import { getHelmetData, helmetReplace } from "../../../core/server/helmet.mjs";
5
6
  import { serializeErrors } from "../../../router/runtime/utils.mjs";
6
7
  import prefetch from "../../prefetch";
7
- import helmet from "../helmet";
8
8
  import { SSRErrors, SSRTimings } from "../tracker";
9
9
  import { RenderLevel } from "../types.mjs";
10
10
  import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID, attributesToString } from "../utils";
@@ -60,8 +60,8 @@ class Entry {
60
60
  createReplaceSSRDataScript(ssrDataScripts),
61
61
  ...this.htmlModifiers
62
62
  ]);
63
- const helmetData = react_helmet.renderStatic();
64
- return helmetData ? helmet(html, helmetData) : html;
63
+ const helmetData = getHelmetData(context);
64
+ return helmetData ? helmetReplace(html, helmetData) : html;
65
65
  }
66
66
  async prefetch(context) {
67
67
  let prefetchData;
@@ -82,11 +82,14 @@ class Entry {
82
82
  const end = time();
83
83
  const { ssrContext } = context;
84
84
  try {
85
- const App = react.createElement(this.App, {
85
+ const helmetContext = context._helmetContext ??= {};
86
+ const App = react.createElement(HelmetProvider, {
87
+ context: helmetContext
88
+ }, react.createElement(this.App, {
86
89
  context: Object.assign(context, {
87
90
  ssr: true
88
91
  })
89
- });
92
+ }));
90
93
  html = await createRender(App).addCollector(createStyledCollector(this.result)).addCollector(createLoadableCollector({
91
94
  stats: ssrContext.loadableStats,
92
95
  result: this.result,
@@ -1,8 +1,11 @@
1
1
  import "node:module";
2
2
  import { jsx } from "react/jsx-runtime";
3
+ import { HelmetProvider } from "react-helmet-async";
3
4
  import { InternalRuntimeContext, RuntimeContext } from "../context/index.mjs";
4
5
  function wrapRuntimeContextProvider(App, contextValue) {
5
- const { isBrowser, initialData, routes, routerFramework, context, routeManifest, routerRuntime, routerInstance, routerHydrationScript, routerMatchedRouteIds, routerServerSnapshot, routerContext, unstable_getBlockNavState, ssrContext, _internalContext, _internalRouterBaseName, ...rest } = contextValue;
6
+ const { isBrowser, initialData, routes, routerFramework, context, routeManifest, routerRuntime, routerInstance, routerHydrationScript, routerMatchedRouteIds, routerServerSnapshot, routerContext, unstable_getBlockNavState, ssrContext, _internalContext, _internalRouterBaseName, _helmetContext, ...rest } = contextValue;
7
+ const internalContextValue = contextValue;
8
+ internalContextValue._helmetContext ??= {};
6
9
  const runtimeContextValue = {
7
10
  isBrowser,
8
11
  initialData,
@@ -12,10 +15,13 @@ function wrapRuntimeContextProvider(App, contextValue) {
12
15
  ...rest
13
16
  };
14
17
  return /*#__PURE__*/ jsx(InternalRuntimeContext.Provider, {
15
- value: contextValue,
18
+ value: internalContextValue,
16
19
  children: /*#__PURE__*/ jsx(RuntimeContext.Provider, {
17
20
  value: runtimeContextValue,
18
- children: App
21
+ children: /*#__PURE__*/ jsx(HelmetProvider, {
22
+ context: internalContextValue._helmetContext,
23
+ children: App
24
+ })
19
25
  })
20
26
  });
21
27
  }
@@ -6,6 +6,9 @@ const RE_BODY_ATTR = /<body[^>]*>/;
6
6
  const RE_LAST_IN_HEAD = /<\/head>/;
7
7
  const RE_TITLE = /<title[^>]*>([\s\S\n\r]*?)<\/title>/;
8
8
  const TEST_TITLE_CONTENT = /(?<=<title[^>]*>)([\s\S\n\r]*?)([.|\S])([\s\S\n\r]*?)(?=<\/title>)/;
9
+ function getHelmetData(runtimeContext) {
10
+ return runtimeContext._helmetContext?.helmet ?? void 0;
11
+ }
9
12
  function createReplaceHelemt(helmetData) {
10
13
  return helmetData ? (template)=>helmetReplace(template, helmetData) : (tempalte)=>tempalte;
11
14
  }
@@ -36,4 +39,4 @@ function helmetReplace(content, helmetData) {
36
39
  ].reduce((pre, cur)=>pre + (cur.length > 0 ? ` ${cur}${EOL}` : ''), '');
37
40
  return safeReplace(result, RE_LAST_IN_HEAD, `${helmetStr}</head>`);
38
41
  }
39
- export { createReplaceHelemt, helmetReplace };
42
+ export { createReplaceHelemt, getHelmetData, helmetReplace };
@@ -1,10 +1,9 @@
1
1
  import "node:module";
2
2
  import { matchRoutes } from "@modern-js/runtime-utils/router";
3
- import react_helmet from "react-helmet";
4
3
  import { getRouterMatchedRouteIds } from "../../../router/runtime/lifecycle.mjs";
5
4
  import { CHUNK_CSS_PLACEHOLDER } from "../constants.mjs";
6
5
  import { createFederatedCssLinks } from "../federatedCss.mjs";
7
- import { createReplaceHelemt } from "../helmet.mjs";
6
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
8
7
  import { buildHtml } from "../shared.mjs";
9
8
  import { checkIsNode, safeReplace } from "../utils.mjs";
10
9
  import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
@@ -23,7 +22,7 @@ const checkIsInline = (chunk, enableInline)=>{
23
22
  };
24
23
  async function buildShellBeforeTemplate(beforeAppTemplate, options) {
25
24
  const { config, runtimeContext, styledComponentsStyleTags, entryName, moduleFederationCssAssets } = options;
26
- const helmetData = react_helmet.renderStatic();
25
+ const helmetData = getHelmetData(runtimeContext);
27
26
  const callbacks = [
28
27
  createReplaceHelemt(helmetData),
29
28
  (template)=>injectCss(template, entryName, styledComponentsStyleTags)
@@ -1,10 +1,9 @@
1
1
  import "node:module";
2
2
  import { matchRoutes } from "@modern-js/runtime-utils/router";
3
- import react_helmet from "react-helmet";
4
3
  import { getRouterMatchedRouteIds } from "../../../router/runtime/lifecycle.mjs";
5
4
  import { CHUNK_CSS_PLACEHOLDER } from "../constants.mjs";
6
5
  import { createFederatedCssLinks } from "../federatedCss.mjs";
7
- import { createReplaceHelemt } from "../helmet.mjs";
6
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
8
7
  import { buildHtml } from "../shared.mjs";
9
8
  import { safeReplace } from "../utils.mjs";
10
9
  const checkIsInline = (chunk, enableInline)=>{
@@ -14,7 +13,7 @@ const checkIsInline = (chunk, enableInline)=>{
14
13
  };
15
14
  async function buildShellBeforeTemplate(beforeAppTemplate, options) {
16
15
  const { config, runtimeContext, styledComponentsStyleTags, entryName, moduleFederationCssAssets } = options;
17
- const helmetData = react_helmet.renderStatic();
16
+ const helmetData = getHelmetData(runtimeContext);
18
17
  const callbacks = [
19
18
  createReplaceHelemt(helmetData),
20
19
  (template)=>injectCss(template, entryName, styledComponentsStyleTags)
@@ -36,11 +36,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
36
36
  if (extender.modifyRootElement) processedRootElement = extender.modifyRootElement(processedRootElement);
37
37
  });
38
38
  const chunkVec = [];
39
+ let hasStartedPipe = false;
39
40
  return new Promise((resolve)=>{
40
41
  const { pipe: reactStreamingPipe } = renderToPipeableStream(processedRootElement, {
41
42
  nonce: config.nonce,
42
43
  identifierPrefix: SSR_HYDRATION_ID_PREFIX,
43
44
  [onReady] () {
45
+ if (hasStartedPipe) return;
46
+ hasStartedPipe = true;
44
47
  let styledComponentsStyleTags = '';
45
48
  extenders.forEach((extender)=>{
46
49
  if (extender.getStyleTags) styledComponentsStyleTags += extender.getStyleTags();
@@ -2,24 +2,23 @@ import "node:module";
2
2
  import { time } from "@modern-js/runtime-utils/time";
3
3
  import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
4
4
  import server from "react-dom/server";
5
- import react_helmet from "react-helmet";
6
5
  import { RenderLevel } from "../../constants.mjs";
7
6
  import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
8
7
  import { wrapRuntimeContextProvider } from "../../react/wrapper.mjs";
9
8
  import { CHUNK_CSS_PLACEHOLDER, CHUNK_JS_PLACEHOLDER, HTML_PLACEHOLDER, SSR_DATA_PLACEHOLDER } from "../constants.mjs";
10
- import { createReplaceHelemt } from "../helmet.mjs";
9
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
11
10
  import { buildHtml } from "../shared.mjs";
12
11
  import { SSRErrors, SSRTimings } from "../tracer.mjs";
13
12
  import { getSSRConfigByEntry, safeReplace } from "../utils.mjs";
14
13
  import { LoadableCollector } from "./loadable.mjs";
15
14
  import { SSRDataCollector } from "./ssrData.mjs";
16
15
  const renderString = async (request, serverRoot, options)=>{
17
- const { resource, runtimeContext, config, onError, onTiming } = options;
16
+ const { resource, runtimeContext: runtimeContext1, config, onError, onTiming } = options;
18
17
  const tracer = {
19
18
  onError,
20
19
  onTiming
21
20
  };
22
- const routerContext = runtimeContext.routerContext;
21
+ const routerContext = runtimeContext1.routerContext;
23
22
  const { htmlTemplate, entryName, loadableStats, routeManifest, moduleFederationCssAssets } = resource;
24
23
  const ssrConfig = getSSRConfigByEntry(entryName, config.ssr, config.ssrByEntries);
25
24
  const chunkSet = {
@@ -33,7 +32,7 @@ const renderString = async (request, serverRoot, options)=>{
33
32
  stats: loadableStats,
34
33
  nonce: config.nonce,
35
34
  routeManifest,
36
- runtimeContext,
35
+ runtimeContext: runtimeContext1,
37
36
  template: htmlTemplate,
38
37
  entryName,
39
38
  moduleFederationCssAssets,
@@ -41,10 +40,10 @@ const renderString = async (request, serverRoot, options)=>{
41
40
  config
42
41
  }),
43
42
  new SSRDataCollector({
44
- runtimeContext,
43
+ runtimeContext: runtimeContext1,
45
44
  request,
46
45
  ssrConfig,
47
- ssrContext: runtimeContext.ssrContext,
46
+ ssrContext: runtimeContext1.ssrContext,
48
47
  chunkSet,
49
48
  routerContext,
50
49
  nonce: config.nonce,
@@ -57,10 +56,10 @@ const renderString = async (request, serverRoot, options)=>{
57
56
  chunkSet
58
57
  });
59
58
  for (const c of extraCollectors)if (c) collectors.unshift(c);
60
- const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext, {
59
+ const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext1, {
61
60
  ssr: true
62
61
  }));
63
- const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], tracer);
62
+ const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext1.ssrContext?.htmlModifiers || [], tracer);
64
63
  return html;
65
64
  };
66
65
  async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, { onError, onTiming }) {
@@ -73,7 +72,7 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
73
72
  identifierPrefix: SSR_HYDRATION_ID_PREFIX
74
73
  });
75
74
  chunkSet.renderLevel = RenderLevel.SERVER_RENDER;
76
- helmetData = react_helmet.renderStatic();
75
+ helmetData = getHelmetData(runtimeContext);
77
76
  const cost = end();
78
77
  onTiming(SSRTimings.RENDER_HTML, cost);
79
78
  } catch (e) {
@@ -1,6 +1,191 @@
1
1
  import "node:module";
2
2
  "use client";
3
- import react_helmet, { Helmet } from "react-helmet";
4
- const head = react_helmet;
5
- export default head;
6
- export { Helmet };
3
+ import react from "react";
4
+ import { Helmet, HelmetData, HelmetProvider } from "react-helmet-async";
5
+ import { InternalRuntimeContext } from "../core/context/index.mjs";
6
+ const ATTRIBUTE_NAME_MAP = {
7
+ charSet: 'charset',
8
+ className: 'class',
9
+ contentEditable: 'contenteditable',
10
+ httpEquiv: 'http-equiv',
11
+ itemProp: 'itemprop',
12
+ tabIndex: 'tabindex'
13
+ };
14
+ const escapeHtml = (value)=>String(value).replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
15
+ const toHtmlAttributeName = (name)=>ATTRIBUTE_NAME_MAP[name] ?? name;
16
+ const attributesToString = (attributes, includeHelmetAttribute = false)=>{
17
+ const pairs = [];
18
+ if (includeHelmetAttribute) pairs.push('data-rh="true"');
19
+ for (const [name, value] of Object.entries(attributes ?? {})){
20
+ if (false === value || null == value) continue;
21
+ const htmlName = toHtmlAttributeName(name);
22
+ if (true === value) pairs.push(htmlName);
23
+ else pairs.push(`${htmlName}="${escapeHtml(value)}"`);
24
+ }
25
+ return pairs.join(' ');
26
+ };
27
+ const createDatum = (tagName, tags)=>({
28
+ toComponent: ()=>[],
29
+ toString: ()=>tags.map((tag)=>{
30
+ const attrs = attributesToString(tag, true);
31
+ if ("script" === tagName && 'string' == typeof tag.innerHTML) return `<script ${attrs}>${tag.innerHTML}</script>`;
32
+ if ('style' === tagName && 'string' == typeof tag.cssText) return `<style ${attrs}>${tag.cssText}</style>`;
33
+ if ("noscript" === tagName && 'string' == typeof tag.innerHTML) return `<noscript ${attrs}>${tag.innerHTML}</noscript>`;
34
+ return `<${tagName} ${attrs}>`;
35
+ }).join('')
36
+ });
37
+ const createAttributeDatum = (attributes)=>({
38
+ toComponent: ()=>attributes,
39
+ toString: ()=>attributesToString(attributes)
40
+ });
41
+ const createTitleDatum = (title, attributes)=>({
42
+ toComponent: ()=>[],
43
+ toString: ()=>{
44
+ if (!title) return '';
45
+ const attrs = attributesToString(attributes, true);
46
+ return `<title ${attrs}>${escapeHtml(title)}</title>`;
47
+ }
48
+ });
49
+ const createEmptyHelmetState = ()=>({
50
+ base: createDatum('base', []),
51
+ bodyAttributes: createAttributeDatum({}),
52
+ htmlAttributes: createAttributeDatum({}),
53
+ link: createDatum('link', []),
54
+ meta: createDatum('meta', []),
55
+ noscript: createDatum("noscript", []),
56
+ priority: createDatum('meta', []),
57
+ script: createDatum("script", []),
58
+ style: createDatum('style', []),
59
+ title: createTitleDatum(void 0, {})
60
+ });
61
+ const mergeAttributes = (current, next)=>({
62
+ ...current,
63
+ ...next ?? {}
64
+ });
65
+ const collectChildren = (children, draft)=>{
66
+ react.Children.forEach(children, (child)=>{
67
+ if (!react.isValidElement(child)) return;
68
+ if (child.type === react.Fragment) return void collectChildren(child.props.children, draft);
69
+ if ('string' != typeof child.type) return;
70
+ const { children: nestedChildren, ...props } = child.props;
71
+ if ('title' === child.type) {
72
+ draft.title = react.Children.toArray(nestedChildren).join('');
73
+ draft.titleAttributes = mergeAttributes(draft.titleAttributes, props);
74
+ return;
75
+ }
76
+ if ('html' === child.type || 'body' === child.type) return;
77
+ if ('base' === child.type || 'link' === child.type || 'meta' === child.type || "noscript" === child.type || "script" === child.type || 'style' === child.type) {
78
+ const tag = {
79
+ ...props
80
+ };
81
+ if (("script" === child.type || 'style' === child.type || "noscript" === child.type) && 'string' == typeof nestedChildren) tag['style' === child.type ? 'cssText' : 'innerHTML'] = nestedChildren;
82
+ draft[child.type].push(tag);
83
+ }
84
+ });
85
+ };
86
+ const collectHelmetProps = (current, props)=>{
87
+ const baseState = current ?? createEmptyHelmetState();
88
+ const draft = {
89
+ base: [
90
+ ...props.base ? [
91
+ props.base
92
+ ] : []
93
+ ],
94
+ bodyAttributes: props.bodyAttributes,
95
+ htmlAttributes: props.htmlAttributes,
96
+ link: [
97
+ ...props.link ?? []
98
+ ],
99
+ meta: [
100
+ ...props.meta ?? []
101
+ ],
102
+ noscript: [
103
+ ...props.noscript ?? []
104
+ ],
105
+ script: [
106
+ ...props.script ?? []
107
+ ],
108
+ style: [
109
+ ...props.style ?? []
110
+ ],
111
+ title: 'string' == typeof props.title ? props.title : Array.isArray(props.title) ? props.title.join('') : void 0,
112
+ titleAttributes: props.titleAttributes ?? {}
113
+ };
114
+ collectChildren(props.children, draft);
115
+ const title = draft.title && props.titleTemplate ? props.titleTemplate.replaceAll('%s', draft.title) : draft.title ?? props.defaultTitle;
116
+ return {
117
+ base: createDatum('base', [
118
+ ...baseState.__baseTags ?? [],
119
+ ...draft.base
120
+ ]),
121
+ bodyAttributes: createAttributeDatum(mergeAttributes(baseState.__bodyAttributes ?? {}, draft.bodyAttributes)),
122
+ htmlAttributes: createAttributeDatum(mergeAttributes(baseState.__htmlAttributes ?? {}, draft.htmlAttributes)),
123
+ link: createDatum('link', [
124
+ ...baseState.__linkTags ?? [],
125
+ ...draft.link
126
+ ]),
127
+ meta: createDatum('meta', [
128
+ ...baseState.__metaTags ?? [],
129
+ ...draft.meta
130
+ ]),
131
+ noscript: createDatum("noscript", [
132
+ ...baseState.__noscriptTags ?? [],
133
+ ...draft.noscript
134
+ ]),
135
+ priority: createDatum('meta', []),
136
+ script: createDatum("script", [
137
+ ...baseState.__scriptTags ?? [],
138
+ ...draft.script
139
+ ]),
140
+ style: createDatum('style', [
141
+ ...baseState.__styleTags ?? [],
142
+ ...draft.style
143
+ ]),
144
+ title: createTitleDatum(title ?? baseState.__title, mergeAttributes(baseState.__titleAttributes ?? {}, draft.titleAttributes)),
145
+ __baseTags: [
146
+ ...baseState.__baseTags ?? [],
147
+ ...draft.base
148
+ ],
149
+ __bodyAttributes: mergeAttributes(baseState.__bodyAttributes ?? {}, draft.bodyAttributes),
150
+ __htmlAttributes: mergeAttributes(baseState.__htmlAttributes ?? {}, draft.htmlAttributes),
151
+ __linkTags: [
152
+ ...baseState.__linkTags ?? [],
153
+ ...draft.link
154
+ ],
155
+ __metaTags: [
156
+ ...baseState.__metaTags ?? [],
157
+ ...draft.meta
158
+ ],
159
+ __noscriptTags: [
160
+ ...baseState.__noscriptTags ?? [],
161
+ ...draft.noscript
162
+ ],
163
+ __scriptTags: [
164
+ ...baseState.__scriptTags ?? [],
165
+ ...draft.script
166
+ ],
167
+ __styleTags: [
168
+ ...baseState.__styleTags ?? [],
169
+ ...draft.style
170
+ ],
171
+ __title: title ?? baseState.__title,
172
+ __titleAttributes: mergeAttributes(baseState.__titleAttributes ?? {}, draft.titleAttributes)
173
+ };
174
+ };
175
+ const head_Helmet = (props)=>{
176
+ const runtimeContext = react.useContext(InternalRuntimeContext);
177
+ if (runtimeContext && !runtimeContext.isBrowser) {
178
+ runtimeContext._helmetContext ??= {};
179
+ runtimeContext._helmetContext.helmet = collectHelmetProps(runtimeContext._helmetContext.helmet ?? void 0, props);
180
+ return null;
181
+ }
182
+ return react.createElement(Helmet, props);
183
+ };
184
+ const head = {
185
+ Helmet: head_Helmet,
186
+ HelmetData: HelmetData,
187
+ HelmetProvider: HelmetProvider
188
+ };
189
+ const exports_head = head;
190
+ export default exports_head;
191
+ export { HelmetData, HelmetProvider, head_Helmet as Helmet };
@@ -2,10 +2,10 @@ import "node:module";
2
2
  import { sanitizeSSRPayload, serializeJson } from "@modern-js/runtime-utils/node";
3
3
  import { time } from "@modern-js/runtime-utils/time";
4
4
  import react from "react";
5
- import react_helmet from "react-helmet";
5
+ import { HelmetProvider } from "react-helmet-async";
6
+ import { getHelmetData, helmetReplace } from "../../../core/server/helmet.mjs";
6
7
  import { serializeErrors } from "../../../router/runtime/utils.mjs";
7
8
  import prefetch from "../../prefetch";
8
- import helmet from "../helmet";
9
9
  import { SSRErrors, SSRTimings } from "../tracker";
10
10
  import { RenderLevel } from "../types.mjs";
11
11
  import { ROUTER_DATA_JSON_ID, SSR_DATA_JSON_ID, attributesToString } from "../utils";
@@ -61,8 +61,8 @@ class Entry {
61
61
  createReplaceSSRDataScript(ssrDataScripts),
62
62
  ...this.htmlModifiers
63
63
  ]);
64
- const helmetData = react_helmet.renderStatic();
65
- return helmetData ? helmet(html, helmetData) : html;
64
+ const helmetData = getHelmetData(context);
65
+ return helmetData ? helmetReplace(html, helmetData) : html;
66
66
  }
67
67
  async prefetch(context) {
68
68
  let prefetchData;
@@ -83,11 +83,14 @@ class Entry {
83
83
  const end = time();
84
84
  const { ssrContext } = context;
85
85
  try {
86
- const App = react.createElement(this.App, {
86
+ const helmetContext = context._helmetContext ??= {};
87
+ const App = react.createElement(HelmetProvider, {
88
+ context: helmetContext
89
+ }, react.createElement(this.App, {
87
90
  context: Object.assign(context, {
88
91
  ssr: true
89
92
  })
90
- });
93
+ }));
91
94
  html = await createRender(App).addCollector(createStyledCollector(this.result)).addCollector(createLoadableCollector({
92
95
  stats: ssrContext.loadableStats,
93
96
  result: this.result,
@@ -1,4 +1,5 @@
1
1
  import type { RouteObject, StaticHandlerContext } from '@modern-js/runtime-utils/router';
2
+ import type { HelmetServerState } from 'react-helmet-async';
2
3
  import type { InternalRouterRuntimeState, InternalRouterServerSnapshot, RouteManifest, RouterFramework } from '../../router/runtime/types';
3
4
  import type { RequestContext, SSRServerContext } from '../types';
4
5
  export interface TRuntimeContext {
@@ -28,6 +29,9 @@ export interface TInternalRuntimeContext extends TRuntimeContext {
28
29
  ssrContext?: SSRServerContext;
29
30
  _internalContext?: any;
30
31
  _internalRouterBaseName?: any;
32
+ _helmetContext?: {
33
+ helmet?: HelmetServerState | null;
34
+ };
31
35
  }
32
36
  export declare const InternalRuntimeContext: import("react").Context<TInternalRuntimeContext>;
33
37
  export declare const RuntimeContext: import("react").Context<TRuntimeContext>;