@bleedingdev/modern-js-runtime 3.8.3-ultramodern.1 → 3.8.3-ultramodern.2
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/cli/ssr/loadable-bundler-plugin.js +1 -1
- package/dist/cjs/core/browser/hydrate.js +1 -11
- package/dist/cjs/core/context/extensions.js +7 -2
- package/dist/cjs/core/context/runtime.js +13 -2
- package/dist/cjs/core/react/wrapper.js +10 -8
- package/dist/cjs/core/server/federatedCss.js +7 -4
- package/dist/cjs/core/server/helmet.js +2 -0
- package/dist/cjs/core/server/requestResponse.js +35 -11
- package/dist/cjs/core/server/routerCleanup.js +10 -1
- package/dist/cjs/core/server/scriptOrder.js +5 -2
- package/dist/cjs/core/server/stream/beforeTemplate.worker.js +1 -1
- package/dist/cjs/core/server/stream/createReadableStream.js +18 -5
- package/dist/cjs/core/server/stream/createReadableStream.worker.js +10 -2
- package/dist/cjs/core/server/stream/deferredScript.js +2 -1
- package/dist/cjs/core/server/string/index.js +4 -0
- package/dist/cjs/core/server/string/loadable.js +11 -2
- package/dist/cjs/core/server/utils.js +12 -5
- package/dist/cjs/exports/head.js +7 -242
- package/dist/cjs/module-federation/last-known-good.js +35 -2
- package/dist/cjs/router/cli/nestedRoutesSpec.js +7 -38
- package/dist/cjs/router/runtime/PrefetchLink.js +55 -8
- package/dist/cjs/router/runtime/redirect.js +11 -1
- package/dist/cjs/router/runtime/utils.js +2 -0
- package/dist/esm/cli/ssr/loadable-bundler-plugin.mjs +1 -1
- package/dist/esm/core/browser/hydrate.mjs +1 -11
- package/dist/esm/core/context/extensions.mjs +7 -2
- package/dist/esm/core/context/runtime.mjs +13 -2
- package/dist/esm/core/react/wrapper.mjs +11 -9
- package/dist/esm/core/server/federatedCss.mjs +7 -4
- package/dist/esm/core/server/helmet.mjs +2 -0
- package/dist/esm/core/server/requestResponse.mjs +35 -11
- package/dist/esm/core/server/routerCleanup.mjs +10 -1
- package/dist/esm/core/server/scriptOrder.mjs +6 -3
- package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -2
- package/dist/esm/core/server/stream/createReadableStream.mjs +16 -3
- package/dist/esm/core/server/stream/createReadableStream.worker.mjs +10 -2
- package/dist/esm/core/server/stream/deferredScript.mjs +2 -1
- package/dist/esm/core/server/string/index.mjs +4 -0
- package/dist/esm/core/server/string/loadable.mjs +11 -2
- package/dist/esm/core/server/utils.mjs +8 -4
- package/dist/esm/exports/head.mjs +8 -237
- package/dist/esm/module-federation/last-known-good.mjs +35 -2
- package/dist/esm/router/cli/nestedRoutesSpec.mjs +7 -38
- package/dist/esm/router/runtime/PrefetchLink.mjs +28 -8
- package/dist/esm/router/runtime/redirect.mjs +11 -1
- package/dist/esm/router/runtime/utils.mjs +2 -0
- package/dist/esm/rslib-runtime.mjs +9 -0
- package/dist/esm-node/cli/ssr/loadable-bundler-plugin.mjs +1 -1
- package/dist/esm-node/core/browser/hydrate.mjs +1 -11
- package/dist/esm-node/core/context/extensions.mjs +7 -2
- package/dist/esm-node/core/context/runtime.mjs +13 -2
- package/dist/esm-node/core/react/wrapper.mjs +11 -9
- package/dist/esm-node/core/server/federatedCss.mjs +7 -4
- package/dist/esm-node/core/server/helmet.mjs +2 -0
- package/dist/esm-node/core/server/requestResponse.mjs +35 -11
- package/dist/esm-node/core/server/routerCleanup.mjs +10 -1
- package/dist/esm-node/core/server/scriptOrder.mjs +6 -3
- package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -2
- package/dist/esm-node/core/server/stream/createReadableStream.mjs +16 -3
- package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +10 -2
- package/dist/esm-node/core/server/stream/deferredScript.mjs +2 -1
- package/dist/esm-node/core/server/string/index.mjs +4 -0
- package/dist/esm-node/core/server/string/loadable.mjs +11 -2
- package/dist/esm-node/core/server/utils.mjs +8 -4
- package/dist/esm-node/exports/head.mjs +8 -237
- package/dist/esm-node/module-federation/last-known-good.mjs +35 -2
- package/dist/esm-node/router/cli/nestedRoutesSpec.mjs +7 -38
- package/dist/esm-node/router/runtime/PrefetchLink.mjs +28 -8
- package/dist/esm-node/router/runtime/redirect.mjs +11 -1
- package/dist/esm-node/router/runtime/utils.mjs +2 -0
- package/dist/esm-node/rslib-runtime.mjs +10 -0
- package/dist/types/core/context/extensions.d.ts +2 -4
- package/dist/types/core/context/public.d.ts +4 -8
- package/dist/types/core/server/requestResponse.d.ts +1 -1
- package/dist/types/core/server/routerCleanup.d.ts +2 -0
- package/dist/types/core/server/string/loadable.d.ts +1 -0
- package/dist/types/core/server/utils.d.ts +1 -0
- package/dist/types/exports/head.d.ts +1 -2
- package/dist/types/module-federation/last-known-good.d.ts +0 -20
- package/package.json +10 -9
- package/rstest.config.mts +11 -4
|
@@ -4,247 +4,18 @@ import react from "react";
|
|
|
4
4
|
import { Helmet, HelmetData, HelmetProvider } from "react-helmet-async";
|
|
5
5
|
import { InternalRuntimeContext } from "../core/context/index.mjs";
|
|
6
6
|
import { ensureHelmetContext } from "../core/context/helmetContext.mjs";
|
|
7
|
-
|
|
8
|
-
charSet: 'charset',
|
|
9
|
-
className: 'class',
|
|
10
|
-
contentEditable: 'contenteditable',
|
|
11
|
-
httpEquiv: 'http-equiv',
|
|
12
|
-
hrefLang: 'hreflang',
|
|
13
|
-
itemProp: 'itemprop',
|
|
14
|
-
tabIndex: 'tabindex'
|
|
15
|
-
};
|
|
16
|
-
const CONTENT_PROPERTIES = new Set([
|
|
17
|
-
'innerHTML',
|
|
18
|
-
'cssText'
|
|
19
|
-
]);
|
|
20
|
-
const escapeHtml = (value)=>String(value).replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<').replaceAll('>', '>');
|
|
21
|
-
const toHtmlAttributeName = (name)=>ATTRIBUTE_NAME_MAP[name] ?? name;
|
|
22
|
-
const attributesToString = (attributes, includeHelmetAttribute = false)=>{
|
|
23
|
-
const pairs = [];
|
|
24
|
-
if (includeHelmetAttribute) pairs.push('data-rh="true"');
|
|
25
|
-
for (const [name, value] of Object.entries(attributes ?? {})){
|
|
26
|
-
if (false === value || null == value) continue;
|
|
27
|
-
if (CONTENT_PROPERTIES.has(name)) continue;
|
|
28
|
-
const htmlName = toHtmlAttributeName(name);
|
|
29
|
-
if (true === value) pairs.push(htmlName);
|
|
30
|
-
else pairs.push(`${htmlName}="${escapeHtml(value)}"`);
|
|
31
|
-
}
|
|
32
|
-
return pairs.join(' ');
|
|
33
|
-
};
|
|
34
|
-
const createDatum = (tagName, tags)=>({
|
|
35
|
-
toComponent: ()=>[],
|
|
36
|
-
toString: ()=>tags.map((tag)=>{
|
|
37
|
-
const attrs = attributesToString(tag, true);
|
|
38
|
-
if ("script" === tagName && 'string' == typeof tag.innerHTML) return `<script ${attrs}>${tag.innerHTML}</script>`;
|
|
39
|
-
if ('style' === tagName && 'string' == typeof tag.cssText) return `<style ${attrs}>${tag.cssText}</style>`;
|
|
40
|
-
if ("noscript" === tagName && 'string' == typeof tag.innerHTML) return `<noscript ${attrs}>${tag.innerHTML}</noscript>`;
|
|
41
|
-
return `<${tagName} ${attrs}>`;
|
|
42
|
-
}).join('')
|
|
43
|
-
});
|
|
44
|
-
const createAttributeDatum = (attributes)=>({
|
|
45
|
-
toComponent: ()=>attributes,
|
|
46
|
-
toString: ()=>attributesToString(attributes)
|
|
47
|
-
});
|
|
48
|
-
const createTitleDatum = (title, attributes)=>({
|
|
49
|
-
toComponent: ()=>[],
|
|
50
|
-
toString: ()=>{
|
|
51
|
-
if (void 0 === title || '' === title) return '';
|
|
52
|
-
const attrs = attributesToString(attributes, true);
|
|
53
|
-
return `<title ${attrs}>${escapeHtml(title)}</title>`;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
const normalizeHelmetTitle = (title)=>{
|
|
57
|
-
if ('string' == typeof title) return title;
|
|
58
|
-
if (Array.isArray(title)) return title.map((part)=>String(part)).join('');
|
|
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) {
|
|
76
|
-
draft.htmlAttributes = mergeAttributes(draft.htmlAttributes ?? {}, props);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if ('body' === child.type) {
|
|
80
|
-
draft.bodyAttributes = mergeAttributes(draft.bodyAttributes ?? {}, props);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if ('base' === child.type || 'link' === child.type || 'meta' === child.type || "noscript" === child.type || "script" === child.type || 'style' === child.type) {
|
|
84
|
-
const tag = {
|
|
85
|
-
...props
|
|
86
|
-
};
|
|
87
|
-
if (("script" === child.type || 'style' === child.type || "noscript" === child.type) && 'string' == typeof nestedChildren) tag['style' === child.type ? 'cssText' : 'innerHTML'] = nestedChildren;
|
|
88
|
-
draft[child.type].push(tag);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
const createHelmetRecord = (props)=>{
|
|
93
|
-
const draft = {
|
|
94
|
-
base: [
|
|
95
|
-
...void 0 !== props.base ? [
|
|
96
|
-
props.base
|
|
97
|
-
] : []
|
|
98
|
-
],
|
|
99
|
-
bodyAttributes: props.bodyAttributes,
|
|
100
|
-
htmlAttributes: props.htmlAttributes,
|
|
101
|
-
link: [
|
|
102
|
-
...props.link ?? []
|
|
103
|
-
],
|
|
104
|
-
meta: [
|
|
105
|
-
...props.meta ?? []
|
|
106
|
-
],
|
|
107
|
-
noscript: [
|
|
108
|
-
...props.noscript ?? []
|
|
109
|
-
],
|
|
110
|
-
script: [
|
|
111
|
-
...props.script ?? []
|
|
112
|
-
],
|
|
113
|
-
style: [
|
|
114
|
-
...props.style ?? []
|
|
115
|
-
],
|
|
116
|
-
title: normalizeHelmetTitle(props.title),
|
|
117
|
-
titleAttributes: props.titleAttributes ?? {}
|
|
118
|
-
};
|
|
119
|
-
collectChildren(props.children, draft);
|
|
120
|
-
return {
|
|
121
|
-
...draft,
|
|
122
|
-
titleTemplate: props.titleTemplate,
|
|
123
|
-
defaultTitle: props.defaultTitle
|
|
124
|
-
};
|
|
125
|
-
};
|
|
126
|
-
const getInnermostDefined = (records, pick)=>{
|
|
127
|
-
for(let i = records.length - 1; i >= 0; i -= 1){
|
|
128
|
-
const value = pick(records[i]);
|
|
129
|
-
if (void 0 !== value) return value;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
const deriveTitle = (records)=>{
|
|
133
|
-
const innermostTitle = getInnermostDefined(records, (record)=>record.title);
|
|
134
|
-
const innermostTemplate = getInnermostDefined(records, (record)=>record.titleTemplate);
|
|
135
|
-
if (innermostTemplate && innermostTitle) return innermostTemplate.replaceAll('%s', innermostTitle);
|
|
136
|
-
return innermostTitle || getInnermostDefined(records, (record)=>record.defaultTitle);
|
|
137
|
-
};
|
|
138
|
-
const mergeRecordAttributes = (records, pick)=>records.reduce((merged, record)=>mergeAttributes(merged, pick(record)), {});
|
|
139
|
-
const deriveBaseTags = (records)=>{
|
|
140
|
-
for(let i = records.length - 1; i >= 0; i -= 1){
|
|
141
|
-
const tags = records[i].base;
|
|
142
|
-
for(let j = tags.length - 1; j >= 0; j -= 1)if (tags[j].href) return [
|
|
143
|
-
tags[j]
|
|
144
|
-
];
|
|
145
|
-
}
|
|
146
|
-
return [];
|
|
147
|
-
};
|
|
148
|
-
const TAG_PRIMARY_ATTRIBUTES = {
|
|
149
|
-
link: [
|
|
150
|
-
'rel',
|
|
151
|
-
'href'
|
|
152
|
-
],
|
|
153
|
-
meta: [
|
|
154
|
-
'name',
|
|
155
|
-
'charset',
|
|
156
|
-
'http-equiv',
|
|
157
|
-
'property',
|
|
158
|
-
'itemprop'
|
|
159
|
-
],
|
|
160
|
-
noscript: [
|
|
161
|
-
'innerHTML'
|
|
162
|
-
],
|
|
163
|
-
script: [
|
|
164
|
-
'src',
|
|
165
|
-
'innerHTML'
|
|
166
|
-
],
|
|
167
|
-
style: [
|
|
168
|
-
'cssText'
|
|
169
|
-
]
|
|
170
|
-
};
|
|
171
|
-
const getPrimaryAttribute = (tagName, tag)=>{
|
|
172
|
-
const candidates = TAG_PRIMARY_ATTRIBUTES[tagName];
|
|
173
|
-
let selectedKey;
|
|
174
|
-
let selectedValue;
|
|
175
|
-
for (const [key, value] of Object.entries(tag)){
|
|
176
|
-
const normalizedKey = CONTENT_PROPERTIES.has(key) ? key : toHtmlAttributeName(key).toLowerCase();
|
|
177
|
-
if (candidates.includes(normalizedKey)) {
|
|
178
|
-
if ('rel' !== selectedKey || 'canonical' !== String(selectedValue).toLowerCase()) {
|
|
179
|
-
if ('rel' !== normalizedKey || 'stylesheet' !== String(value).toLowerCase()) {
|
|
180
|
-
selectedKey = normalizedKey;
|
|
181
|
-
selectedValue = value;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (void 0 === selectedKey || !selectedValue) return;
|
|
187
|
-
return {
|
|
188
|
-
key: selectedKey,
|
|
189
|
-
value: String(selectedValue).toLowerCase()
|
|
190
|
-
};
|
|
191
|
-
};
|
|
192
|
-
const dedupeTags = (tagName, records)=>{
|
|
193
|
-
const approvedValues = new Map();
|
|
194
|
-
const approved = [];
|
|
195
|
-
for(let i = records.length - 1; i >= 0; i -= 1){
|
|
196
|
-
const instanceValues = new Map();
|
|
197
|
-
const kept = [];
|
|
198
|
-
for (const tag of records[i][tagName]){
|
|
199
|
-
const primary = getPrimaryAttribute(tagName, tag);
|
|
200
|
-
if (void 0 === primary) continue;
|
|
201
|
-
if (approvedValues.get(primary.key)?.has(primary.value)) continue;
|
|
202
|
-
let seen = instanceValues.get(primary.key);
|
|
203
|
-
if (void 0 === seen) {
|
|
204
|
-
seen = new Set();
|
|
205
|
-
instanceValues.set(primary.key, seen);
|
|
206
|
-
}
|
|
207
|
-
seen.add(primary.value);
|
|
208
|
-
kept.push(tag);
|
|
209
|
-
}
|
|
210
|
-
approved.unshift(...kept);
|
|
211
|
-
for (const [key, values] of instanceValues){
|
|
212
|
-
const target = approvedValues.get(key);
|
|
213
|
-
if (void 0 === target) approvedValues.set(key, values);
|
|
214
|
-
else for (const value of values)target.add(value);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return approved;
|
|
218
|
-
};
|
|
219
|
-
const deriveHelmetServerState = (records)=>({
|
|
220
|
-
base: createDatum('base', deriveBaseTags(records)),
|
|
221
|
-
bodyAttributes: createAttributeDatum(mergeRecordAttributes(records, (record)=>record.bodyAttributes)),
|
|
222
|
-
htmlAttributes: createAttributeDatum(mergeRecordAttributes(records, (record)=>record.htmlAttributes)),
|
|
223
|
-
link: createDatum('link', dedupeTags('link', records)),
|
|
224
|
-
meta: createDatum('meta', dedupeTags('meta', records)),
|
|
225
|
-
noscript: createDatum("noscript", dedupeTags("noscript", records)),
|
|
226
|
-
priority: createDatum('meta', []),
|
|
227
|
-
script: createDatum("script", dedupeTags("script", records)),
|
|
228
|
-
style: createDatum('style', dedupeTags('style', records)),
|
|
229
|
-
title: createTitleDatum(deriveTitle(records), mergeRecordAttributes(records, (record)=>record.titleAttributes))
|
|
230
|
-
});
|
|
231
|
-
const serverHelmetRecords = new WeakMap();
|
|
7
|
+
import * as __rspack_external__modern_js_runtime_extensions_ce611631 from "@modern-js/runtime-extensions";
|
|
232
8
|
const collectServerHelmet = (runtimeContext, props)=>{
|
|
233
9
|
const helmetContext = ensureHelmetContext(runtimeContext);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
records.push(createHelmetRecord(props));
|
|
240
|
-
helmetContext.helmet = deriveHelmetServerState(records);
|
|
10
|
+
const createRecord = ()=>__rspack_external__modern_js_runtime_extensions_ce611631.createHelmetRecord(react, props);
|
|
11
|
+
const deriveState = (records)=>__rspack_external__modern_js_runtime_extensions_ce611631.deriveHelmetServerState(react, records);
|
|
12
|
+
const marker = __rspack_external__modern_js_runtime_extensions_ce611631.collectHeadState(runtimeContext, createRecord, deriveState, helmetContext);
|
|
13
|
+
if (void 0 !== marker) return marker;
|
|
14
|
+
helmetContext.helmet = __rspack_external__modern_js_runtime_extensions_ce611631.collectImmediateHelmetState(react, helmetContext, createRecord());
|
|
241
15
|
};
|
|
242
16
|
const head_Helmet = (props)=>{
|
|
243
17
|
const runtimeContext = react.useContext(InternalRuntimeContext);
|
|
244
|
-
if (null !== runtimeContext && false === runtimeContext.isBrowser)
|
|
245
|
-
collectServerHelmet(runtimeContext, props);
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
18
|
+
if (null !== runtimeContext && false === runtimeContext.isBrowser) return __rspack_external__modern_js_runtime_extensions_ce611631.renderHeadMarker(react, collectServerHelmet(runtimeContext, props));
|
|
248
19
|
return react.createElement(Helmet, props);
|
|
249
20
|
};
|
|
250
21
|
const head = {
|
|
@@ -254,4 +25,4 @@ const head = {
|
|
|
254
25
|
};
|
|
255
26
|
const exports_head = head;
|
|
256
27
|
export default exports_head;
|
|
257
|
-
export {
|
|
28
|
+
export { head_Helmet as Helmet };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { selectResolvedSurface } from "@modern-js/utils/universal";
|
|
2
3
|
import { createDiscoveryError, deliveryUnitResolutionKey } from "./surface-resolution-types.mjs";
|
|
3
4
|
const LAST_KNOWN_GOOD_PROVIDER_NAME = 'last-known-good';
|
|
4
5
|
function createInMemoryLkgStorage() {
|
|
@@ -32,16 +33,38 @@ function staleRecordResult(ref, env, ageMs, maxStaleMs) {
|
|
|
32
33
|
})
|
|
33
34
|
};
|
|
34
35
|
}
|
|
36
|
+
function resolutionAnswersRef(resolved, ref) {
|
|
37
|
+
const selected = selectResolvedSurface(resolved, ref);
|
|
38
|
+
return selected.ok && selected.surface.servedMajor === ref.major;
|
|
39
|
+
}
|
|
35
40
|
function createLastKnownGoodProvider(options) {
|
|
36
41
|
const storage = options.storage ?? createInMemoryLkgStorage();
|
|
37
42
|
const now = options.now ?? (()=>Math.floor(performance.timeOrigin + performance.now()));
|
|
38
43
|
const maxStaleMs = options.freshness?.maxStaleMs;
|
|
44
|
+
let nextResolutionGeneration = 0;
|
|
45
|
+
const latestCacheableGenerationByKey = new Map();
|
|
46
|
+
const writeTailByKey = new Map();
|
|
47
|
+
async function cacheCompatibleResolution(key, generation, record) {
|
|
48
|
+
latestCacheableGenerationByKey.set(key, Math.max(latestCacheableGenerationByKey.get(key) ?? 0, generation));
|
|
49
|
+
const previous = writeTailByKey.get(key) ?? Promise.resolve();
|
|
50
|
+
const write = previous.catch(()=>void 0).then(async ()=>{
|
|
51
|
+
if (latestCacheableGenerationByKey.get(key) !== generation) return;
|
|
52
|
+
await storage.write(key, record);
|
|
53
|
+
});
|
|
54
|
+
writeTailByKey.set(key, write);
|
|
55
|
+
try {
|
|
56
|
+
await write;
|
|
57
|
+
} finally{
|
|
58
|
+
if (writeTailByKey.get(key) === write) writeTailByKey.delete(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
39
61
|
async function serveFromCache(ref, env, fallback) {
|
|
40
62
|
const key = deliveryUnitResolutionKey(ref.unitId, env);
|
|
41
63
|
const cached = await storage.read(key);
|
|
42
64
|
if (!cached) return fallback();
|
|
43
65
|
if (cached.major !== ref.major) return fallback();
|
|
44
|
-
if (
|
|
66
|
+
if (!resolutionAnswersRef(cached.resolved, ref)) return fallback();
|
|
67
|
+
if ('compatible' !== cached.resolved.compatibility.status) return fallback();
|
|
45
68
|
const ageMs = now() - cached.storedAt;
|
|
46
69
|
if (void 0 !== maxStaleMs && ageMs > maxStaleMs) return staleRecordResult(ref, env, ageMs, maxStaleMs);
|
|
47
70
|
return {
|
|
@@ -53,6 +76,7 @@ function createLastKnownGoodProvider(options) {
|
|
|
53
76
|
name: LAST_KNOWN_GOOD_PROVIDER_NAME,
|
|
54
77
|
async resolve (ref, env) {
|
|
55
78
|
const key = deliveryUnitResolutionKey(ref.unitId, env);
|
|
79
|
+
const generation = ++nextResolutionGeneration;
|
|
56
80
|
let resolution;
|
|
57
81
|
try {
|
|
58
82
|
resolution = await options.provider.resolve(ref, env);
|
|
@@ -69,7 +93,16 @@ function createLastKnownGoodProvider(options) {
|
|
|
69
93
|
const failure = resolution;
|
|
70
94
|
return serveFromCache(ref, env, ()=>failure);
|
|
71
95
|
}
|
|
72
|
-
if (
|
|
96
|
+
if (!resolutionAnswersRef(resolution.unit, ref)) return serveFromCache(ref, env, ()=>({
|
|
97
|
+
ok: false,
|
|
98
|
+
error: createDiscoveryError('identity-mismatch', ref, `Provider ${options.provider.name} returned a delivery unit that does not answer the requested surface.`, {
|
|
99
|
+
env,
|
|
100
|
+
provider: options.provider.name,
|
|
101
|
+
recordUnitId: resolution.unit.unitId,
|
|
102
|
+
availableSurfaces: resolution.unit.surfaces.map((surface)=>surface.surfaceId)
|
|
103
|
+
})
|
|
104
|
+
}));
|
|
105
|
+
if ('compatible' === resolution.unit.compatibility.status) await cacheCompatibleResolution(key, generation, {
|
|
73
106
|
resolved: resolution.unit,
|
|
74
107
|
storedAt: now(),
|
|
75
108
|
major: ref.major
|
|
@@ -1,39 +1,12 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
|
-
import { setTimeout as promises_setTimeout } from "node:timers/promises";
|
|
4
3
|
import { fs } from "@modern-js/utils";
|
|
5
4
|
import { cloneDeep } from "@modern-js/utils/lodash";
|
|
6
|
-
const lockPollIntervalMs = 25;
|
|
7
|
-
const staleLockAgeMs = 120000;
|
|
8
5
|
const pendingUpdates = new Map();
|
|
9
6
|
let tempFileCounter = 0;
|
|
10
|
-
async function acquireSpecLock(specPath) {
|
|
11
|
-
const lockDir = `${specPath}.lock`;
|
|
12
|
-
await fs.ensureDir(node_path.dirname(specPath));
|
|
13
|
-
while(true){
|
|
14
|
-
try {
|
|
15
|
-
await fs.mkdir(lockDir);
|
|
16
|
-
return async ()=>{
|
|
17
|
-
await fs.remove(lockDir);
|
|
18
|
-
};
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if ('EEXIST' !== error.code) throw error;
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
const stat = await fs.stat(lockDir);
|
|
24
|
-
if (performance.timeOrigin + performance.now() - stat.mtimeMs > staleLockAgeMs) {
|
|
25
|
-
await fs.remove(lockDir);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
} catch (error) {
|
|
29
|
-
if ('ENOENT' !== error.code) throw error;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
await promises_setTimeout(lockPollIntervalMs);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
7
|
async function writeJSONAtomically(filePath, value) {
|
|
36
8
|
const directory = node_path.dirname(filePath);
|
|
9
|
+
await fs.ensureDir(directory);
|
|
37
10
|
const tempPath = node_path.join(directory, `.${node_path.basename(filePath)}.${process.pid}.${tempFileCounter += 1}.tmp`);
|
|
38
11
|
try {
|
|
39
12
|
await fs.writeFile(tempPath, `${JSON.stringify(value)}\n`);
|
|
@@ -48,16 +21,12 @@ async function updateNestedRoutesSpec(specPath, nextRoutes) {
|
|
|
48
21
|
const nextRoutesSnapshot = cloneDeep(nextRoutes);
|
|
49
22
|
const previousUpdate = pendingUpdates.get(resolvedSpecPath) ?? Promise.resolve();
|
|
50
23
|
const currentUpdate = previousUpdate.catch(()=>void 0).then(async ()=>{
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
58
|
-
} finally{
|
|
59
|
-
await releaseLock();
|
|
60
|
-
}
|
|
24
|
+
const existingRoutes = await fs.pathExists(resolvedSpecPath) ? await fs.readJSON(resolvedSpecPath) : {};
|
|
25
|
+
const mergedRoutes = {
|
|
26
|
+
...existingRoutes,
|
|
27
|
+
...nextRoutesSnapshot
|
|
28
|
+
};
|
|
29
|
+
await writeJSONAtomically(resolvedSpecPath, mergedRoutes);
|
|
61
30
|
});
|
|
62
31
|
pendingUpdates.set(resolvedSpecPath, currentUpdate);
|
|
63
32
|
try {
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
"use client";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { getNavigationWarmupCacheKey } from "@modern-js/runtime-extensions";
|
|
4
5
|
import { Link as router_Link, NavLink, matchRoutes, useHref, useMatches, useResolvedPath } from "@modern-js/runtime-utils/router";
|
|
5
6
|
import react, { useContext, useMemo } from "react";
|
|
6
7
|
import { InternalRuntimeContext } from "../../core/context/index.mjs";
|
|
8
|
+
import { __webpack_require__ } from "../../rslib-runtime.mjs";
|
|
9
|
+
const getWebpackChunkLoader = ()=>__webpack_chunk_load__;
|
|
10
|
+
const getWebpackPublicPath = ()=>{
|
|
11
|
+
try {
|
|
12
|
+
return __webpack_require__.p || '';
|
|
13
|
+
} catch {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
};
|
|
7
17
|
function composeEventHandlers(theirHandler, ourHandler) {
|
|
8
18
|
return (event)=>{
|
|
9
19
|
theirHandler?.(event);
|
|
@@ -42,8 +52,7 @@ const runNextWarmup = ()=>{
|
|
|
42
52
|
const task = warmupQueue.shift();
|
|
43
53
|
if (task.cancelled) continue;
|
|
44
54
|
activeWarmups += 1;
|
|
45
|
-
task.run().catch((
|
|
46
|
-
console.error(error);
|
|
55
|
+
task.run().catch(()=>{
|
|
47
56
|
warmupCache.delete(task.key);
|
|
48
57
|
}).finally(()=>{
|
|
49
58
|
activeWarmups -= 1;
|
|
@@ -65,6 +74,7 @@ const scheduleWarmup = (key, run)=>{
|
|
|
65
74
|
runNextWarmup();
|
|
66
75
|
return ()=>{
|
|
67
76
|
task.cancelled = true;
|
|
77
|
+
if (warmupQueue.includes(task)) warmupCache.delete(task.key);
|
|
68
78
|
};
|
|
69
79
|
};
|
|
70
80
|
const setRef = (ref, value)=>{
|
|
@@ -148,16 +158,17 @@ function usePrefetchBehavior(prefetch, preload, theirElementProps) {
|
|
|
148
158
|
setViewportElement
|
|
149
159
|
];
|
|
150
160
|
}
|
|
151
|
-
async function loadRouteModule(route, routeAssets) {
|
|
161
|
+
async function loadRouteModule(route, routeAssets, chunkLoader) {
|
|
152
162
|
const routeId = route.id;
|
|
153
163
|
if (!routeId) return;
|
|
154
164
|
if (!routeAssets[routeId]) return;
|
|
155
165
|
const { chunkIds } = routeAssets[routeId];
|
|
156
166
|
if (!chunkIds) return;
|
|
157
167
|
try {
|
|
158
|
-
await Promise.all(chunkIds.map((chunkId)=>
|
|
168
|
+
await Promise.all(chunkIds.map((chunkId)=>chunkLoader(chunkId)));
|
|
159
169
|
} catch (error) {
|
|
160
170
|
console.error(error);
|
|
171
|
+
throw error;
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
const getRequestUrl = (pathname, routeId)=>{
|
|
@@ -190,22 +201,31 @@ const PrefetchPageLinks = ({ path, includeData })=>{
|
|
|
190
201
|
pathname,
|
|
191
202
|
routes
|
|
192
203
|
]);
|
|
204
|
+
const chunkLoader = getWebpackChunkLoader();
|
|
205
|
+
const routeAssetGeneration = JSON.stringify([
|
|
206
|
+
getWebpackPublicPath(),
|
|
207
|
+
matches?.map(({ route: { id } })=>[
|
|
208
|
+
id,
|
|
209
|
+
routeAssets?.[id]?.chunkIds
|
|
210
|
+
])
|
|
211
|
+
]);
|
|
193
212
|
react.useEffect(()=>{
|
|
194
|
-
if (!allowNetworkWarmup || !Array.isArray(matches) || !routeAssets || !
|
|
213
|
+
if (!allowNetworkWarmup || !Array.isArray(matches) || !routeAssets || !chunkLoader) return;
|
|
195
214
|
const cancellations = matches.map((match)=>{
|
|
196
215
|
const routeId = match.route.id;
|
|
197
216
|
const routeAsset = routeId ? routeAssets[routeId] : void 0;
|
|
198
217
|
const chunkIds = routeAsset?.chunkIds;
|
|
199
218
|
if (!routeId || !Array.isArray(chunkIds) || 0 === chunkIds.length) return ()=>{};
|
|
200
|
-
return scheduleWarmup(`route-module:${routeId}:${chunkIds.join(',')}
|
|
219
|
+
return scheduleWarmup(getNavigationWarmupCacheKey(context, chunkLoader, getWebpackPublicPath(), `route-module:${routeId}:${chunkIds.join(',')}`), ()=>loadRouteModule(match.route, routeAssets, chunkLoader));
|
|
201
220
|
});
|
|
202
221
|
return ()=>{
|
|
203
222
|
cancellations.forEach((cancel)=>cancel());
|
|
204
223
|
};
|
|
205
224
|
}, [
|
|
206
225
|
allowNetworkWarmup,
|
|
207
|
-
|
|
208
|
-
|
|
226
|
+
chunkLoader,
|
|
227
|
+
context,
|
|
228
|
+
routeAssetGeneration
|
|
209
229
|
]);
|
|
210
230
|
if (!allowNetworkWarmup || !includeData || !window._SSR_DATA) return null;
|
|
211
231
|
return /*#__PURE__*/ jsx(PrefetchDataLinks, {
|
|
@@ -2,7 +2,17 @@ import "node:module";
|
|
|
2
2
|
const handleRSCRedirect = (headers, basename, status)=>{
|
|
3
3
|
const newHeaders = new Headers(headers);
|
|
4
4
|
let redirectUrl = headers.get('Location');
|
|
5
|
-
if (
|
|
5
|
+
if (!redirectUrl || ![
|
|
6
|
+
301,
|
|
7
|
+
302,
|
|
8
|
+
303,
|
|
9
|
+
307,
|
|
10
|
+
308
|
|
11
|
+
].includes(status) || !URL.canParse(redirectUrl, 'http://localhost')) return new Response(null, {
|
|
12
|
+
status,
|
|
13
|
+
headers: newHeaders
|
|
14
|
+
});
|
|
15
|
+
if ('/' !== basename && redirectUrl.startsWith(basename)) redirectUrl = redirectUrl.slice(basename.length) || '/';
|
|
6
16
|
newHeaders.set('X-Modernjs-Redirect', redirectUrl);
|
|
7
17
|
newHeaders.set('X-Modernjs-BaseUrl', basename);
|
|
8
18
|
newHeaders.delete('Location');
|
|
@@ -177,12 +177,14 @@ function serializeRouteErrorResponse(error) {
|
|
|
177
177
|
status: error.status,
|
|
178
178
|
statusText: 'Internal Server Error',
|
|
179
179
|
data: 'Unexpected Server Error',
|
|
180
|
+
internal: error.internal,
|
|
180
181
|
__type: 'RouteErrorResponse'
|
|
181
182
|
};
|
|
182
183
|
return {
|
|
183
184
|
status: error.status,
|
|
184
185
|
statusText: error.statusText,
|
|
185
186
|
data: error.data,
|
|
187
|
+
internal: error.internal,
|
|
186
188
|
__type: 'RouteErrorResponse'
|
|
187
189
|
};
|
|
188
190
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
var scriptUrl;
|
|
5
|
+
if ("string" == typeof import.meta.url) scriptUrl = import.meta.url;
|
|
6
|
+
if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
|
|
7
|
+
scriptUrl = scriptUrl.replace(/^blob:/, "").replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
|
|
8
|
+
__webpack_require__.p = scriptUrl;
|
|
9
|
+
})();
|
|
10
|
+
export { __webpack_require__ };
|
|
@@ -22,10 +22,8 @@ export interface RuntimeContextExtension<T> {
|
|
|
22
22
|
/**
|
|
23
23
|
* Removes the extension slot from a context object.
|
|
24
24
|
*
|
|
25
|
-
* The slot
|
|
26
|
-
*
|
|
27
|
-
* `RuntimeContext` value to keep internal state (router instances, helmet
|
|
28
|
-
* state, ...) unreachable from the public context object.
|
|
25
|
+
* The symbol slot crosses object spreads while remaining absent from string-key
|
|
26
|
+
* enumeration and JSON. Callers can still explicitly clear the original context.
|
|
29
27
|
*/
|
|
30
28
|
export declare function stripRuntimeContextExtensions(context: object): void;
|
|
31
29
|
/**
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
type
|
|
2
|
-
type
|
|
3
|
-
export type RequestContext
|
|
4
|
-
request: RuntimeRequest;
|
|
5
|
-
response: RuntimeResponse;
|
|
6
|
-
};
|
|
1
|
+
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
2
|
+
import type { RequestContext } from '../types';
|
|
3
|
+
export type { RequestContext };
|
|
7
4
|
export interface TRuntimeContext {
|
|
8
5
|
initialData?: Record<string, unknown>;
|
|
9
6
|
isBrowser: boolean;
|
|
10
|
-
routes?:
|
|
7
|
+
routes?: RouteObject[];
|
|
11
8
|
requestContext: RequestContext;
|
|
12
9
|
/**
|
|
13
10
|
* @deprecated Use `requestContext` instead
|
|
@@ -24,4 +21,3 @@ export declare const ReactRuntimeContext: import("react").Context<TRuntimeContex
|
|
|
24
21
|
* @deprecated use use(RuntimeContext) instead
|
|
25
22
|
*/
|
|
26
23
|
export declare const useRuntimeContext: () => TRuntimeContext;
|
|
27
|
-
export {};
|
|
@@ -12,4 +12,4 @@ export type RedirectContext = {
|
|
|
12
12
|
};
|
|
13
13
|
export declare const applyRouterSnapshotResult: (context: TInternalRuntimeContext, onError: OnError) => void;
|
|
14
14
|
export declare const createLoaderRedirectResponse: (beforeRenderResult: Response | undefined, redirectCtx: RedirectContext) => Response | undefined;
|
|
15
|
-
export declare const finalizeRenderResponse: (response: Response, responseProxy: ResponseProxy, redirectCtx: RedirectContext, routerCleanup: RouterCleanup) => Response
|
|
15
|
+
export declare const finalizeRenderResponse: (response: Response, responseProxy: ResponseProxy, redirectCtx: RedirectContext, routerCleanup: RouterCleanup) => Promise<Response>;
|
|
@@ -23,6 +23,8 @@ export type RouterCleanup = {
|
|
|
23
23
|
* returned as-is and the caller is expected to invoke `run()` itself.
|
|
24
24
|
*/
|
|
25
25
|
deferUntilBodyDone: (response: Response) => Response;
|
|
26
|
+
/** Cancels a discarded body before running router cleanup. */
|
|
27
|
+
discardBody: (response: Response) => Promise<void>;
|
|
26
28
|
};
|
|
27
29
|
export declare function runWithRouterCleanupOnError<T>(routerCleanup: RouterCleanup, callback: () => Promise<T> | T): Promise<T>;
|
|
28
30
|
export declare function finishWithRouterCleanup<T>(routerCleanup: RouterCleanup, callback: () => Promise<T> | T): Promise<T>;
|
|
@@ -29,6 +29,7 @@ export declare class LoadableCollector implements Collector {
|
|
|
29
29
|
private options;
|
|
30
30
|
private extractor?;
|
|
31
31
|
constructor(options: LoadableCollectorOptions);
|
|
32
|
+
private get existsAssets();
|
|
32
33
|
collect(comopnent: ReactElement): ReactElement;
|
|
33
34
|
effect(): Promise<void>;
|
|
34
35
|
private emitLoadableScripts;
|
|
@@ -15,6 +15,7 @@ export declare function checkIsNode(): boolean;
|
|
|
15
15
|
* license at https://github.com/remix-run/remix/blob/main/LICENSE.md
|
|
16
16
|
*/
|
|
17
17
|
export declare function serializeErrors(errors: StaticHandlerContext['errors']): StaticHandlerContext['errors'];
|
|
18
|
+
export declare function shouldRedactServerError(): boolean;
|
|
18
19
|
export declare function getSSRConfigByEntry(entryName: string, ssr?: ServerUserConfig['ssr'], ssrByEntries?: ServerUserConfig['ssrByEntries']): true | {
|
|
19
20
|
forceCSR?: boolean;
|
|
20
21
|
mode?: import("@modern-js/types").SSRMode;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Helmet as AsyncHelmet, HelmetData as AsyncHelmetData, type HelmetDatum, type HelmetHTMLBodyDatum, type HelmetHTMLElementDatum, type HelmetProps, HelmetProvider, type HelmetServerState, type HelmetTags } from 'react-helmet-async';
|
|
3
|
-
export declare const Helmet: (props: React.PropsWithChildren<HelmetProps>) => React.CElement<React.PropsWithChildren<HelmetProps>, AsyncHelmet> | null;
|
|
3
|
+
export declare const Helmet: (props: React.PropsWithChildren<HelmetProps>) => React.CElement<React.PropsWithChildren<HelmetProps>, AsyncHelmet> | React.ReactElement<{}, string | React.JSXElementConstructor<any>> | null;
|
|
4
4
|
declare const head: {
|
|
5
5
|
Helmet: typeof Helmet;
|
|
6
6
|
HelmetData: typeof AsyncHelmetData;
|
|
@@ -8,4 +8,3 @@ declare const head: {
|
|
|
8
8
|
};
|
|
9
9
|
export default head;
|
|
10
10
|
export type { HelmetDatum, HelmetHTMLBodyDatum, HelmetHTMLElementDatum, HelmetProps, HelmetServerState, HelmetTags, };
|
|
11
|
-
export { AsyncHelmetData as HelmetData, HelmetProvider };
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* G24a/b — last-known-good (LKG) provider wrapper.
|
|
3
|
-
*
|
|
4
|
-
* RESOLUTION-0001 §2.2: the LKG cache "wraps any provider; serves the last
|
|
5
|
-
* complete record on provider failure, marked `compatibility: 'degraded'`". It
|
|
6
|
-
* is universal — no DOM/fetch/framework coupling — so the same wrapper works in
|
|
7
|
-
* the browser, Node, and Cloudflare.
|
|
8
|
-
*
|
|
9
|
-
* Rollback semantics (ADR-0019 / CONTEXT.md Rollback): the delivery UNIT is
|
|
10
|
-
* the unit of rollback. The cache stores exactly ONE whole
|
|
11
|
-
* {@link ResolvedDeliveryUnit} per (unitId, env) key — never per (ref, env) —
|
|
12
|
-
* so every surface of a unit shares one snapshot and a fresh success swaps that
|
|
13
|
-
* snapshot atomically for all of them (two surfaces can never be served from
|
|
14
|
-
* different cached build markers). It never stores or serves a partial record,
|
|
15
|
-
* and it never mixes locations across build markers — a served degraded record
|
|
16
|
-
* is a byte-for-byte prior success with only its compatibility verdict flipped
|
|
17
|
-
* to `degraded`. An `incompatible` record is never good: it is neither cached
|
|
18
|
-
* as the last-known-good snapshot nor ever served. The ref's external-major
|
|
19
|
-
* participates in lookup validation but not in the storage key.
|
|
20
|
-
*/
|
|
21
1
|
import { type ResolvedDeliveryUnit, type SurfaceResolutionProvider } from './surface-resolution-types';
|
|
22
2
|
export declare const LAST_KNOWN_GOOD_PROVIDER_NAME = "last-known-good";
|
|
23
3
|
/** One cached complete record plus the time it was stored. */
|