@reckona/mreact-server 0.0.137 → 0.0.139
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/boundary.d.ts.map +1 -1
- package/dist/boundary.js +5 -1
- package/dist/boundary.js.map +1 -1
- package/dist/buffer-sink.d.ts.map +1 -1
- package/dist/buffer-sink.js +1 -5
- package/dist/buffer-sink.js.map +1 -1
- package/dist/flight.js +56 -24
- package/dist/flight.js.map +1 -1
- package/dist/html-helpers.js +52 -6
- package/dist/html-helpers.js.map +1 -1
- package/dist/html-internals.d.ts.map +1 -1
- package/dist/html-internals.js +55 -9
- package/dist/html-internals.js.map +1 -1
- package/package.json +3 -3
- package/src/boundary.ts +6 -2
- package/src/buffer-sink.ts +1 -5
- package/src/flight.ts +80 -18
- package/src/html-helpers.ts +68 -5
- package/src/html-internals.ts +71 -9
package/src/html-helpers.ts
CHANGED
|
@@ -184,7 +184,12 @@ function appendReactNodeList(
|
|
|
184
184
|
|
|
185
185
|
for (const node of nodes) {
|
|
186
186
|
if (chain !== undefined) {
|
|
187
|
-
|
|
187
|
+
const buffered = renderReactNodeToBufferedString(node, state);
|
|
188
|
+
chain = chain.then(async () => {
|
|
189
|
+
await buffered.result;
|
|
190
|
+
await buffered.sink.drain();
|
|
191
|
+
sink.append(buffered.sink.toString());
|
|
192
|
+
});
|
|
188
193
|
continue;
|
|
189
194
|
}
|
|
190
195
|
|
|
@@ -198,6 +203,18 @@ function appendReactNodeList(
|
|
|
198
203
|
return chain;
|
|
199
204
|
}
|
|
200
205
|
|
|
206
|
+
function renderReactNodeToBufferedString(
|
|
207
|
+
node: unknown,
|
|
208
|
+
state: HtmlRenderState,
|
|
209
|
+
): { result: void | PromiseLike<void>; sink: ReturnType<typeof createStringSink> } {
|
|
210
|
+
const sink = createStringSink();
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
result: appendReactNode(sink, node, state),
|
|
214
|
+
sink,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
201
218
|
function appendReactElement(
|
|
202
219
|
sink: HtmlSink,
|
|
203
220
|
element: ReactCompatElement,
|
|
@@ -294,7 +311,12 @@ function appendRawTextNodeList(
|
|
|
294
311
|
|
|
295
312
|
for (const node of nodes) {
|
|
296
313
|
if (chain !== undefined) {
|
|
297
|
-
|
|
314
|
+
const buffered = renderRawTextNodeToBufferedString(node);
|
|
315
|
+
chain = chain.then(async () => {
|
|
316
|
+
await buffered.result;
|
|
317
|
+
await buffered.sink.drain();
|
|
318
|
+
sink.append(buffered.sink.toString());
|
|
319
|
+
});
|
|
298
320
|
continue;
|
|
299
321
|
}
|
|
300
322
|
|
|
@@ -308,6 +330,17 @@ function appendRawTextNodeList(
|
|
|
308
330
|
return chain;
|
|
309
331
|
}
|
|
310
332
|
|
|
333
|
+
function renderRawTextNodeToBufferedString(
|
|
334
|
+
node: unknown,
|
|
335
|
+
): { result: void | PromiseLike<void>; sink: ReturnType<typeof createStringSink> } {
|
|
336
|
+
const sink = createStringSink();
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
result: appendRawTextNode(sink, node),
|
|
340
|
+
sink,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
311
344
|
function appendSuspenseElement(
|
|
312
345
|
sink: HtmlSink,
|
|
313
346
|
element: ReactCompatElement,
|
|
@@ -410,7 +443,8 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
410
443
|
name === "ref" ||
|
|
411
444
|
value === null ||
|
|
412
445
|
value === undefined ||
|
|
413
|
-
typeof value === "function"
|
|
446
|
+
typeof value === "function" ||
|
|
447
|
+
/^on/i.test(name)
|
|
414
448
|
) {
|
|
415
449
|
return "";
|
|
416
450
|
}
|
|
@@ -421,6 +455,10 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
421
455
|
return "";
|
|
422
456
|
}
|
|
423
457
|
|
|
458
|
+
if (/^on/i.test(attributeName)) {
|
|
459
|
+
return "";
|
|
460
|
+
}
|
|
461
|
+
|
|
424
462
|
if (value === false && !isBooleanishStringAttribute(attributeName)) {
|
|
425
463
|
return "";
|
|
426
464
|
}
|
|
@@ -488,11 +526,25 @@ const HTML_ATTRIBUTE_ALIASES: Record<string, string> = {
|
|
|
488
526
|
};
|
|
489
527
|
|
|
490
528
|
function isBooleanishStringAttribute(name: string): boolean {
|
|
491
|
-
const attributeName =
|
|
492
|
-
|
|
529
|
+
const attributeName = name;
|
|
530
|
+
|
|
531
|
+
if (
|
|
493
532
|
attributeName.startsWith("aria-") ||
|
|
494
533
|
attributeName.startsWith("data-") ||
|
|
495
534
|
BOOLEANISH_STRING_ATTRIBUTES.has(attributeName)
|
|
535
|
+
) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (!hasAsciiUppercase(attributeName)) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const lowerAttributeName = attributeName.toLowerCase();
|
|
544
|
+
return (
|
|
545
|
+
lowerAttributeName.startsWith("aria-") ||
|
|
546
|
+
lowerAttributeName.startsWith("data-") ||
|
|
547
|
+
BOOLEANISH_STRING_ATTRIBUTES.has(lowerAttributeName)
|
|
496
548
|
);
|
|
497
549
|
}
|
|
498
550
|
|
|
@@ -502,6 +554,17 @@ const BOOLEANISH_STRING_ATTRIBUTES = new Set<string>([
|
|
|
502
554
|
"spellcheck",
|
|
503
555
|
]);
|
|
504
556
|
|
|
557
|
+
function hasAsciiUppercase(value: string): boolean {
|
|
558
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
559
|
+
const code = value.charCodeAt(index);
|
|
560
|
+
if (code >= 65 && code <= 90) {
|
|
561
|
+
return true;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
|
|
505
568
|
function isPromiseLikeNode(value: unknown): value is PromiseLike<unknown> {
|
|
506
569
|
return isPromiseLikeUnknown(value);
|
|
507
570
|
}
|
package/src/html-internals.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
const attributeEscapePattern = /["&<>]/;
|
|
2
|
+
const scriptJsonEscapePattern = /[<\u2028\u2029]/;
|
|
3
|
+
|
|
1
4
|
export function escapeAttribute(value: string): string {
|
|
2
|
-
return value
|
|
3
|
-
.replaceAll("&", "&")
|
|
4
|
-
.replaceAll('"', """)
|
|
5
|
-
.replaceAll("<", "<")
|
|
6
|
-
.replaceAll(">", ">");
|
|
5
|
+
return escapeString(value, attributeEscapePattern, attributeReplacement);
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
export function serializeScriptJson(value: unknown): string {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const json = JSON.stringify(value);
|
|
10
|
+
if (json === undefined) {
|
|
11
|
+
throw new TypeError("Cannot serialize undefined as script JSON");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return escapeString(json, scriptJsonEscapePattern, scriptJsonReplacement);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function renderNonceAttribute(nonce: string | undefined): string {
|
|
@@ -25,3 +26,64 @@ export function isPromiseLikeUnknown(value: unknown): value is PromiseLike<unkno
|
|
|
25
26
|
typeof (value as { then?: unknown }).then === "function"
|
|
26
27
|
);
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
function escapeString(
|
|
31
|
+
value: string,
|
|
32
|
+
pattern: RegExp,
|
|
33
|
+
replacementForCode: (code: number) => string | undefined,
|
|
34
|
+
): string {
|
|
35
|
+
const match = pattern.exec(value);
|
|
36
|
+
|
|
37
|
+
if (match === null) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let escaped = "";
|
|
42
|
+
let lastIndex = 0;
|
|
43
|
+
let index = match.index;
|
|
44
|
+
|
|
45
|
+
for (; index < value.length; index += 1) {
|
|
46
|
+
const replacement = replacementForCode(value.charCodeAt(index));
|
|
47
|
+
|
|
48
|
+
if (replacement === undefined) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (lastIndex !== index) {
|
|
53
|
+
escaped += value.substring(lastIndex, index);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
escaped += replacement;
|
|
57
|
+
lastIndex = index + 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return lastIndex === index ? escaped : escaped + value.substring(lastIndex, index);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function attributeReplacement(code: number): string | undefined {
|
|
64
|
+
switch (code) {
|
|
65
|
+
case 34:
|
|
66
|
+
return """;
|
|
67
|
+
case 38:
|
|
68
|
+
return "&";
|
|
69
|
+
case 60:
|
|
70
|
+
return "<";
|
|
71
|
+
case 62:
|
|
72
|
+
return ">";
|
|
73
|
+
default:
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function scriptJsonReplacement(code: number): string | undefined {
|
|
79
|
+
switch (code) {
|
|
80
|
+
case 60:
|
|
81
|
+
return "\\u003c";
|
|
82
|
+
case 8232:
|
|
83
|
+
return "\\u2028";
|
|
84
|
+
case 8233:
|
|
85
|
+
return "\\u2029";
|
|
86
|
+
default:
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|