@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.16 → 3.9.0-ultramodern.18

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.
@@ -57,8 +57,9 @@ var __webpack_exports__ = {};
57
57
  "createWebHeadMarkerStripper",
58
58
  "default",
59
59
  "finishHeadRender",
60
- "abortHeadRender",
60
+ "LATE_HEAD_MESSAGE",
61
61
  "beginHeadRender",
62
+ "abortHeadRender",
62
63
  "completeHeadRender",
63
64
  "createHeadChunkProcessor",
64
65
  "publishHeadRender",
@@ -71,6 +72,7 @@ var __webpack_exports__ = {};
71
72
  const MARKER_SUFFIX = '"></template>';
72
73
  const RECORD_ID_WIDTH = 12;
73
74
  const MARKER_LENGTH = MARKER_PREFIX.length + 1 + 36 + RECORD_ID_WIDTH + MARKER_SUFFIX.length;
75
+ const LATE_HEAD_MESSAGE = "<Helmet> rendered in a boundary that completed after the shell; head tags dropped. Move it above the Suspense boundary or set server.ssrByEntries[entry].mode to 'string'.";
74
76
  const HEAD_STATE = Symbol('modern.rendererHead');
75
77
  const getState = (context)=>{
76
78
  const headContext = context;
@@ -91,7 +93,12 @@ var __webpack_exports__ = {};
91
93
  transaction.nextRecordId += 1;
92
94
  return token;
93
95
  };
94
- const beginHeadRender = (context)=>{
96
+ const dropLateHead = (transaction)=>{
97
+ if (transaction.lateHeadReported) return;
98
+ transaction.lateHeadReported = true;
99
+ transaction.reportLateHead(LATE_HEAD_MESSAGE);
100
+ };
101
+ const beginHeadRender = (context, reportLateHead)=>{
95
102
  const state = getState(context);
96
103
  state.transaction = {
97
104
  recordsByToken: new Map(),
@@ -100,7 +107,9 @@ var __webpack_exports__ = {};
100
107
  nextRecordId: 0,
101
108
  nonce: globalThis.crypto.randomUUID(),
102
109
  previousRecords: state.publishedRecords,
103
- sealed: false
110
+ sealed: false,
111
+ reportLateHead,
112
+ lateHeadReported: false
104
113
  };
105
114
  state.publish?.([]);
106
115
  };
@@ -111,7 +120,6 @@ var __webpack_exports__ = {};
111
120
  const hadPublisher = void 0 !== state.publish;
112
121
  state.publish = (records)=>publish(records);
113
122
  if (!hadPublisher) state.publish([]);
114
- if (transaction.sealed) return null;
115
123
  const token = createToken(transaction);
116
124
  transaction.recordsByToken.set(token, createRecord());
117
125
  return {
@@ -141,7 +149,9 @@ var __webpack_exports__ = {};
141
149
  const token = html.slice(tokenStart, end);
142
150
  if (transaction.recordsByToken.has(token)) {
143
151
  stripped += html.slice(cursor, start);
144
- if (!transaction.sealed && !transaction.committedTokens.has(token)) {
152
+ if (transaction.sealed) {
153
+ if (!transaction.committedTokens.has(token)) dropLateHead(transaction);
154
+ } else if (!transaction.committedTokens.has(token)) {
145
155
  transaction.committedTokens.add(token);
146
156
  transaction.committedRecords.push(transaction.recordsByToken.get(token));
147
157
  }
@@ -227,6 +237,7 @@ var __webpack_exports__ = {};
227
237
  });
228
238
  };
229
239
  __webpack_require__.d(__webpack_exports__, {}, {
240
+ LATE_HEAD_MESSAGE: LATE_HEAD_MESSAGE,
230
241
  abortHeadRender: abortHeadRender,
231
242
  beginHeadRender: beginHeadRender,
232
243
  collectHeadRecord: collectHeadRecord,
@@ -238,6 +249,7 @@ var __webpack_exports__ = {};
238
249
  publishHeadRender: publishHeadRender
239
250
  });
240
251
  })();
252
+ exports.LATE_HEAD_MESSAGE = __webpack_exports__.LATE_HEAD_MESSAGE;
241
253
  exports.abortHeadRender = __webpack_exports__.abortHeadRender;
242
254
  exports.beginHeadRender = __webpack_exports__.beginHeadRender;
243
255
  exports.collectHeadRecord = __webpack_exports__.collectHeadRecord;
@@ -248,6 +260,7 @@ exports.createWebHeadMarkerStripper = __webpack_exports__.createWebHeadMarkerStr
248
260
  exports.finishHeadRender = __webpack_exports__.finishHeadRender;
249
261
  exports.publishHeadRender = __webpack_exports__.publishHeadRender;
250
262
  for(var __rspack_i in __webpack_exports__)if (-1 === [
263
+ "LATE_HEAD_MESSAGE",
251
264
  "abortHeadRender",
252
265
  "beginHeadRender",
253
266
  "collectHeadRecord",
@@ -81,7 +81,7 @@ const toOrigin = (value)=>{
81
81
  if (!value) return;
82
82
  try {
83
83
  return new URL(value).origin;
84
- } catch (error) {
84
+ } catch {
85
85
  return;
86
86
  }
87
87
  };
@@ -87,7 +87,7 @@ const shouldRetryWithDefaults = (method, statusCode, error, retryableStatusCodes
87
87
  const shouldRetry = (retryOptions, context, retryableStatusCodes)=>{
88
88
  if ('function' == typeof retryOptions?.shouldRetry) try {
89
89
  return retryOptions.shouldRetry(context);
90
- } catch (error) {
90
+ } catch {
91
91
  return false;
92
92
  }
93
93
  return shouldRetryWithDefaults(context.method, context.statusCode, context.error, retryableStatusCodes);
@@ -104,7 +104,7 @@ const emitDegradedEvent = (transport, event)=>{
104
104
  if ('function' != typeof transport?.onDegraded) return;
105
105
  try {
106
106
  transport.onDegraded(event);
107
- } catch (error) {}
107
+ } catch {}
108
108
  };
109
109
  const withTimeout = async (promise, timeoutMs, signalBuilder)=>{
110
110
  if (!timeoutMs || timeoutMs <= 0) {
@@ -4,6 +4,7 @@ const MARKER_PREFIX = `<template ${MARKER_ATTRIBUTE}="`;
4
4
  const MARKER_SUFFIX = '"></template>';
5
5
  const RECORD_ID_WIDTH = 12;
6
6
  const MARKER_LENGTH = MARKER_PREFIX.length + 1 + 36 + RECORD_ID_WIDTH + MARKER_SUFFIX.length;
7
+ const LATE_HEAD_MESSAGE = "<Helmet> rendered in a boundary that completed after the shell; head tags dropped. Move it above the Suspense boundary or set server.ssrByEntries[entry].mode to 'string'.";
7
8
  const HEAD_STATE = Symbol('modern.rendererHead');
8
9
  const getState = (context)=>{
9
10
  const headContext = context;
@@ -24,7 +25,12 @@ const createToken = (transaction)=>{
24
25
  transaction.nextRecordId += 1;
25
26
  return token;
26
27
  };
27
- const beginHeadRender = (context)=>{
28
+ const dropLateHead = (transaction)=>{
29
+ if (transaction.lateHeadReported) return;
30
+ transaction.lateHeadReported = true;
31
+ transaction.reportLateHead(LATE_HEAD_MESSAGE);
32
+ };
33
+ const beginHeadRender = (context, reportLateHead)=>{
28
34
  const state = getState(context);
29
35
  state.transaction = {
30
36
  recordsByToken: new Map(),
@@ -33,7 +39,9 @@ const beginHeadRender = (context)=>{
33
39
  nextRecordId: 0,
34
40
  nonce: globalThis.crypto.randomUUID(),
35
41
  previousRecords: state.publishedRecords,
36
- sealed: false
42
+ sealed: false,
43
+ reportLateHead,
44
+ lateHeadReported: false
37
45
  };
38
46
  state.publish?.([]);
39
47
  };
@@ -44,7 +52,6 @@ const collectHeadRecord = (context, createRecord, publish)=>{
44
52
  const hadPublisher = void 0 !== state.publish;
45
53
  state.publish = (records)=>publish(records);
46
54
  if (!hadPublisher) state.publish([]);
47
- if (transaction.sealed) return null;
48
55
  const token = createToken(transaction);
49
56
  transaction.recordsByToken.set(token, createRecord());
50
57
  return {
@@ -74,7 +81,9 @@ const consumeMarkers = (context, html)=>{
74
81
  const token = html.slice(tokenStart, end);
75
82
  if (transaction.recordsByToken.has(token)) {
76
83
  stripped += html.slice(cursor, start);
77
- if (!transaction.sealed && !transaction.committedTokens.has(token)) {
84
+ if (transaction.sealed) {
85
+ if (!transaction.committedTokens.has(token)) dropLateHead(transaction);
86
+ } else if (!transaction.committedTokens.has(token)) {
78
87
  transaction.committedTokens.add(token);
79
88
  transaction.committedRecords.push(transaction.recordsByToken.get(token));
80
89
  }
@@ -159,4 +168,4 @@ const createWebHeadMarkerStripper = (context)=>{
159
168
  }
160
169
  });
161
170
  };
162
- export { abortHeadRender, beginHeadRender, collectHeadRecord, collectHeadState, completeHeadRender, createHeadChunkProcessor, createWebHeadMarkerStripper, finishHeadRender, publishHeadRender };
171
+ export { LATE_HEAD_MESSAGE, abortHeadRender, beginHeadRender, collectHeadRecord, collectHeadState, completeHeadRender, createHeadChunkProcessor, createWebHeadMarkerStripper, finishHeadRender, publishHeadRender };
@@ -30,7 +30,7 @@ const toOrigin = (value)=>{
30
30
  if (!value) return;
31
31
  try {
32
32
  return new URL(value).origin;
33
- } catch (error) {
33
+ } catch {
34
34
  return;
35
35
  }
36
36
  };
@@ -58,7 +58,7 @@ const shouldRetryWithDefaults = (method, statusCode, error, retryableStatusCodes
58
58
  const shouldRetry = (retryOptions, context, retryableStatusCodes)=>{
59
59
  if ('function' == typeof retryOptions?.shouldRetry) try {
60
60
  return retryOptions.shouldRetry(context);
61
- } catch (error) {
61
+ } catch {
62
62
  return false;
63
63
  }
64
64
  return shouldRetryWithDefaults(context.method, context.statusCode, context.error, retryableStatusCodes);
@@ -75,7 +75,7 @@ const emitDegradedEvent = (transport, event)=>{
75
75
  if ('function' != typeof transport?.onDegraded) return;
76
76
  try {
77
77
  transport.onDegraded(event);
78
- } catch (error) {}
78
+ } catch {}
79
79
  };
80
80
  const withTimeout = async (promise, timeoutMs, signalBuilder)=>{
81
81
  if (!timeoutMs || timeoutMs <= 0) {
@@ -5,6 +5,7 @@ const MARKER_PREFIX = `<template ${MARKER_ATTRIBUTE}="`;
5
5
  const MARKER_SUFFIX = '"></template>';
6
6
  const RECORD_ID_WIDTH = 12;
7
7
  const MARKER_LENGTH = MARKER_PREFIX.length + 1 + 36 + RECORD_ID_WIDTH + MARKER_SUFFIX.length;
8
+ const LATE_HEAD_MESSAGE = "<Helmet> rendered in a boundary that completed after the shell; head tags dropped. Move it above the Suspense boundary or set server.ssrByEntries[entry].mode to 'string'.";
8
9
  const HEAD_STATE = Symbol('modern.rendererHead');
9
10
  const getState = (context)=>{
10
11
  const headContext = context;
@@ -25,7 +26,12 @@ const createToken = (transaction)=>{
25
26
  transaction.nextRecordId += 1;
26
27
  return token;
27
28
  };
28
- const beginHeadRender = (context)=>{
29
+ const dropLateHead = (transaction)=>{
30
+ if (transaction.lateHeadReported) return;
31
+ transaction.lateHeadReported = true;
32
+ transaction.reportLateHead(LATE_HEAD_MESSAGE);
33
+ };
34
+ const beginHeadRender = (context, reportLateHead)=>{
29
35
  const state = getState(context);
30
36
  state.transaction = {
31
37
  recordsByToken: new Map(),
@@ -34,7 +40,9 @@ const beginHeadRender = (context)=>{
34
40
  nextRecordId: 0,
35
41
  nonce: globalThis.crypto.randomUUID(),
36
42
  previousRecords: state.publishedRecords,
37
- sealed: false
43
+ sealed: false,
44
+ reportLateHead,
45
+ lateHeadReported: false
38
46
  };
39
47
  state.publish?.([]);
40
48
  };
@@ -45,7 +53,6 @@ const collectHeadRecord = (context, createRecord, publish)=>{
45
53
  const hadPublisher = void 0 !== state.publish;
46
54
  state.publish = (records)=>publish(records);
47
55
  if (!hadPublisher) state.publish([]);
48
- if (transaction.sealed) return null;
49
56
  const token = createToken(transaction);
50
57
  transaction.recordsByToken.set(token, createRecord());
51
58
  return {
@@ -75,7 +82,9 @@ const consumeMarkers = (context, html)=>{
75
82
  const token = html.slice(tokenStart, end);
76
83
  if (transaction.recordsByToken.has(token)) {
77
84
  stripped += html.slice(cursor, start);
78
- if (!transaction.sealed && !transaction.committedTokens.has(token)) {
85
+ if (transaction.sealed) {
86
+ if (!transaction.committedTokens.has(token)) dropLateHead(transaction);
87
+ } else if (!transaction.committedTokens.has(token)) {
79
88
  transaction.committedTokens.add(token);
80
89
  transaction.committedRecords.push(transaction.recordsByToken.get(token));
81
90
  }
@@ -160,4 +169,4 @@ const createWebHeadMarkerStripper = (context)=>{
160
169
  }
161
170
  });
162
171
  };
163
- export { abortHeadRender, beginHeadRender, collectHeadRecord, collectHeadState, completeHeadRender, createHeadChunkProcessor, createWebHeadMarkerStripper, finishHeadRender, publishHeadRender };
172
+ export { LATE_HEAD_MESSAGE, abortHeadRender, beginHeadRender, collectHeadRecord, collectHeadState, completeHeadRender, createHeadChunkProcessor, createWebHeadMarkerStripper, finishHeadRender, publishHeadRender };
@@ -31,7 +31,7 @@ const toOrigin = (value)=>{
31
31
  if (!value) return;
32
32
  try {
33
33
  return new URL(value).origin;
34
- } catch (error) {
34
+ } catch {
35
35
  return;
36
36
  }
37
37
  };
@@ -59,7 +59,7 @@ const shouldRetryWithDefaults = (method, statusCode, error, retryableStatusCodes
59
59
  const shouldRetry = (retryOptions, context, retryableStatusCodes)=>{
60
60
  if ('function' == typeof retryOptions?.shouldRetry) try {
61
61
  return retryOptions.shouldRetry(context);
62
- } catch (error) {
62
+ } catch {
63
63
  return false;
64
64
  }
65
65
  return shouldRetryWithDefaults(context.method, context.statusCode, context.error, retryableStatusCodes);
@@ -76,7 +76,7 @@ const emitDegradedEvent = (transport, event)=>{
76
76
  if ('function' != typeof transport?.onDegraded) return;
77
77
  try {
78
78
  transport.onDegraded(event);
79
- } catch (error) {}
79
+ } catch {}
80
80
  };
81
81
  const withTimeout = async (promise, timeoutMs, signalBuilder)=>{
82
82
  if (!timeoutMs || timeoutMs <= 0) {
@@ -63,7 +63,7 @@ export type HelmetCompatState<Element> = {
63
63
  style: HeadDatum<Element>;
64
64
  title: HeadDatum<Element>;
65
65
  };
66
- export declare const renderHeadMarker: <Element>(react: ElementFactory<Element>, marker: MarkerProps | null | undefined) => Element | null;
66
+ export declare const renderHeadMarker: <Element>(react: ElementFactory<Element>, marker: MarkerProps | undefined) => Element | null;
67
67
  export declare const markPriorityHeadRecord: (record: object, prioritize: boolean | undefined) => void;
68
68
  export declare const isPriorityHeadTag: (tag: object) => boolean;
69
69
  export declare const headTagsToComponents: <Element>(react: ElementFactory<Element>, tagName: string, tags: HeadTag[]) => Element[];
@@ -1,11 +1,13 @@
1
1
  declare const MARKER_ATTRIBUTE = "data-modern-helmet";
2
2
  export type RendererHeadMarkerProps = Record<typeof MARKER_ATTRIBUTE, string>;
3
3
  export * from './helmetCompat.js';
4
- export declare const beginHeadRender: (context: object) => void;
5
- export declare const collectHeadRecord: <RecordType>(context: object, createRecord: () => RecordType, publish: (records: RecordType[]) => void) => RendererHeadMarkerProps | null | undefined;
4
+ export type ReportLateHead = (message: string) => void;
5
+ export declare const LATE_HEAD_MESSAGE = "<Helmet> rendered in a boundary that completed after the shell; head tags dropped. Move it above the Suspense boundary or set server.ssrByEntries[entry].mode to 'string'.";
6
+ export declare const beginHeadRender: (context: object, reportLateHead: ReportLateHead) => void;
7
+ export declare const collectHeadRecord: <RecordType>(context: object, createRecord: () => RecordType, publish: (records: RecordType[]) => void) => RendererHeadMarkerProps | undefined;
6
8
  export declare const collectHeadState: <RecordType, StateType>(context: object, createRecord: () => RecordType, derive: (records: RecordType[]) => StateType, target: {
7
9
  helmet?: StateType;
8
- }) => RendererHeadMarkerProps | null | undefined;
10
+ }) => RendererHeadMarkerProps | undefined;
9
11
  export declare const publishHeadRender: (context: object) => void;
10
12
  export declare const finishHeadRender: (context: object) => void;
11
13
  export declare const completeHeadRender: (context: object, html: string) => string;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "directory": "packages/runtime/runtime-extensions"
12
12
  },
13
13
  "license": "MIT",
14
- "version": "3.9.0-ultramodern.16",
14
+ "version": "3.9.0-ultramodern.18",
15
15
  "engines": {
16
16
  "node": ">=26.7.0"
17
17
  },
@@ -70,14 +70,6 @@
70
70
  },
71
71
  "default": "./dist/esm/localisedLoaderIdentity.mjs"
72
72
  },
73
- "./react-context": {
74
- "types": "./dist/types/reactContext.d.ts",
75
- "node": {
76
- "import": "./dist/esm-node/reactContext.mjs",
77
- "require": "./dist/cjs/reactContext.js"
78
- },
79
- "default": "./dist/esm/reactContext.mjs"
80
- },
81
73
  "./context-extensions": {
82
74
  "types": "./dist/types/contextExtensions.d.ts",
83
75
  "node": {
@@ -222,10 +214,6 @@
222
214
  "./dist/types/localisedLoaderIdentity.d.ts",
223
215
  "./src/localisedLoaderIdentity.ts"
224
216
  ],
225
- "react-context": [
226
- "./dist/types/reactContext.d.ts",
227
- "./src/reactContext.ts"
228
- ],
229
217
  "context-extensions": [
230
218
  "./dist/types/contextExtensions.d.ts",
231
219
  "./src/contextExtensions.ts"
@@ -286,7 +274,7 @@
286
274
  },
287
275
  "devDependencies": {
288
276
  "@loadable/component": "5.16.7",
289
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.16",
277
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.18",
290
278
  "@rsbuild/core": "2.2.9",
291
279
  "@rslib/core": "1.0.2",
292
280
  "@scripts/rstest-config": "2.66.0",
@@ -315,8 +303,8 @@
315
303
  }
316
304
  },
317
305
  "dependencies": {
318
- "@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.16",
319
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.16",
306
+ "@modern-js/create-request": "npm:@bleedingdev/modern-js-create-request@3.9.0-ultramodern.18",
307
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.0-ultramodern.18",
320
308
  "react-helmet-async": "3.0.0"
321
309
  },
322
310
  "scripts": {
@@ -149,7 +149,7 @@ const createElementFactory = <Element>(react: ElementFactory<Element>) =>
149
149
 
150
150
  export const renderHeadMarker = <Element>(
151
151
  react: ElementFactory<Element>,
152
- marker: MarkerProps | null | undefined,
152
+ marker: MarkerProps | undefined,
153
153
  ): Element | null =>
154
154
  marker ? createElementFactory(react)('template', marker) : null;
155
155
 
@@ -9,6 +9,10 @@ export type RendererHeadMarkerProps = Record<typeof MARKER_ATTRIBUTE, string>;
9
9
  export * from './helmetCompat';
10
10
 
11
11
  type PublishRecords = (records: unknown[]) => void;
12
+ export type ReportLateHead = (message: string) => void;
13
+
14
+ export const LATE_HEAD_MESSAGE =
15
+ "<Helmet> rendered in a boundary that completed after the shell; head tags dropped. Move it above the Suspense boundary or set server.ssrByEntries[entry].mode to 'string'.";
12
16
 
13
17
  type HeadTransaction = {
14
18
  recordsByToken: Map<string, unknown>;
@@ -18,6 +22,8 @@ type HeadTransaction = {
18
22
  nonce: string;
19
23
  previousRecords: unknown[];
20
24
  sealed: boolean;
25
+ reportLateHead: ReportLateHead;
26
+ lateHeadReported: boolean;
21
27
  };
22
28
 
23
29
  type HeadState = {
@@ -48,7 +54,16 @@ const createToken = (transaction: HeadTransaction): string => {
48
54
  return token;
49
55
  };
50
56
 
51
- export const beginHeadRender = (context: object): void => {
57
+ const dropLateHead = (transaction: HeadTransaction): void => {
58
+ if (transaction.lateHeadReported) return;
59
+ transaction.lateHeadReported = true;
60
+ transaction.reportLateHead(LATE_HEAD_MESSAGE);
61
+ };
62
+
63
+ export const beginHeadRender = (
64
+ context: object,
65
+ reportLateHead: ReportLateHead,
66
+ ): void => {
52
67
  const state = getState(context);
53
68
  state.transaction = {
54
69
  recordsByToken: new Map(),
@@ -58,6 +73,8 @@ export const beginHeadRender = (context: object): void => {
58
73
  nonce: globalThis.crypto.randomUUID(),
59
74
  previousRecords: state.publishedRecords,
60
75
  sealed: false,
76
+ reportLateHead,
77
+ lateHeadReported: false,
61
78
  };
62
79
  state.publish?.([]);
63
80
  };
@@ -66,7 +83,7 @@ export const collectHeadRecord = <RecordType>(
66
83
  context: object,
67
84
  createRecord: () => RecordType,
68
85
  publish: (records: RecordType[]) => void,
69
- ): RendererHeadMarkerProps | null | undefined => {
86
+ ): RendererHeadMarkerProps | undefined => {
70
87
  const state = getState(context);
71
88
  const transaction = state.transaction;
72
89
  if (transaction === undefined) {
@@ -78,10 +95,8 @@ export const collectHeadRecord = <RecordType>(
78
95
  if (!hadPublisher) {
79
96
  state.publish([]);
80
97
  }
81
- if (transaction.sealed) {
82
- return null;
83
- }
84
-
98
+ // A Helmet rendered after the seal still gets a marker: it is dropped, and
99
+ // reported, only if its boundary actually commits to the output.
85
100
  const token = createToken(transaction);
86
101
  transaction.recordsByToken.set(token, createRecord());
87
102
  return { [MARKER_ATTRIBUTE]: token };
@@ -92,7 +107,7 @@ export const collectHeadState = <RecordType, StateType>(
92
107
  createRecord: () => RecordType,
93
108
  derive: (records: RecordType[]) => StateType,
94
109
  target: { helmet?: StateType },
95
- ): RendererHeadMarkerProps | null | undefined =>
110
+ ): RendererHeadMarkerProps | undefined =>
96
111
  collectHeadRecord(context, createRecord, records => {
97
112
  target.helmet = derive(records);
98
113
  });
@@ -123,7 +138,9 @@ const consumeMarkers = (context: object, html: string): string => {
123
138
  stripped += html.slice(cursor, end + MARKER_SUFFIX.length);
124
139
  } else {
125
140
  stripped += html.slice(cursor, start);
126
- if (!transaction.sealed && !transaction.committedTokens.has(token)) {
141
+ if (transaction.sealed) {
142
+ if (!transaction.committedTokens.has(token)) dropLateHead(transaction);
143
+ } else if (!transaction.committedTokens.has(token)) {
127
144
  transaction.committedTokens.add(token);
128
145
  transaction.committedRecords.push(
129
146
  transaction.recordsByToken.get(token),
@@ -92,7 +92,7 @@ export const toOrigin = (value?: string) => {
92
92
  }
93
93
  try {
94
94
  return new URL(value).origin;
95
- } catch (error) {
95
+ } catch {
96
96
  return undefined;
97
97
  }
98
98
  };
@@ -131,7 +131,7 @@ const shouldRetry = (
131
131
  if (typeof retryOptions?.shouldRetry === 'function') {
132
132
  try {
133
133
  return retryOptions.shouldRetry(context);
134
- } catch (error) {
134
+ } catch {
135
135
  return false;
136
136
  }
137
137
  }
@@ -176,7 +176,7 @@ const emitDegradedEvent = (
176
176
 
177
177
  try {
178
178
  transport.onDegraded(event);
179
- } catch (error) {
179
+ } catch {
180
180
  // best-effort telemetry only
181
181
  }
182
182
  };
@@ -10,7 +10,7 @@ const getHelmetData = (context: object) => getHelmetContext(context)?.helmet;
10
10
  const createServerContext = () => ({ isBrowser: false }) as any;
11
11
 
12
12
  const renderWithContext = (context: any, node: React.ReactNode) => {
13
- rendererHead.beginHeadRender(context);
13
+ rendererHead.beginHeadRender(context, () => {});
14
14
  try {
15
15
  const html = renderToString(
16
16
  wrapServerRoot(node, {
@@ -5,6 +5,8 @@ import {
5
5
  collectHeadRecord,
6
6
  completeHeadRender,
7
7
  createHeadChunkProcessor,
8
+ LATE_HEAD_MESSAGE,
9
+ publishHeadRender,
8
10
  } from '../src';
9
11
  import { createNodeHeadMarkerStripper, pipeNodeHeadStream } from '../src/node';
10
12
 
@@ -15,7 +17,7 @@ describe('renderer head transactions', () => {
15
17
  it('commits only markers observed in completed output', () => {
16
18
  const context = {};
17
19
  let published: string[] = [];
18
- beginHeadRender(context);
20
+ beginHeadRender(context, () => {});
19
21
  const committed = collectHeadRecord(
20
22
  context,
21
23
  () => 'committed',
@@ -35,9 +37,50 @@ describe('renderer head transactions', () => {
35
37
  expect(published).toEqual(['committed']);
36
38
  });
37
39
 
40
+ it('reports head records whose markers commit after the seal', () => {
41
+ const context = {};
42
+ const reports: string[] = [];
43
+ let published: string[] = [];
44
+ const publish = (records: string[]) => {
45
+ published = records;
46
+ };
47
+ beginHeadRender(context, message => reports.push(message));
48
+ const shell = collectHeadRecord(context, () => 'shell', publish)!;
49
+ // A boundary rendered before the seal but flushed after it.
50
+ const flushedLate = collectHeadRecord(context, () => 'flushed', publish)!;
51
+ const processor = createHeadChunkProcessor(context);
52
+ expect(processor.push(`a${marker(shell)}`)).toBe('a');
53
+ publishHeadRender(context);
54
+ // A boundary rendered after the seal.
55
+ const renderedLate = collectHeadRecord(context, () => 'rendered', publish)!;
56
+ // A retry that renders a Helmet and then suspends again never commits.
57
+ collectHeadRecord(context, () => 'discarded', publish);
58
+ expect(reports).toEqual([]);
59
+
60
+ expect(processor.push(`b${marker(flushedLate)}${marker(shell)}`)).toBe('b');
61
+ expect(processor.finish(`c${marker(renderedLate)}`)).toBe('c');
62
+
63
+ expect(published).toEqual(['shell']);
64
+ expect(reports).toEqual([LATE_HEAD_MESSAGE]);
65
+ });
66
+
67
+ it('does not report a late Helmet whose boundary never commits', () => {
68
+ const context = {};
69
+ const reports: string[] = [];
70
+ beginHeadRender(context, message => reports.push(message));
71
+ publishHeadRender(context);
72
+ collectHeadRecord(
73
+ context,
74
+ () => 'discarded',
75
+ () => {},
76
+ );
77
+ expect(completeHeadRender(context, 'done')).toBe('done');
78
+ expect(reports).toEqual([]);
79
+ });
80
+
38
81
  it('preserves user templates that are not current transaction markers', () => {
39
82
  const context = {};
40
- beginHeadRender(context);
83
+ beginHeadRender(context, () => {});
41
84
  const userTemplate =
42
85
  '<template data-modern-helmet="h00000000-0000-0000-0000-000000000000000000000000"></template>';
43
86
 
@@ -46,7 +89,7 @@ describe('renderer head transactions', () => {
46
89
 
47
90
  it('strips markers split across chunks without corrupting unicode', () => {
48
91
  const sampleContext = {};
49
- beginHeadRender(sampleContext);
92
+ beginHeadRender(sampleContext, () => {});
50
93
  const sampleProps = collectHeadRecord(
51
94
  sampleContext,
52
95
  () => 'head',
@@ -56,7 +99,7 @@ describe('renderer head transactions', () => {
56
99
 
57
100
  for (let split = 1; split < sampleHtml.length; split += 1) {
58
101
  const context = {};
59
- beginHeadRender(context);
102
+ beginHeadRender(context, () => {});
60
103
  const props = collectHeadRecord(
61
104
  context,
62
105
  () => 'head',
@@ -78,12 +121,12 @@ describe('renderer head transactions', () => {
78
121
  published = records;
79
122
  };
80
123
 
81
- beginHeadRender(context);
124
+ beginHeadRender(context, () => {});
82
125
  const previous = collectHeadRecord(context, () => 'previous', publish)!;
83
126
  completeHeadRender(context, marker(previous));
84
127
  expect(published).toEqual(['previous']);
85
128
 
86
- beginHeadRender(context);
129
+ beginHeadRender(context, () => {});
87
130
  expect(published).toEqual([]);
88
131
  collectHeadRecord(context, () => 'provisional', publish);
89
132
  abortHeadRender(context);
@@ -93,7 +136,7 @@ describe('renderer head transactions', () => {
93
136
  it('strips split markers from a Node stream', async () => {
94
137
  const context = {};
95
138
  let published: string[] = [];
96
- beginHeadRender(context);
139
+ beginHeadRender(context, () => {});
97
140
  const props = collectHeadRecord(
98
141
  context,
99
142
  () => 'node',
@@ -127,7 +170,7 @@ describe('renderer head transactions', () => {
127
170
  const prefix = Buffer.from('x'.repeat(9 * 1024));
128
171
  const unicodeTail = Buffer.from('č尾');
129
172
  const input = Buffer.concat([prefix, unicodeTail]);
130
- beginHeadRender(context);
173
+ beginHeadRender(context, () => {});
131
174
  const stripper = createNodeHeadMarkerStripper(context, terminalMarker);
132
175
  const output = new Promise<string>((resolve, reject) => {
133
176
  let rendered = '';
@@ -152,7 +195,7 @@ describe('renderer head transactions', () => {
152
195
  const terminalMarker = '<!-- shell stream end -->';
153
196
  const input = Buffer.from(`shell${terminalMarker}č尾`);
154
197
  const markerOffset = Buffer.byteLength('shell') + 7;
155
- beginHeadRender(context);
198
+ beginHeadRender(context, () => {});
156
199
  const stripper = createNodeHeadMarkerStripper(context, terminalMarker);
157
200
  const output = new Promise<string>((resolve, reject) => {
158
201
  let rendered = '';
@@ -62,10 +62,10 @@ describe('conserving web shell stream', () => {
62
62
  const publish = (records: string[]) => {
63
63
  published = records;
64
64
  };
65
- beginHeadRender(context);
65
+ beginHeadRender(context, () => {});
66
66
  const previous = collectHeadRecord(context, () => 'previous', publish)!;
67
67
  completeHeadRender(context, headMarker(previous));
68
- beginHeadRender(context);
68
+ beginHeadRender(context, () => {});
69
69
  collectHeadRecord(context, () => 'incomplete', publish);
70
70
  const bytes = encoder.encode(`α${'x'.repeat(9 * 1024)}尾`);
71
71
  const stream = createConservingWebShellStream(
@@ -1,45 +0,0 @@
1
- "use strict";
2
- var __webpack_require__ = {};
3
- (()=>{
4
- __webpack_require__.d = (exports1, getters, values)=>{
5
- var define = (defs, kind)=>{
6
- for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
- enumerable: true,
8
- [kind]: defs[key]
9
- });
10
- };
11
- define(getters, "get");
12
- define(values, "value");
13
- };
14
- })();
15
- (()=>{
16
- __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
- })();
18
- (()=>{
19
- __webpack_require__.r = (exports1)=>{
20
- if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
- value: 'Module'
22
- });
23
- Object.defineProperty(exports1, '__esModule', {
24
- value: true
25
- });
26
- };
27
- })();
28
- var __webpack_exports__ = {};
29
- __webpack_require__.r(__webpack_exports__);
30
- function getRuntimeReactContext(kind, create) {
31
- const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
32
- const contexts = globalThis;
33
- contexts[key] ??= create();
34
- return contexts[key];
35
- }
36
- __webpack_require__.d(__webpack_exports__, {
37
- getRuntimeReactContext: ()=>getRuntimeReactContext
38
- });
39
- exports.getRuntimeReactContext = __webpack_exports__.getRuntimeReactContext;
40
- for(var __rspack_i in __webpack_exports__)if (-1 === [
41
- "getRuntimeReactContext"
42
- ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
43
- Object.defineProperty(exports, '__esModule', {
44
- value: true
45
- });
@@ -1,7 +0,0 @@
1
- function getRuntimeReactContext(kind, create) {
2
- const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
3
- const contexts = globalThis;
4
- contexts[key] ??= create();
5
- return contexts[key];
6
- }
7
- export { getRuntimeReactContext };
@@ -1,8 +0,0 @@
1
- import "node:module";
2
- function getRuntimeReactContext(kind, create) {
3
- const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
4
- const contexts = globalThis;
5
- contexts[key] ??= create();
6
- return contexts[key];
7
- }
8
- export { getRuntimeReactContext };
@@ -1,6 +0,0 @@
1
- /**
2
- * Federation can bundle a runtime subpath separately from its shared root.
3
- * Share React context identity across those copies, leaving request values to
4
- * React's providers. Public and internal contexts retain separate identities.
5
- */
6
- export declare function getRuntimeReactContext<T>(kind: 'public' | 'internal', create: () => T): T;
@@ -1,16 +0,0 @@
1
- /**
2
- * Federation can bundle a runtime subpath separately from its shared root.
3
- * Share React context identity across those copies, leaving request values to
4
- * React's providers. Public and internal contexts retain separate identities.
5
- */
6
- export function getRuntimeReactContext<T>(
7
- kind: 'public' | 'internal',
8
- create: () => T,
9
- ): T {
10
- const key = Symbol.for(`@modern-js/runtime:react-context:v1:${kind}`);
11
- const contexts = globalThis as typeof globalThis & {
12
- [key: symbol]: unknown;
13
- };
14
- contexts[key] ??= create();
15
- return contexts[key] as T;
16
- }