@bleedingdev/modern-js-runtime 3.8.3-ultramodern.1 → 3.8.3-ultramodern.3

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 (81) hide show
  1. package/dist/cjs/cli/ssr/loadable-bundler-plugin.js +1 -1
  2. package/dist/cjs/core/browser/hydrate.js +1 -11
  3. package/dist/cjs/core/context/extensions.js +7 -2
  4. package/dist/cjs/core/context/runtime.js +13 -2
  5. package/dist/cjs/core/react/wrapper.js +10 -8
  6. package/dist/cjs/core/server/federatedCss.js +7 -4
  7. package/dist/cjs/core/server/helmet.js +2 -0
  8. package/dist/cjs/core/server/requestResponse.js +35 -11
  9. package/dist/cjs/core/server/routerCleanup.js +10 -1
  10. package/dist/cjs/core/server/scriptOrder.js +5 -2
  11. package/dist/cjs/core/server/stream/beforeTemplate.worker.js +1 -1
  12. package/dist/cjs/core/server/stream/createReadableStream.js +18 -5
  13. package/dist/cjs/core/server/stream/createReadableStream.worker.js +10 -2
  14. package/dist/cjs/core/server/stream/deferredScript.js +2 -1
  15. package/dist/cjs/core/server/string/index.js +4 -0
  16. package/dist/cjs/core/server/string/loadable.js +11 -2
  17. package/dist/cjs/core/server/utils.js +12 -5
  18. package/dist/cjs/exports/head.js +7 -242
  19. package/dist/cjs/module-federation/last-known-good.js +35 -2
  20. package/dist/cjs/router/cli/nestedRoutesSpec.js +7 -38
  21. package/dist/cjs/router/runtime/PrefetchLink.js +55 -8
  22. package/dist/cjs/router/runtime/redirect.js +11 -1
  23. package/dist/cjs/router/runtime/utils.js +2 -0
  24. package/dist/esm/cli/ssr/loadable-bundler-plugin.mjs +1 -1
  25. package/dist/esm/core/browser/hydrate.mjs +1 -11
  26. package/dist/esm/core/context/extensions.mjs +7 -2
  27. package/dist/esm/core/context/runtime.mjs +13 -2
  28. package/dist/esm/core/react/wrapper.mjs +11 -9
  29. package/dist/esm/core/server/federatedCss.mjs +7 -4
  30. package/dist/esm/core/server/helmet.mjs +2 -0
  31. package/dist/esm/core/server/requestResponse.mjs +35 -11
  32. package/dist/esm/core/server/routerCleanup.mjs +10 -1
  33. package/dist/esm/core/server/scriptOrder.mjs +6 -3
  34. package/dist/esm/core/server/stream/beforeTemplate.worker.mjs +2 -2
  35. package/dist/esm/core/server/stream/createReadableStream.mjs +16 -3
  36. package/dist/esm/core/server/stream/createReadableStream.worker.mjs +10 -2
  37. package/dist/esm/core/server/stream/deferredScript.mjs +2 -1
  38. package/dist/esm/core/server/string/index.mjs +4 -0
  39. package/dist/esm/core/server/string/loadable.mjs +11 -2
  40. package/dist/esm/core/server/utils.mjs +8 -4
  41. package/dist/esm/exports/head.mjs +8 -237
  42. package/dist/esm/module-federation/last-known-good.mjs +35 -2
  43. package/dist/esm/router/cli/nestedRoutesSpec.mjs +7 -38
  44. package/dist/esm/router/runtime/PrefetchLink.mjs +28 -8
  45. package/dist/esm/router/runtime/redirect.mjs +11 -1
  46. package/dist/esm/router/runtime/utils.mjs +2 -0
  47. package/dist/esm/rslib-runtime.mjs +9 -0
  48. package/dist/esm-node/cli/ssr/loadable-bundler-plugin.mjs +1 -1
  49. package/dist/esm-node/core/browser/hydrate.mjs +1 -11
  50. package/dist/esm-node/core/context/extensions.mjs +7 -2
  51. package/dist/esm-node/core/context/runtime.mjs +13 -2
  52. package/dist/esm-node/core/react/wrapper.mjs +11 -9
  53. package/dist/esm-node/core/server/federatedCss.mjs +7 -4
  54. package/dist/esm-node/core/server/helmet.mjs +2 -0
  55. package/dist/esm-node/core/server/requestResponse.mjs +35 -11
  56. package/dist/esm-node/core/server/routerCleanup.mjs +10 -1
  57. package/dist/esm-node/core/server/scriptOrder.mjs +6 -3
  58. package/dist/esm-node/core/server/stream/beforeTemplate.worker.mjs +2 -2
  59. package/dist/esm-node/core/server/stream/createReadableStream.mjs +16 -3
  60. package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +10 -2
  61. package/dist/esm-node/core/server/stream/deferredScript.mjs +2 -1
  62. package/dist/esm-node/core/server/string/index.mjs +4 -0
  63. package/dist/esm-node/core/server/string/loadable.mjs +11 -2
  64. package/dist/esm-node/core/server/utils.mjs +8 -4
  65. package/dist/esm-node/exports/head.mjs +8 -237
  66. package/dist/esm-node/module-federation/last-known-good.mjs +35 -2
  67. package/dist/esm-node/router/cli/nestedRoutesSpec.mjs +7 -38
  68. package/dist/esm-node/router/runtime/PrefetchLink.mjs +28 -8
  69. package/dist/esm-node/router/runtime/redirect.mjs +11 -1
  70. package/dist/esm-node/router/runtime/utils.mjs +2 -0
  71. package/dist/esm-node/rslib-runtime.mjs +10 -0
  72. package/dist/types/core/context/extensions.d.ts +2 -4
  73. package/dist/types/core/context/public.d.ts +4 -8
  74. package/dist/types/core/server/requestResponse.d.ts +1 -1
  75. package/dist/types/core/server/routerCleanup.d.ts +2 -0
  76. package/dist/types/core/server/string/loadable.d.ts +1 -0
  77. package/dist/types/core/server/utils.d.ts +1 -0
  78. package/dist/types/exports/head.d.ts +1 -2
  79. package/dist/types/module-federation/last-known-good.d.ts +0 -20
  80. package/package.json +10 -9
  81. 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
- const ATTRIBUTE_NAME_MAP = {
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('&', '&amp;').replaceAll('"', '&quot;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
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
- let records = serverHelmetRecords.get(helmetContext);
235
- if (void 0 === records) {
236
- records = [];
237
- serverHelmetRecords.set(helmetContext, records);
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 { HelmetData, HelmetProvider, head_Helmet as Helmet };
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 ('incompatible' === cached.resolved.compatibility.status) return fallback();
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 ('incompatible' !== resolution.unit.compatibility.status) await storage.write(key, {
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 releaseLock = await acquireSpecLock(resolvedSpecPath);
52
- try {
53
- const existingRoutes = await fs.pathExists(resolvedSpecPath) ? await fs.readJSON(resolvedSpecPath) : {};
54
- await writeJSONAtomically(resolvedSpecPath, {
55
- ...existingRoutes,
56
- ...nextRoutesSnapshot
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((error)=>{
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)=>__webpack_chunk_load__?.(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 || !__webpack_chunk_load__) return;
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(',')}`, ()=>loadRouteModule(match.route, routeAssets));
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
- matches,
208
- routeAssets
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 ('/' !== basename) redirectUrl = redirectUrl.replace(basename, '');
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 is an enumerable symbol-keyed property, so object spreads copy it
26
- * onto derived objects. Call this on *copies* that form the public
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 RuntimeRequest = Record<string, unknown>;
2
- type RuntimeResponse = Record<string, unknown>;
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?: unknown[];
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. */