@modern-js/runtime 3.9.2 → 3.9.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.
@@ -1,5 +1,6 @@
1
1
  import "node:module";
2
2
  import { PassThrough, Readable, Transform } from "stream";
3
+ import { StringDecoder } from "string_decoder";
3
4
  import { storage } from "@modern-js/runtime-utils/node";
4
5
  import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
5
6
  import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
@@ -7,6 +8,7 @@ import { RenderLevel } from "../../constants.mjs";
7
8
  import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
8
9
  import { getMonitors } from "../../context/monitors/index.mjs";
9
10
  import { enqueueFromEntries } from "./deferredScript.mjs";
11
+ import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
10
12
  import { ShellChunkStatus, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
11
13
  import { getTemplates } from "./template.mjs";
12
14
  const defaultExtender = {
@@ -55,7 +57,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
55
57
  entryName,
56
58
  styledComponentsStyleTags
57
59
  }).then(({ shellAfter, shellBefore })=>{
60
+ const decoder = new StringDecoder('utf8');
58
61
  const pendingScripts = [];
62
+ const outputState = {};
63
+ let deferredResolversComplete = Promise.resolve();
64
+ const writeReact = (content)=>{
65
+ if (!outputState.coordinator) throw new Error("Deferred script coordinator is not ready");
66
+ outputState.coordinator.writeReact(content);
67
+ };
59
68
  const body = new Transform({
60
69
  transform (chunk, _encoding, callback) {
61
70
  try {
@@ -67,17 +76,49 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
67
76
  const beforeMark = concatedChunk.slice(0, markerIndex);
68
77
  const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
69
78
  shellChunkStatus = ShellChunkStatus.FINISH;
70
- this.push(`${shellBefore}${beforeMark}${shellAfter}`);
71
- if (afterMark) this.push(afterMark);
72
- if (pendingScripts.length > 0) for (const s of pendingScripts)this.push(s);
79
+ writeReact(`${shellBefore}${beforeMark}${shellAfter}`);
80
+ if (afterMark) writeReact(afterMark);
81
+ if (outputState.coordinator) {
82
+ outputState.coordinator.markShellFinished();
83
+ for (const script of pendingScripts)outputState.coordinator.enqueueResolver(script);
84
+ pendingScripts.length = 0;
85
+ }
73
86
  }
74
- } else this.push(chunk);
87
+ } else {
88
+ const decodedChunk = decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
89
+ writeReact(decodedChunk);
90
+ }
75
91
  callback();
76
92
  } catch (e) {
77
93
  e instanceof Error ? callback(e) : callback(new Error('Received unknown error when streaming'));
78
94
  }
95
+ },
96
+ flush (callback) {
97
+ try {
98
+ writeReact(decoder.end());
99
+ } catch (error) {
100
+ callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
101
+ return;
102
+ }
103
+ deferredResolversComplete.then(()=>{
104
+ try {
105
+ outputState.coordinator?.finish();
106
+ callback();
107
+ } catch (error) {
108
+ callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
109
+ }
110
+ }, (error)=>{
111
+ callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
112
+ });
79
113
  }
80
114
  });
115
+ const coordinator = new DeferredScriptOutputCoordinator((content)=>{
116
+ body.push(content);
117
+ });
118
+ outputState.coordinator = coordinator;
119
+ body.once('close', ()=>{
120
+ coordinator.abort();
121
+ });
81
122
  const passThrough = new PassThrough();
82
123
  const stream = Readable.toWeb(body);
83
124
  resolve(stream);
@@ -85,23 +126,25 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
85
126
  extenders.forEach((extender)=>{
86
127
  if (extender.processStream) processedStream = extender.processStream(processedStream);
87
128
  });
88
- reactStreamingPipe(passThrough);
89
- processedStream.pipe(body);
90
129
  try {
91
130
  const storageContext = storage.useContext?.();
92
131
  const activeDeferreds = storageContext?.activeDeferreds;
132
+ const deferredScriptKeys = storageContext?.deferredScriptKeys;
93
133
  const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
94
134
  if (entries.length > 0) {
95
135
  const enqueueScript = (s)=>{
96
- if (shellChunkStatus === ShellChunkStatus.FINISH) body.write(s);
136
+ if (!outputState.coordinator) throw new Error("Deferred script coordinator is not ready");
137
+ if (shellChunkStatus === ShellChunkStatus.FINISH) outputState.coordinator.enqueueResolver(s);
97
138
  else pendingScripts.push(s);
98
139
  };
99
- enqueueFromEntries(entries, config.nonce, (s)=>enqueueScript(s));
140
+ deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, enqueueScript);
100
141
  }
101
142
  } catch (err) {
102
143
  const monitors = getMonitors();
103
144
  monitors.error("cannot inject router data script", err);
104
145
  }
146
+ reactStreamingPipe(passThrough);
147
+ processedStream.pipe(body);
105
148
  });
106
149
  },
107
150
  onShellError (error) {
@@ -4,6 +4,7 @@ import { storage } from "@modern-js/runtime-utils/node";
4
4
  import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
5
5
  import { RenderLevel } from "../../constants.mjs";
6
6
  import { enqueueFromEntries } from "./deferredScript.mjs";
7
+ import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
7
8
  import { ShellChunkStatus, encodeForWebStream, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
8
9
  import { getTemplates } from "./template.mjs";
9
10
  const createReadableStreamFromElement = async (request, rootElement, options)=>{
@@ -36,10 +37,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
36
37
  const { waitForAllReady } = resolveStreamingMode(request, forceStreamToString);
37
38
  if (waitForAllReady) await readableOriginal.allReady;
38
39
  const reader = readableOriginal.getReader();
40
+ let coordinator;
39
41
  const stream = new ReadableStream({
40
42
  start (controller) {
43
+ const decoder = new TextDecoder();
41
44
  const pendingScripts = [];
42
45
  let isClosed = false;
46
+ let deferredResolversComplete = Promise.resolve();
43
47
  const safeEnqueue = (chunk)=>{
44
48
  if (isClosed) return;
45
49
  try {
@@ -56,38 +60,55 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
56
60
  } catch {}
57
61
  }
58
62
  };
59
- const flushPendingScripts = ()=>{
60
- for (const s of pendingScripts)safeEnqueue(encodeForWebStream(s));
61
- pendingScripts.length = 0;
62
- };
63
- const enqueueScript = (script)=>{
64
- if (shellChunkStatus === ShellChunkStatus.FINISH) safeEnqueue(encodeForWebStream(script));
65
- else pendingScripts.push(script);
66
- };
63
+ coordinator = new DeferredScriptOutputCoordinator((content)=>{
64
+ safeEnqueue(encodeForWebStream(content));
65
+ });
67
66
  const storageContext = storage.useContext?.();
68
67
  const activeDeferreds = storageContext?.activeDeferreds;
68
+ const deferredScriptKeys = storageContext?.deferredScriptKeys;
69
69
  const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
70
- if (entries.length > 0) enqueueFromEntries(entries, config.nonce, enqueueScript);
70
+ if (entries.length > 0) deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, (script)=>{
71
+ if (!coordinator) throw new Error("Deferred script coordinator is not ready");
72
+ if (shellChunkStatus === ShellChunkStatus.FINISH) coordinator.enqueueResolver(script);
73
+ else pendingScripts.push(script);
74
+ });
71
75
  async function push() {
72
76
  try {
73
77
  const { done, value } = await reader.read();
74
- if (done) return void closeController();
78
+ if (done) {
79
+ const trailingText = decoder.decode();
80
+ if (trailingText) coordinator?.writeReact(trailingText);
81
+ await deferredResolversComplete;
82
+ coordinator?.finish();
83
+ closeController();
84
+ return;
85
+ }
75
86
  if (isClosed) return;
76
87
  if (shellChunkStatus !== ShellChunkStatus.FINISH) {
77
- chunkVec.push(new TextDecoder().decode(value));
88
+ chunkVec.push(decoder.decode(value, {
89
+ stream: true
90
+ }));
78
91
  const concatedChunk = chunkVec.join('');
79
92
  const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
80
93
  if (-1 !== markerIndex) {
81
94
  const beforeMark = concatedChunk.slice(0, markerIndex);
82
95
  const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
83
96
  shellChunkStatus = ShellChunkStatus.FINISH;
84
- safeEnqueue(encodeForWebStream(`${shellBefore}${beforeMark}${shellAfter}`));
85
- if (afterMark) safeEnqueue(encodeForWebStream(afterMark));
86
- flushPendingScripts();
97
+ coordinator?.writeReact(`${shellBefore}${beforeMark}${shellAfter}`);
98
+ if (afterMark) coordinator?.writeReact(afterMark);
99
+ coordinator?.markShellFinished();
100
+ for (const script of pendingScripts)coordinator?.enqueueResolver(script);
101
+ pendingScripts.length = 0;
87
102
  }
88
- } else safeEnqueue(value);
103
+ } else {
104
+ const decodedChunk = decoder.decode(value, {
105
+ stream: true
106
+ });
107
+ if (decodedChunk) coordinator?.writeReact(decodedChunk);
108
+ }
89
109
  if (!isClosed) push();
90
110
  } catch (error) {
111
+ coordinator?.abort();
91
112
  if (!isClosed) {
92
113
  isClosed = true;
93
114
  try {
@@ -99,6 +120,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
99
120
  push();
100
121
  },
101
122
  cancel (reason) {
123
+ coordinator?.abort();
102
124
  reader.cancel(reason).catch(()=>{});
103
125
  }
104
126
  });
@@ -41,23 +41,29 @@ function buildDeferredDataScript(nonce, args) {
41
41
  const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
42
42
  return `<script async${nonceAttr} data-fn-name="r" data-script-src="modern-run-router-data-fn" data-fn-args='${escaped}' suppressHydrationWarning>${runRouterDataFnStr}</script>`;
43
43
  }
44
- function enqueueFromEntries(entries, nonce, emit) {
44
+ function enqueueFromEntries(entries, deferredScriptKeys, nonce, emit) {
45
+ const pendingResolvers = [];
45
46
  entries.forEach(([routeId, value])=>{
46
47
  if (!isDeferredDataLike(value)) return;
47
- const pendingKeys = new Set(value.pendingKeys ?? []);
48
- pendingKeys.forEach((key)=>{
48
+ const serializedKeys = deferredScriptKeys ? deferredScriptKeys.get(routeId) ?? [] : value.pendingKeys ?? [];
49
+ serializedKeys.forEach((key)=>{
49
50
  const tracked = value.data?.[key];
50
- if (isPromiseLike(tracked)) tracked.then((val)=>emit(buildDeferredDataScript(nonce, [
51
+ if (isPromiseLike(tracked)) pendingResolvers.push(Promise.resolve(tracked).then((val)=>{
52
+ emit(buildDeferredDataScript(nonce, [
51
53
  routeId,
52
54
  key,
53
55
  val
54
- ])), (err)=>emit(buildDeferredDataScript(nonce, [
56
+ ]));
57
+ }, (err)=>{
58
+ emit(buildDeferredDataScript(nonce, [
55
59
  routeId,
56
60
  key,
57
61
  void 0,
58
62
  toErrorInfo(err)
59
- ])));
63
+ ]));
64
+ }));
60
65
  });
61
66
  });
67
+ return Promise.all(pendingResolvers).then(()=>void 0);
62
68
  }
63
69
  export { buildDeferredDataScript, enqueueFromEntries, escapeAttr, isDeferredDataLike, isPromiseLike, isRecord, toErrorInfo };
@@ -0,0 +1,333 @@
1
+ import "node:module";
2
+ import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
3
+ const VOID_ELEMENTS = new Set([
4
+ 'area',
5
+ 'base',
6
+ 'br',
7
+ 'col',
8
+ 'embed',
9
+ 'hr',
10
+ 'img',
11
+ 'input',
12
+ 'link',
13
+ 'meta',
14
+ 'param',
15
+ 'source',
16
+ 'track',
17
+ 'wbr'
18
+ ]);
19
+ const RAW_TEXT_ELEMENTS = new Set([
20
+ 'iframe',
21
+ 'noembed',
22
+ 'noframes',
23
+ "script",
24
+ 'style',
25
+ 'textarea',
26
+ 'title',
27
+ 'xmp'
28
+ ]);
29
+ const REACT_SEGMENT_ID_PATTERN = new RegExp(`^${SSR_HYDRATION_ID_PREFIX}S:[0-9a-f]+$`);
30
+ const TABLE_SEGMENT_CHILDREN = new Set([
31
+ 'tbody',
32
+ 'tr',
33
+ 'colgroup'
34
+ ]);
35
+ function isAsciiAlpha(char) {
36
+ return /[A-Za-z]/.test(char);
37
+ }
38
+ function isWhitespace(char) {
39
+ return /[\t\n\f\r ]/.test(char);
40
+ }
41
+ function getSuffix(value, pattern) {
42
+ const start = Math.max(0, value.length - pattern.length);
43
+ for(let index = start; index < value.length; index++){
44
+ const suffix = value.slice(index);
45
+ if (pattern.startsWith(suffix)) return suffix;
46
+ }
47
+ return '';
48
+ }
49
+ class DeferredScriptOutputCoordinator {
50
+ enqueueResolver(script) {
51
+ if (this.closed) return;
52
+ this.pendingResolvers.push(script);
53
+ this.flushResolvers();
54
+ }
55
+ markShellFinished() {
56
+ if (this.closed) return;
57
+ this.shellFinished = true;
58
+ this.flushResolvers();
59
+ }
60
+ writeReact(chunk) {
61
+ if (this.closed || !chunk) return;
62
+ this.flushResolvers();
63
+ let emittedUntil = 0;
64
+ for(let index = 0; index < chunk.length; index++){
65
+ const wasSafe = this.isSafeBoundary();
66
+ this.consume(chunk[index]);
67
+ if (!wasSafe && this.isSafeBoundary() && this.pendingResolvers.length) {
68
+ this.emit(chunk.slice(emittedUntil, index + 1));
69
+ emittedUntil = index + 1;
70
+ this.flushResolvers();
71
+ }
72
+ }
73
+ if (emittedUntil < chunk.length) this.emit(chunk.slice(emittedUntil));
74
+ }
75
+ finish() {
76
+ if (this.closed) return;
77
+ this.flushResolvers();
78
+ this.closed = true;
79
+ if (this.pendingResolvers.length > 0) throw new Error("Cannot emit deferred resolver scripts because the React stream ended outside a safe HTML boundary.");
80
+ }
81
+ abort() {
82
+ this.closed = true;
83
+ this.pendingResolvers.length = 0;
84
+ }
85
+ isSafeBoundary() {
86
+ return this.shellFinished && 'data' === this.state && 0 === this.openElements.length && !this.awaitingContinuationProtocol;
87
+ }
88
+ flushResolvers() {
89
+ if (!this.isSafeBoundary() || 0 === this.pendingResolvers.length) return;
90
+ while(this.pendingResolvers.length > 0){
91
+ const resolver = this.pendingResolvers.shift();
92
+ if (resolver) this.emit(resolver);
93
+ }
94
+ }
95
+ consume(char) {
96
+ switch(this.state){
97
+ case 'data':
98
+ if ('<' === char) this.state = 'tag-open';
99
+ return;
100
+ case 'tag-open':
101
+ if ('/' === char) {
102
+ this.currentTagName = '';
103
+ this.state = 'end-tag-name';
104
+ } else if ('!' === char) {
105
+ this.declarationPrefix = '';
106
+ this.state = 'markup-declaration';
107
+ } else if ('?' === char) this.state = 'bogus-comment';
108
+ else if (isAsciiAlpha(char)) {
109
+ this.startTag(char);
110
+ this.state = 'start-tag-name';
111
+ } else this.state = 'data';
112
+ return;
113
+ case 'start-tag-name':
114
+ if (isWhitespace(char)) this.state = 'before-attribute-name';
115
+ else if ('/' === char) this.state = 'self-closing-start-tag';
116
+ else if ('>' === char) this.completeStartTag(false);
117
+ else this.currentTagName += char.toLowerCase();
118
+ return;
119
+ case 'before-attribute-name':
120
+ if (isWhitespace(char)) return;
121
+ if ('/' === char) this.state = 'self-closing-start-tag';
122
+ else if ('>' === char) this.completeStartTag(false);
123
+ else {
124
+ this.startAttribute(char);
125
+ this.state = 'attribute-name';
126
+ }
127
+ return;
128
+ case 'attribute-name':
129
+ if (isWhitespace(char)) this.state = 'after-attribute-name';
130
+ else if ('=' === char) {
131
+ this.currentAttributeHasValue = true;
132
+ this.state = 'before-attribute-value';
133
+ } else if ('/' === char) {
134
+ this.completeAttribute();
135
+ this.state = 'self-closing-start-tag';
136
+ } else if ('>' === char) this.completeStartTag(false);
137
+ else this.currentAttributeName += char.toLowerCase();
138
+ return;
139
+ case 'after-attribute-name':
140
+ if (isWhitespace(char)) return;
141
+ if ('=' === char) {
142
+ this.currentAttributeHasValue = true;
143
+ this.state = 'before-attribute-value';
144
+ } else if ('/' === char) {
145
+ this.completeAttribute();
146
+ this.state = 'self-closing-start-tag';
147
+ } else if ('>' === char) this.completeStartTag(false);
148
+ else {
149
+ this.completeAttribute();
150
+ this.startAttribute(char);
151
+ this.state = 'attribute-name';
152
+ }
153
+ return;
154
+ case 'before-attribute-value':
155
+ if (isWhitespace(char)) return;
156
+ if ('"' === char) this.state = 'attribute-value-double-quoted';
157
+ else if ("'" === char) this.state = 'attribute-value-single-quoted';
158
+ else if ('>' === char) this.completeStartTag(false);
159
+ else {
160
+ this.currentAttributeValue += char;
161
+ this.state = 'attribute-value-unquoted';
162
+ }
163
+ return;
164
+ case 'attribute-value-double-quoted':
165
+ if ('"' === char) {
166
+ this.completeAttribute();
167
+ this.state = 'before-attribute-name';
168
+ } else this.currentAttributeValue += char;
169
+ return;
170
+ case 'attribute-value-single-quoted':
171
+ if ("'" === char) {
172
+ this.completeAttribute();
173
+ this.state = 'before-attribute-name';
174
+ } else this.currentAttributeValue += char;
175
+ return;
176
+ case 'attribute-value-unquoted':
177
+ if (isWhitespace(char)) {
178
+ this.completeAttribute();
179
+ this.state = 'before-attribute-name';
180
+ } else if ('>' === char) this.completeStartTag(false);
181
+ else this.currentAttributeValue += char;
182
+ return;
183
+ case 'self-closing-start-tag':
184
+ if ('>' === char) this.completeStartTag(true);
185
+ else if (!isWhitespace(char)) {
186
+ this.startAttribute(char);
187
+ this.state = 'attribute-name';
188
+ }
189
+ return;
190
+ case 'end-tag-name':
191
+ if (isWhitespace(char)) this.state = 'after-end-tag-name';
192
+ else if ('>' === char) this.completeEndTag();
193
+ else this.currentTagName += char.toLowerCase();
194
+ return;
195
+ case 'after-end-tag-name':
196
+ if ('>' === char) this.completeEndTag();
197
+ return;
198
+ case 'markup-declaration':
199
+ if (this.declarationPrefix.length < 2 && '-' === char) {
200
+ this.declarationPrefix += char;
201
+ if ('--' === this.declarationPrefix) {
202
+ this.commentTail = '';
203
+ this.state = 'comment';
204
+ }
205
+ } else this.state = '>' === char ? 'data' : 'declaration';
206
+ return;
207
+ case 'declaration':
208
+ if ('>' === char) this.state = 'data';
209
+ return;
210
+ case 'comment':
211
+ this.commentTail = `${this.commentTail}${char}`.slice(-3);
212
+ if ('-->' === this.commentTail) this.state = 'data';
213
+ return;
214
+ case 'bogus-comment':
215
+ if ('>' === char) this.state = 'data';
216
+ return;
217
+ case 'raw-text':
218
+ this.consumeRawText(char);
219
+ return;
220
+ case 'raw-end-tag':
221
+ if ('>' === char) this.completeRawTextEndTag();
222
+ else if (!isWhitespace(char)) {
223
+ this.state = 'raw-text';
224
+ this.rawTextSearch = getSuffix(`${this.rawTextSearch}${char.toLowerCase()}`, `</${this.rawTextElement}`);
225
+ }
226
+ }
227
+ }
228
+ startTag(char) {
229
+ this.currentTagName = char.toLowerCase();
230
+ this.currentAttributes = new Map();
231
+ this.currentAttributeName = '';
232
+ this.currentAttributeValue = '';
233
+ this.currentAttributeHasValue = false;
234
+ }
235
+ startAttribute(char) {
236
+ this.currentAttributeName = char.toLowerCase();
237
+ this.currentAttributeValue = '';
238
+ this.currentAttributeHasValue = false;
239
+ }
240
+ completeAttribute() {
241
+ if (!this.currentAttributeName) return;
242
+ this.currentAttributes.set(this.currentAttributeName, this.currentAttributeHasValue ? this.currentAttributeValue : true);
243
+ this.currentAttributeName = '';
244
+ this.currentAttributeValue = '';
245
+ this.currentAttributeHasValue = false;
246
+ }
247
+ completeStartTag(selfClosing) {
248
+ this.completeAttribute();
249
+ const tagName = this.currentTagName;
250
+ const id = this.currentAttributes.get('id');
251
+ const hasReactSegmentId = 'string' == typeof id && REACT_SEGMENT_ID_PATTERN.test(id);
252
+ const hasHiddenAttribute = this.currentAttributes.has('hidden');
253
+ const isHiddenSegmentTable = 'table' === tagName && hasHiddenAttribute && !hasReactSegmentId;
254
+ const isTableSegmentChild = hasReactSegmentId && TABLE_SEGMENT_CHILDREN.has(tagName) && this.openElements[this.openElements.length - 1]?.isSegmentTableWrapper === true;
255
+ const isContinuationContainer = hasReactSegmentId && ('div' === tagName && hasHiddenAttribute || ('svg' === tagName || 'math' === tagName) && void 0 !== id && 'true' === this.currentAttributes.get('aria-hidden') && 'display:none' === this.currentAttributes.get('style') || 'table' === tagName && hasHiddenAttribute || isTableSegmentChild);
256
+ this.continuationProtocolScript = "script" === tagName && this.awaitingContinuationProtocol;
257
+ this.continuationProtocolTail = '';
258
+ this.hasContinuationProtocolCall = false;
259
+ if (!selfClosing && !VOID_ELEMENTS.has(tagName)) this.openElements.push({
260
+ tagName,
261
+ isContinuationContainer,
262
+ isSegmentTableWrapper: isHiddenSegmentTable
263
+ });
264
+ this.currentTagName = '';
265
+ this.currentAttributes = new Map();
266
+ this.currentAttributeName = '';
267
+ this.currentAttributeValue = '';
268
+ this.currentAttributeHasValue = false;
269
+ if (!selfClosing && RAW_TEXT_ELEMENTS.has(tagName)) {
270
+ this.rawTextElement = tagName;
271
+ this.rawTextSearch = '';
272
+ this.state = 'raw-text';
273
+ } else this.state = 'data';
274
+ }
275
+ completeEndTag() {
276
+ this.closeOpenElement(this.currentTagName);
277
+ this.currentTagName = '';
278
+ this.state = 'data';
279
+ }
280
+ consumeRawText(char) {
281
+ const rawTextElement = this.rawTextElement;
282
+ if (!rawTextElement) {
283
+ this.state = 'data';
284
+ return;
285
+ }
286
+ const endTagPrefix = `</${rawTextElement}`;
287
+ if (this.continuationProtocolScript) {
288
+ this.continuationProtocolTail = `${this.continuationProtocolTail}${char}`.slice(-4);
289
+ if (/\$(?:RC|RS|RX|RB|RR)\b/.test(this.continuationProtocolTail)) this.hasContinuationProtocolCall = true;
290
+ }
291
+ this.rawTextSearch = getSuffix(`${this.rawTextSearch}${char.toLowerCase()}`, endTagPrefix);
292
+ if (this.rawTextSearch === endTagPrefix) this.state = 'raw-end-tag';
293
+ }
294
+ completeRawTextEndTag() {
295
+ const rawTextElement = this.rawTextElement;
296
+ if (rawTextElement) this.closeOpenElement(rawTextElement);
297
+ if ("script" === rawTextElement && this.continuationProtocolScript && this.hasContinuationProtocolCall) this.awaitingContinuationProtocol = false;
298
+ this.rawTextElement = void 0;
299
+ this.rawTextSearch = '';
300
+ this.continuationProtocolScript = false;
301
+ this.continuationProtocolTail = '';
302
+ this.hasContinuationProtocolCall = false;
303
+ this.state = 'data';
304
+ }
305
+ closeOpenElement(tagName) {
306
+ const matchingIndex = this.openElements.map((element)=>element.tagName).lastIndexOf(tagName);
307
+ if (-1 === matchingIndex) return;
308
+ const closedElements = this.openElements.splice(matchingIndex);
309
+ const closedContinuation = closedElements.some((element)=>element.isContinuationContainer);
310
+ if (closedContinuation) this.awaitingContinuationProtocol = true;
311
+ }
312
+ constructor(emit){
313
+ this.emit = emit;
314
+ this.pendingResolvers = [];
315
+ this.openElements = [];
316
+ this.shellFinished = false;
317
+ this.closed = false;
318
+ this.state = 'data';
319
+ this.currentTagName = '';
320
+ this.currentAttributes = new Map();
321
+ this.currentAttributeName = '';
322
+ this.currentAttributeValue = '';
323
+ this.currentAttributeHasValue = false;
324
+ this.declarationPrefix = '';
325
+ this.commentTail = '';
326
+ this.rawTextSearch = '';
327
+ this.continuationProtocolScript = false;
328
+ this.continuationProtocolTail = '';
329
+ this.hasContinuationProtocolCall = false;
330
+ this.awaitingContinuationProtocol = false;
331
+ }
332
+ }
333
+ export { DeferredScriptOutputCoordinator };
@@ -14,7 +14,8 @@ const DeferredDataScripts = (props)=>{
14
14
  }, []);
15
15
  const deferredScripts = useMemo(()=>{
16
16
  if (!staticContext) return null;
17
- const activeDeferreds = storage.useContext().activeDeferreds;
17
+ const requestStorage = storage.useContext();
18
+ const activeDeferreds = requestStorage.activeDeferreds;
18
19
  const _ROUTER_DATA = {
19
20
  loaderData: staticContext.loaderData,
20
21
  errors: serializeErrors(staticContext.errors)
@@ -27,6 +28,7 @@ const DeferredDataScripts = (props)=>{
27
28
  const deferredDataScripts = [];
28
29
  const initialScripts = Array.from(activeDeferreds.entries()).map(([routeId, deferredData])=>{
29
30
  const pendingKeys = new Set(deferredData.pendingKeys);
31
+ requestStorage.deferredScriptKeys?.set(routeId, Array.from(pendingKeys));
30
32
  const { deferredKeys } = deferredData;
31
33
  const deferredKeyPromiseManifests = deferredKeys.map((key)=>{
32
34
  if (pendingKeys.has(key)) return {
@@ -12,4 +12,4 @@ export declare function toErrorInfo(error: unknown): {
12
12
  stack?: string;
13
13
  };
14
14
  export declare function buildDeferredDataScript(nonce: string | undefined, args: unknown[]): string;
15
- export declare function enqueueFromEntries(entries: Array<[string, unknown]>, nonce: string | undefined, emit: (script: string) => void): void;
15
+ export declare function enqueueFromEntries(entries: Array<[string, unknown]>, deferredScriptKeys: Map<string, string[]> | undefined, nonce: string | undefined, emit: (script: string) => void): Promise<void>;