@modern-js/runtime 3.9.1 → 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.
- package/dist/cjs/cli/template.server.js +1 -2
- package/dist/cjs/core/browser/hydrate.js +13 -11
- package/dist/cjs/core/server/requestHandler.js +2 -0
- package/dist/cjs/core/server/stream/beforeTemplate.js +3 -3
- package/dist/cjs/core/server/stream/createReadableStream.js +51 -8
- package/dist/cjs/core/server/stream/createReadableStream.worker.js +37 -15
- package/dist/cjs/core/server/stream/deferredScript.js +12 -6
- package/dist/cjs/core/server/stream/deferredScriptOutputCoordinator.js +370 -0
- package/dist/cjs/core/server/string/loadable.js +4 -4
- package/dist/cjs/router/cli/code/nestedRoutes.js +1 -2
- package/dist/cjs/router/cli/code/templates.js +1 -1
- package/dist/cjs/router/cli/code/utils.js +2 -2
- package/dist/cjs/router/runtime/DeferredDataScripts.node.js +13 -10
- package/dist/cjs/router/runtime/plugin.node.js +1 -1
- package/dist/esm/cli/template.server.mjs +1 -2
- package/dist/esm/core/browser/hydrate.mjs +13 -11
- package/dist/esm/core/server/requestHandler.mjs +2 -0
- package/dist/esm/core/server/stream/beforeTemplate.mjs +3 -3
- package/dist/esm/core/server/stream/createReadableStream.mjs +51 -8
- package/dist/esm/core/server/stream/createReadableStream.worker.mjs +37 -15
- package/dist/esm/core/server/stream/deferredScript.mjs +12 -6
- package/dist/esm/core/server/stream/deferredScriptOutputCoordinator.mjs +332 -0
- package/dist/esm/core/server/string/loadable.mjs +4 -4
- package/dist/esm/router/cli/code/nestedRoutes.mjs +1 -2
- package/dist/esm/router/cli/code/templates.mjs +1 -1
- package/dist/esm/router/cli/code/utils.mjs +2 -2
- package/dist/esm/router/runtime/DeferredDataScripts.node.mjs +13 -10
- package/dist/esm/router/runtime/plugin.node.mjs +1 -1
- package/dist/esm-node/cli/template.server.mjs +1 -2
- package/dist/esm-node/core/browser/hydrate.mjs +13 -11
- package/dist/esm-node/core/server/requestHandler.mjs +2 -0
- package/dist/esm-node/core/server/stream/beforeTemplate.mjs +3 -3
- package/dist/esm-node/core/server/stream/createReadableStream.mjs +51 -8
- package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +37 -15
- package/dist/esm-node/core/server/stream/deferredScript.mjs +12 -6
- package/dist/esm-node/core/server/stream/deferredScriptOutputCoordinator.mjs +333 -0
- package/dist/esm-node/core/server/string/loadable.mjs +4 -4
- package/dist/esm-node/router/cli/code/nestedRoutes.mjs +1 -2
- package/dist/esm-node/router/cli/code/templates.mjs +1 -1
- package/dist/esm-node/router/cli/code/utils.mjs +2 -2
- package/dist/esm-node/router/runtime/DeferredDataScripts.node.mjs +13 -10
- package/dist/esm-node/router/runtime/plugin.node.mjs +1 -1
- package/dist/types/core/server/stream/deferredScript.d.ts +1 -1
- package/dist/types/core/server/stream/deferredScriptOutputCoordinator.d.ts +72 -0
- package/package.json +12 -12
|
@@ -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 };
|
|
@@ -11,8 +11,8 @@ const extname = (uri)=>{
|
|
|
11
11
|
const generateChunks = (chunks, ext)=>chunks.filter((chunk)=>Boolean(chunk.url)).filter((chunk)=>extname(chunk.url).slice(1) === ext);
|
|
12
12
|
const checkIsInline = (chunk, enableInline)=>{
|
|
13
13
|
if ('production' !== process.env.NODE_ENV) return false;
|
|
14
|
-
if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
|
|
15
|
-
return Boolean(enableInline);
|
|
14
|
+
else if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
|
|
15
|
+
else return Boolean(enableInline);
|
|
16
16
|
};
|
|
17
17
|
const readAsset = async (chunk)=>{
|
|
18
18
|
const fs = await import("fs/promises");
|
|
@@ -80,7 +80,7 @@ class LoadableCollector {
|
|
|
80
80
|
const scripts = await Promise.all(chunks.filter((chunk)=>!existedScript.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
81
81
|
const script = `<script${attributes} src="${chunk.url}"></script>`;
|
|
82
82
|
if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<script>${content}</script>`).catch((_)=>script);
|
|
83
|
-
return script;
|
|
83
|
+
else return script;
|
|
84
84
|
}));
|
|
85
85
|
chunkSet.jsChunk += scripts.filter((script)=>Boolean(script)).join('');
|
|
86
86
|
}
|
|
@@ -91,7 +91,7 @@ class LoadableCollector {
|
|
|
91
91
|
const css = await Promise.all(chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
92
92
|
const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
|
|
93
93
|
if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
|
|
94
|
-
return link;
|
|
94
|
+
else return link;
|
|
95
95
|
}));
|
|
96
96
|
chunkSet.cssChunk += css.filter((css)=>Boolean(css)).join('');
|
|
97
97
|
}
|
|
@@ -43,8 +43,7 @@ const optimizeRoute = (routeTree)=>{
|
|
|
43
43
|
children: optimizedChildren
|
|
44
44
|
}
|
|
45
45
|
];
|
|
46
|
-
}
|
|
47
|
-
{
|
|
46
|
+
} else {
|
|
48
47
|
const newRoutes = children.map((child)=>{
|
|
49
48
|
const routePath = routeTree.path ? `${routeTree.path}${child.path ? `/${child.path}` : ''}` : child.path || '';
|
|
50
49
|
const newRoute = {
|
|
@@ -11,7 +11,7 @@ const walkDirectory = (dir)=>fs.readdirSync(dir).reduce((previous, filename)=>{
|
|
|
11
11
|
...previous,
|
|
12
12
|
...walkDirectory(filePath)
|
|
13
13
|
];
|
|
14
|
-
return [
|
|
14
|
+
else return [
|
|
15
15
|
...previous,
|
|
16
16
|
filePath
|
|
17
17
|
];
|
|
@@ -28,7 +28,7 @@ const isPageComponentFile = (filePath)=>{
|
|
|
28
28
|
};
|
|
29
29
|
const replaceWithAlias = (base, filePath, alias)=>{
|
|
30
30
|
if (filePath.includes(base)) return normalizeToPosixPath(path.join(alias, path.relative(base, filePath)));
|
|
31
|
-
return filePath;
|
|
31
|
+
else return filePath;
|
|
32
32
|
};
|
|
33
33
|
const parseModule = async ({ source, filename })=>{
|
|
34
34
|
let content = source;
|
|
@@ -14,7 +14,8 @@ const DeferredDataScripts = (props)=>{
|
|
|
14
14
|
}, []);
|
|
15
15
|
const deferredScripts = useMemo(()=>{
|
|
16
16
|
if (!staticContext) return null;
|
|
17
|
-
const
|
|
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 {
|
|
@@ -37,7 +39,7 @@ const DeferredDataScripts = (props)=>{
|
|
|
37
39
|
serializeJson(key)
|
|
38
40
|
]
|
|
39
41
|
};
|
|
40
|
-
{
|
|
42
|
+
else {
|
|
41
43
|
const trackedPromise = deferredData.data[key];
|
|
42
44
|
if (void 0 !== trackedPromise._error) {
|
|
43
45
|
const error = {
|
|
@@ -52,15 +54,16 @@ const DeferredDataScripts = (props)=>{
|
|
|
52
54
|
serializeJson(error)
|
|
53
55
|
]
|
|
54
56
|
};
|
|
57
|
+
} else {
|
|
58
|
+
if (void 0 === trackedPromise._data) throw new Error(`The deferred data for ${key} was not resolved, did you forget to return data from a deferred promise`);
|
|
59
|
+
return {
|
|
60
|
+
key,
|
|
61
|
+
routerDataFnName: 'p',
|
|
62
|
+
routerDataFnArgs: [
|
|
63
|
+
serializeJson(trackedPromise._data)
|
|
64
|
+
]
|
|
65
|
+
};
|
|
55
66
|
}
|
|
56
|
-
if (void 0 === trackedPromise._data) throw new Error(`The deferred data for ${key} was not resolved, did you forget to return data from a deferred promise`);
|
|
57
|
-
return {
|
|
58
|
-
key,
|
|
59
|
-
routerDataFnName: 'p',
|
|
60
|
-
routerDataFnArgs: [
|
|
61
|
-
serializeJson(trackedPromise._data)
|
|
62
|
-
]
|
|
63
|
-
};
|
|
64
67
|
}
|
|
65
68
|
});
|
|
66
69
|
return {
|
|
@@ -107,7 +107,7 @@ const routerPlugin = (userConfig = {})=>({
|
|
|
107
107
|
}) : /*#__PURE__*/ jsx(RSCStaticRouter, {
|
|
108
108
|
basename: basename
|
|
109
109
|
});
|
|
110
|
-
{
|
|
110
|
+
else {
|
|
111
111
|
const routerWrapper = /*#__PURE__*/ jsxs(Fragment, {
|
|
112
112
|
children: [
|
|
113
113
|
/*#__PURE__*/ jsx(StaticRouterProvider, {
|
|
@@ -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>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes the router's deferred-data resolver scripts with React's Fizz
|
|
3
|
+
* output without rewriting either producer's bytes.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* - A resolver script settles the client-side Promise created for a
|
|
7
|
+
* `defer()` key. It must be sent when that individual server Promise
|
|
8
|
+
* settles; waiting for the whole React stream would unnecessarily delay
|
|
9
|
+
* hydration and interactivity behind unrelated Suspense boundaries.
|
|
10
|
+
* - React and the resolver callbacks are independent stream producers. A
|
|
11
|
+
* direct `write()` can therefore land in a partial HTML tag, a hidden Fizz
|
|
12
|
+
* segment container, or between that container and its continuation
|
|
13
|
+
* instruction.
|
|
14
|
+
* - React's public streaming callbacks expose shell/all-ready lifecycle
|
|
15
|
+
* events, not a safe byte-level insertion point. React chunk sizes and
|
|
16
|
+
* `progressiveChunkSize` are buffering heuristics, not HTML or Fizz
|
|
17
|
+
* boundaries.
|
|
18
|
+
*
|
|
19
|
+
* The coordinator deliberately recognizes the Fizz continuation containers
|
|
20
|
+
* and instructions used by the supported React stream format. This is a
|
|
21
|
+
* compatibility boundary: when the supported React/Fizz output protocol is
|
|
22
|
+
* changed, update this recognizer and its Node, Edge, HTML, SVG, MathML, and
|
|
23
|
+
* table continuation tests together. It is not intended to be a general HTML
|
|
24
|
+
* rewriter.
|
|
25
|
+
*
|
|
26
|
+
* Shell completion only makes a resolver eligible to send. The resolver is
|
|
27
|
+
* emitted after a root-level HTML boundary and, for a continuation segment,
|
|
28
|
+
* after React's matching continuation instruction.
|
|
29
|
+
*/
|
|
30
|
+
export declare class DeferredScriptOutputCoordinator {
|
|
31
|
+
private readonly emit;
|
|
32
|
+
private readonly pendingResolvers;
|
|
33
|
+
private readonly openElements;
|
|
34
|
+
private shellFinished;
|
|
35
|
+
private closed;
|
|
36
|
+
private state;
|
|
37
|
+
private currentTagName;
|
|
38
|
+
private currentAttributes;
|
|
39
|
+
private currentAttributeName;
|
|
40
|
+
private currentAttributeValue;
|
|
41
|
+
private currentAttributeHasValue;
|
|
42
|
+
private declarationPrefix;
|
|
43
|
+
private commentTail;
|
|
44
|
+
private rawTextElement;
|
|
45
|
+
private rawTextSearch;
|
|
46
|
+
private continuationProtocolScript;
|
|
47
|
+
private continuationProtocolTail;
|
|
48
|
+
private hasContinuationProtocolCall;
|
|
49
|
+
private awaitingContinuationProtocol;
|
|
50
|
+
constructor(emit: (chunk: string) => void);
|
|
51
|
+
enqueueResolver(script: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Keep this separate from the ShellChunkStatus assignment. The caller sets
|
|
54
|
+
* FINISH at the existing marker position, then calls this after the marker
|
|
55
|
+
* chunk's afterMark bytes have been observed.
|
|
56
|
+
*/
|
|
57
|
+
markShellFinished(): void;
|
|
58
|
+
writeReact(chunk: string): void;
|
|
59
|
+
finish(): void;
|
|
60
|
+
abort(): void;
|
|
61
|
+
private isSafeBoundary;
|
|
62
|
+
private flushResolvers;
|
|
63
|
+
private consume;
|
|
64
|
+
private startTag;
|
|
65
|
+
private startAttribute;
|
|
66
|
+
private completeAttribute;
|
|
67
|
+
private completeStartTag;
|
|
68
|
+
private completeEndTag;
|
|
69
|
+
private consumeRawText;
|
|
70
|
+
private completeRawTextEndTag;
|
|
71
|
+
private closeOpenElement;
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"modern",
|
|
16
16
|
"modern.js"
|
|
17
17
|
],
|
|
18
|
-
"version": "3.9.
|
|
18
|
+
"version": "3.9.3",
|
|
19
19
|
"engines": {
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
@@ -215,12 +215,12 @@
|
|
|
215
215
|
"isbot": "3.8.0",
|
|
216
216
|
"react-helmet": "^6.1.0",
|
|
217
217
|
"react-is": "^18.3.1",
|
|
218
|
-
"@modern-js/plugin": "3.9.
|
|
219
|
-
"@modern-js/
|
|
220
|
-
"@modern-js/
|
|
221
|
-
"@modern-js/
|
|
222
|
-
"@modern-js/
|
|
223
|
-
"@modern-js/utils": "3.9.
|
|
218
|
+
"@modern-js/plugin": "3.9.3",
|
|
219
|
+
"@modern-js/plugin-data-loader": "3.9.3",
|
|
220
|
+
"@modern-js/render": "3.9.3",
|
|
221
|
+
"@modern-js/runtime-utils": "3.9.3",
|
|
222
|
+
"@modern-js/types": "3.9.3",
|
|
223
|
+
"@modern-js/utils": "3.9.3"
|
|
224
224
|
},
|
|
225
225
|
"peerDependencies": {
|
|
226
226
|
"react": ">=18.3.1",
|
|
@@ -228,10 +228,10 @@
|
|
|
228
228
|
},
|
|
229
229
|
"devDependencies": {
|
|
230
230
|
"@remix-run/web-fetch": "^4.1.3",
|
|
231
|
-
"@rsbuild/core": "2.2.
|
|
232
|
-
"@rslib/core": "1.0.
|
|
233
|
-
"@testing-library/dom": "^10.4.
|
|
234
|
-
"@testing-library/react": "^16.3.
|
|
231
|
+
"@rsbuild/core": "2.2.9",
|
|
232
|
+
"@rslib/core": "1.0.1",
|
|
233
|
+
"@testing-library/dom": "^10.4.2",
|
|
234
|
+
"@testing-library/react": "^16.3.3",
|
|
235
235
|
"@types/cookie": "0.6.0",
|
|
236
236
|
"@types/invariant": "^2.2.37",
|
|
237
237
|
"@types/loadable__server": "5.12.11",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"react-dom": "^19.2.8",
|
|
241
241
|
"ts-node": "^10.9.2",
|
|
242
242
|
"typescript": "^5",
|
|
243
|
-
"@modern-js/app-tools": "3.9.
|
|
243
|
+
"@modern-js/app-tools": "3.9.3",
|
|
244
244
|
"@modern-js/rslib": "2.68.10",
|
|
245
245
|
"@scripts/rstest-config": "2.66.0"
|
|
246
246
|
},
|