@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.
- package/dist/cjs/core/react/wrapper.js +9 -3
- package/dist/cjs/core/server/helmet.js +6 -0
- package/dist/cjs/core/server/stream/beforeTemplate.js +1 -12
- package/dist/cjs/core/server/stream/beforeTemplate.worker.js +1 -12
- package/dist/cjs/core/server/stream/createReadableStream.js +3 -0
- package/dist/cjs/core/server/string/index.js +8 -10
- package/dist/cjs/exports/head.js +196 -5
- package/dist/cjs/ssr/serverRender/renderToString/entry.js +9 -8
- package/dist/esm/core/react/wrapper.mjs +9 -3
- package/dist/esm/core/server/helmet.mjs +4 -1
- package/dist/esm/core/server/stream/beforeTemplate.mjs +2 -3
- package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -3
- package/dist/esm/core/server/stream/createReadableStream.mjs +3 -0
- package/dist/esm/core/server/string/index.mjs +9 -10
- package/dist/esm/exports/head.mjs +189 -4
- package/dist/esm/ssr/serverRender/renderToString/entry.mjs +9 -6
- package/dist/esm-node/core/react/wrapper.mjs +9 -3
- package/dist/esm-node/core/server/helmet.mjs +4 -1
- package/dist/esm-node/core/server/stream/beforeTemplate.mjs +2 -3
- package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -3
- package/dist/esm-node/core/server/stream/createReadableStream.mjs +3 -0
- package/dist/esm-node/core/server/string/index.mjs +9 -10
- package/dist/esm-node/exports/head.mjs +189 -4
- package/dist/esm-node/ssr/serverRender/renderToString/entry.mjs +9 -6
- package/dist/types/core/context/runtime.d.ts +4 -0
- package/dist/types/core/server/helmet.d.ts +5 -3
- package/dist/types/exports/head.d.ts +10 -3
- package/package.json +10 -11
|
@@ -1,5 +1,190 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
|
|
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
|
|
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 =
|
|
64
|
-
return helmetData ?
|
|
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
|
|
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:
|
|
18
|
+
value: internalContextValue,
|
|
16
19
|
children: /*#__PURE__*/ jsx(RuntimeContext.Provider, {
|
|
17
20
|
value: runtimeContextValue,
|
|
18
|
-
children:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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(
|
|
59
|
+
const rootElement = wrapRuntimeContextProvider(serverRoot, Object.assign(runtimeContext1, {
|
|
61
60
|
ssr: true
|
|
62
61
|
}));
|
|
63
|
-
const html = await generateHtml(rootElement, htmlTemplate, chunkSet, collectors,
|
|
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 =
|
|
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
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
|
|
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
|
|
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 =
|
|
65
|
-
return helmetData ?
|
|
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
|
|
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>;
|