@bleedingdev/modern-js-runtime 3.2.0-ultramodern.82 → 3.2.0-ultramodern.84

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 (34) hide show
  1. package/dist/cjs/cli/ssr/index.js +3 -14
  2. package/dist/cjs/core/react/wrapper.js +9 -3
  3. package/dist/cjs/core/server/helmet.js +6 -0
  4. package/dist/cjs/core/server/stream/beforeTemplate.js +1 -12
  5. package/dist/cjs/core/server/stream/beforeTemplate.worker.js +1 -12
  6. package/dist/cjs/core/server/stream/createReadableStream.js +3 -0
  7. package/dist/cjs/core/server/string/index.js +8 -10
  8. package/dist/cjs/exports/head.js +196 -5
  9. package/dist/cjs/router/runtime/tanstack/routeTree.js +10 -3
  10. package/dist/cjs/ssr/serverRender/renderToString/entry.js +9 -8
  11. package/dist/esm/cli/ssr/index.mjs +3 -14
  12. package/dist/esm/core/react/wrapper.mjs +9 -3
  13. package/dist/esm/core/server/helmet.mjs +4 -1
  14. package/dist/esm/core/server/stream/beforeTemplate.mjs +2 -3
  15. package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -3
  16. package/dist/esm/core/server/stream/createReadableStream.mjs +3 -0
  17. package/dist/esm/core/server/string/index.mjs +9 -10
  18. package/dist/esm/exports/head.mjs +189 -4
  19. package/dist/esm/router/runtime/tanstack/routeTree.mjs +10 -3
  20. package/dist/esm/ssr/serverRender/renderToString/entry.mjs +9 -6
  21. package/dist/esm-node/cli/ssr/index.mjs +3 -14
  22. package/dist/esm-node/core/react/wrapper.mjs +9 -3
  23. package/dist/esm-node/core/server/helmet.mjs +4 -1
  24. package/dist/esm-node/core/server/stream/beforeTemplate.mjs +2 -3
  25. package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -3
  26. package/dist/esm-node/core/server/stream/createReadableStream.mjs +3 -0
  27. package/dist/esm-node/core/server/string/index.mjs +9 -10
  28. package/dist/esm-node/exports/head.mjs +189 -4
  29. package/dist/esm-node/router/runtime/tanstack/routeTree.mjs +10 -3
  30. package/dist/esm-node/ssr/serverRender/renderToString/entry.mjs +9 -6
  31. package/dist/types/core/context/runtime.d.ts +4 -0
  32. package/dist/types/core/server/helmet.d.ts +5 -3
  33. package/dist/types/exports/head.d.ts +10 -3
  34. package/package.json +10 -11
@@ -1,9 +1,8 @@
1
1
  import { matchRoutes } from "@modern-js/runtime-utils/router";
2
- import react_helmet from "react-helmet";
3
2
  import { getRouterMatchedRouteIds } from "../../../router/runtime/lifecycle.mjs";
4
3
  import { CHUNK_CSS_PLACEHOLDER } from "../constants.mjs";
5
4
  import { createFederatedCssLinks } from "../federatedCss.mjs";
6
- import { createReplaceHelemt } from "../helmet.mjs";
5
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
7
6
  import { buildHtml } from "../shared.mjs";
8
7
  import { safeReplace } from "../utils.mjs";
9
8
  const checkIsInline = (chunk, enableInline)=>{
@@ -13,7 +12,7 @@ const checkIsInline = (chunk, enableInline)=>{
13
12
  };
14
13
  async function buildShellBeforeTemplate(beforeAppTemplate, options) {
15
14
  const { config, runtimeContext, styledComponentsStyleTags, entryName, moduleFederationCssAssets } = options;
16
- const helmetData = react_helmet.renderStatic();
15
+ const helmetData = getHelmetData(runtimeContext);
17
16
  const callbacks = [
18
17
  createReplaceHelemt(helmetData),
19
18
  (template)=>injectCss(template, entryName, styledComponentsStyleTags)
@@ -35,11 +35,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
35
35
  if (extender.modifyRootElement) processedRootElement = extender.modifyRootElement(processedRootElement);
36
36
  });
37
37
  const chunkVec = [];
38
+ let hasStartedPipe = false;
38
39
  return new Promise((resolve)=>{
39
40
  const { pipe: reactStreamingPipe } = renderToPipeableStream(processedRootElement, {
40
41
  nonce: config.nonce,
41
42
  identifierPrefix: SSR_HYDRATION_ID_PREFIX,
42
43
  [onReady] () {
44
+ if (hasStartedPipe) return;
45
+ hasStartedPipe = true;
43
46
  let styledComponentsStyleTags = '';
44
47
  extenders.forEach((extender)=>{
45
48
  if (extender.getStyleTags) styledComponentsStyleTags += extender.getStyleTags();
@@ -1,24 +1,23 @@
1
1
  import { time } from "@modern-js/runtime-utils/time";
2
2
  import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
3
3
  import server from "react-dom/server";
4
- import react_helmet from "react-helmet";
5
4
  import { RenderLevel } from "../../constants.mjs";
6
5
  import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
7
6
  import { wrapRuntimeContextProvider } from "../../react/wrapper.mjs";
8
7
  import { CHUNK_CSS_PLACEHOLDER, CHUNK_JS_PLACEHOLDER, HTML_PLACEHOLDER, SSR_DATA_PLACEHOLDER } from "../constants.mjs";
9
- import { createReplaceHelemt } from "../helmet.mjs";
8
+ import { createReplaceHelemt, getHelmetData } from "../helmet.mjs";
10
9
  import { buildHtml } from "../shared.mjs";
11
10
  import { SSRErrors, SSRTimings } from "../tracer.mjs";
12
11
  import { getSSRConfigByEntry, safeReplace } from "../utils.mjs";
13
12
  import { LoadableCollector } from "./loadable.mjs";
14
13
  import { SSRDataCollector } from "./ssrData.mjs";
15
14
  const renderString = async (request, serverRoot, options)=>{
16
- const { resource, runtimeContext, config, onError, onTiming } = options;
15
+ const { resource, runtimeContext: runtimeContext1, config, onError, onTiming } = options;
17
16
  const tracer = {
18
17
  onError,
19
18
  onTiming
20
19
  };
21
- const routerContext = runtimeContext.routerContext;
20
+ const routerContext = runtimeContext1.routerContext;
22
21
  const { htmlTemplate, entryName, loadableStats, routeManifest, moduleFederationCssAssets } = resource;
23
22
  const ssrConfig = getSSRConfigByEntry(entryName, config.ssr, config.ssrByEntries);
24
23
  const chunkSet = {
@@ -32,7 +31,7 @@ const renderString = async (request, serverRoot, options)=>{
32
31
  stats: loadableStats,
33
32
  nonce: config.nonce,
34
33
  routeManifest,
35
- runtimeContext,
34
+ runtimeContext: runtimeContext1,
36
35
  template: htmlTemplate,
37
36
  entryName,
38
37
  moduleFederationCssAssets,
@@ -40,10 +39,10 @@ const renderString = async (request, serverRoot, options)=>{
40
39
  config
41
40
  }),
42
41
  new SSRDataCollector({
43
- runtimeContext,
42
+ runtimeContext: runtimeContext1,
44
43
  request,
45
44
  ssrConfig,
46
- ssrContext: runtimeContext.ssrContext,
45
+ ssrContext: runtimeContext1.ssrContext,
47
46
  chunkSet,
48
47
  routerContext,
49
48
  nonce: config.nonce,
@@ -56,10 +55,10 @@ const renderString = async (request, serverRoot, options)=>{
56
55
  chunkSet
57
56
  });
58
57
  for (const c of extraCollectors)if (c) collectors.unshift(c);
59
- const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext, {
58
+ const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext1, {
60
59
  ssr: true
61
60
  }));
62
- const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext.ssrContext?.htmlModifiers || [], tracer);
61
+ const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors, runtimeContext1.ssrContext?.htmlModifiers || [], tracer);
63
62
  return html;
64
63
  };
65
64
  async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifiers, { onError, onTiming }) {
@@ -72,7 +71,7 @@ async function generateHtml(App, htmlTemplate, chunkSet, collectors, htmlModifie
72
71
  identifierPrefix: SSR_HYDRATION_ID_PREFIX
73
72
  });
74
73
  chunkSet.renderLevel = RenderLevel.SERVER_RENDER;
75
- helmetData = react_helmet.renderStatic();
74
+ helmetData = getHelmetData(runtimeContext);
76
75
  const cost = end();
77
76
  onTiming(SSRTimings.RENDER_HTML, cost);
78
77
  } catch (e) {
@@ -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 };
@@ -44,12 +44,19 @@ function isModernDeferredData(value) {
44
44
  function normalizeModernLoaderResult(result) {
45
45
  return isModernDeferredData(result) ? result.data : result;
46
46
  }
47
- function pickRouteModuleComponent(routeModule) {
47
+ function pickRouteModuleComponent(routeModule, seen = new Set()) {
48
48
  if ('function' == typeof routeModule || routeModule && 'object' == typeof routeModule && '$$typeof' in routeModule) return routeModule;
49
49
  if (!routeModule || 'object' != typeof routeModule) return;
50
+ if (seen.has(routeModule)) return;
51
+ seen.add(routeModule);
50
52
  const module = routeModule;
51
- const component = module.default || module.Component;
52
- if ('function' == typeof component || component && 'object' == typeof component && '$$typeof' in component) return component;
53
+ for (const candidate of [
54
+ module.default,
55
+ module.Component
56
+ ]){
57
+ const component = pickRouteModuleComponent(candidate, seen);
58
+ if (component) return component;
59
+ }
53
60
  }
54
61
  function createServerLazyImportComponent(lazyImport, fallbackComponent) {
55
62
  if ("u" > typeof document) return fallbackComponent;
@@ -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,
@@ -94,23 +94,12 @@ const ssrBuilderPlugin = (modernAPI, outputModule, exportLoadablePath)=>({
94
94
  console.warn(warningMessage);
95
95
  }
96
96
  const isModuleFederationAppSSR = hasServerRendering && hasExplicitMfSsrFlag && !isCloudflareWorkerSSR;
97
- const useModuleFederationNodeOutput = hasServerRendering && isModuleFederationAppSSR && isNodeEnvironmentTarget(config.output.target);
98
97
  const ssrEnv = userConfig.deploy?.worker?.ssr || userConfig.server?.rsc ? 'edge' : 'node';
99
98
  const appContext = modernAPI.getAppContext();
100
99
  const { appDirectory, entrypoints } = appContext;
101
- const serverBundlerChain = useModuleFederationNodeOutput ? (chain)=>{
102
- chain.target('async-node');
103
- chain.output.module(false);
104
- chain.output.chunkFormat('commonjs');
105
- chain.output.chunkLoading('async-node');
106
- chain.output.library({
107
- ...chain.output.get('library') || {},
108
- type: 'commonjs-module'
109
- });
110
- } : void 0;
111
100
  const useLoadablePlugin = isUseSSRBundle(userConfig) && !isServerEnvironment && checkUseStringSSR(userConfig, appDirectory, entrypoints);
112
101
  const outputConfig = {
113
- module: isServerEnvironment && !useModuleFederationNodeOutput && (outputModule || 'workerSSR' === name && userConfig.deploy?.target === 'cloudflare')
102
+ module: isServerEnvironment && (outputModule || 'workerSSR' === name && userConfig.deploy?.target === 'cloudflare')
114
103
  };
115
104
  const useLoadableComponents = isUseSSRBundle(userConfig) && checkUseStringSSR(userConfig, appDirectory, entrypoints);
116
105
  return mergeEnvironmentConfig(config, {
@@ -123,13 +112,13 @@ const ssrBuilderPlugin = (modernAPI, outputModule, exportLoadablePath)=>({
123
112
  },
124
113
  output: outputConfig,
125
114
  tools: {
126
- bundlerChain: serverBundlerChain || (useLoadablePlugin ? (chain)=>{
115
+ bundlerChain: useLoadablePlugin ? (chain)=>{
127
116
  chain.plugin('loadable').use(loadable_bundler_plugin, [
128
117
  {
129
118
  filename: LOADABLE_STATS_FILE
130
119
  }
131
120
  ]);
132
- } : void 0),
121
+ } : void 0,
133
122
  swc: useLoadableComponents ? {
134
123
  jsc: {
135
124
  experimental: {
@@ -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) {