@reckona/mreact-compat 0.0.153 → 0.0.155
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/dom-props.d.ts.map +1 -1
- package/dist/dom-props.js +0 -21
- package/dist/dom-props.js.map +1 -1
- package/dist/element.d.ts +0 -1
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +2 -41
- package/dist/element.js.map +1 -1
- package/dist/host-reconciler.d.ts.map +1 -1
- package/dist/host-reconciler.js +37 -32
- package/dist/host-reconciler.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-default.d.ts +2 -1
- package/dist/react-default.d.ts.map +1 -1
- package/dist/react-default.js +2 -1
- package/dist/react-default.js.map +1 -1
- package/dist/server-render.d.ts +1 -0
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +92 -38
- package/dist/server-render.js.map +1 -1
- package/package.json +3 -3
- package/src/dom-props.ts +0 -30
- package/src/element.ts +4 -57
- package/src/host-reconciler.ts +43 -36
- package/src/index.ts +1 -1
- package/src/react-default.ts +2 -1
- package/src/server-render.ts +104 -45
package/src/host-reconciler.ts
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
FORWARD_REF_TYPE,
|
|
5
5
|
Fragment,
|
|
6
6
|
HOST_CHILDREN_ONLY_PROPS_META,
|
|
7
|
-
HOST_OWN_PROPS_META,
|
|
8
7
|
LAZY_TYPE,
|
|
9
8
|
MEMO_TYPE,
|
|
10
9
|
Profiler,
|
|
@@ -571,8 +570,8 @@ function getReusableKeyedRowHostFiber(
|
|
|
571
570
|
const previousRecord = previousProps as Record<string, unknown>;
|
|
572
571
|
|
|
573
572
|
if (
|
|
574
|
-
|
|
575
|
-
|
|
573
|
+
getDirectHostTextChild(previousRecord.children) !== row.text ||
|
|
574
|
+
!hostOwnPropsEqual(previousRecord, row.element.props)
|
|
576
575
|
) {
|
|
577
576
|
return undefined;
|
|
578
577
|
}
|
|
@@ -591,7 +590,6 @@ interface KeyedRowHostElement {
|
|
|
591
590
|
element: ReactCompatElement;
|
|
592
591
|
key: string;
|
|
593
592
|
type: string;
|
|
594
|
-
meta: number;
|
|
595
593
|
text: string;
|
|
596
594
|
}
|
|
597
595
|
|
|
@@ -600,7 +598,6 @@ function createKeyedRowHostElementScratch(): KeyedRowHostElement {
|
|
|
600
598
|
element: undefined as unknown as ReactCompatElement,
|
|
601
599
|
key: "",
|
|
602
600
|
type: "",
|
|
603
|
-
meta: 0,
|
|
604
601
|
text: "",
|
|
605
602
|
};
|
|
606
603
|
}
|
|
@@ -618,18 +615,17 @@ function readKeyedRowHostElement(
|
|
|
618
615
|
return false;
|
|
619
616
|
}
|
|
620
617
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const text =
|
|
618
|
+
// Any keyed host row whose children collapse to a single text value
|
|
619
|
+
// qualifies; row props are compared per reuse with hostOwnPropsEqual.
|
|
620
|
+
const text = getDirectHostTextChild((node.props as Record<string, unknown>).children);
|
|
624
621
|
|
|
625
|
-
if (
|
|
622
|
+
if (text === undefined) {
|
|
626
623
|
return false;
|
|
627
624
|
}
|
|
628
625
|
|
|
629
626
|
row.element = node;
|
|
630
627
|
row.key = node.key;
|
|
631
628
|
row.type = node.type;
|
|
632
|
-
row.meta = meta;
|
|
633
629
|
row.text = text;
|
|
634
630
|
return true;
|
|
635
631
|
}
|
|
@@ -667,13 +663,9 @@ function createKeyedRowHostFiber(
|
|
|
667
663
|
}
|
|
668
664
|
|
|
669
665
|
const previousProps = current.memoizedProps ?? current.pendingProps;
|
|
670
|
-
const previousMeta =
|
|
671
|
-
typeof previousProps === "object" && previousProps !== null
|
|
672
|
-
? getHostOwnPropsMeta(previousProps as Record<string, unknown>)
|
|
673
|
-
: undefined;
|
|
674
666
|
const previousText = getDirectHostTextChild(hostFiberChildrenProp(previousProps));
|
|
675
667
|
|
|
676
|
-
if (
|
|
668
|
+
if (previousText !== row.text || !hostOwnPropsEqual(previousProps, row.element.props)) {
|
|
677
669
|
fiber.flags |= Update;
|
|
678
670
|
}
|
|
679
671
|
|
|
@@ -1642,6 +1634,8 @@ function commitHostDirtyFiber(
|
|
|
1642
1634
|
hostPropsAreChildrenOnly(fiber.memoizedProps) &&
|
|
1643
1635
|
hostPropsAreChildrenOnly(props));
|
|
1644
1636
|
const textOnlyRowUpdate =
|
|
1637
|
+
!propsAreUnchanged &&
|
|
1638
|
+
!propsAreChildrenOnly &&
|
|
1645
1639
|
fiber.hydrateExisting !== true &&
|
|
1646
1640
|
isRowTextOnlyUpdate(fiber.memoizedProps, props);
|
|
1647
1641
|
|
|
@@ -2032,6 +2026,8 @@ function commitHostFiber(
|
|
|
2032
2026
|
hostPropsAreChildrenOnly(fiber.memoizedProps) &&
|
|
2033
2027
|
hostPropsAreChildrenOnly(props));
|
|
2034
2028
|
const textOnlyRowUpdate =
|
|
2029
|
+
!propsAreUnchanged &&
|
|
2030
|
+
!propsAreChildrenOnly &&
|
|
2035
2031
|
fiber.hydrateExisting !== true &&
|
|
2036
2032
|
isRowTextOnlyUpdate(fiber.memoizedProps, props);
|
|
2037
2033
|
|
|
@@ -2243,12 +2239,6 @@ function hostOwnPropsEqual(previous: unknown, next: Record<string, unknown>): bo
|
|
|
2243
2239
|
}
|
|
2244
2240
|
|
|
2245
2241
|
const previousProps = previous as Record<string, unknown>;
|
|
2246
|
-
const previousMeta = getHostOwnPropsMeta(previousProps);
|
|
2247
|
-
const nextMeta = getHostOwnPropsMeta(next);
|
|
2248
|
-
|
|
2249
|
-
if (previousMeta !== undefined && nextMeta !== undefined) {
|
|
2250
|
-
return previousMeta === nextMeta;
|
|
2251
|
-
}
|
|
2252
2242
|
|
|
2253
2243
|
let previousCount = 0;
|
|
2254
2244
|
let nextCount = 0;
|
|
@@ -2276,10 +2266,6 @@ function hostOwnPropsEqual(previous: unknown, next: Record<string, unknown>): bo
|
|
|
2276
2266
|
return previousCount === nextCount;
|
|
2277
2267
|
}
|
|
2278
2268
|
|
|
2279
|
-
function getHostOwnPropsMeta(props: Record<string, unknown>): number | undefined {
|
|
2280
|
-
return (props as { [HOST_OWN_PROPS_META]?: number })[HOST_OWN_PROPS_META];
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
2269
|
function hostDirectTextChildChanged(previous: unknown, next: Record<string, unknown>): boolean {
|
|
2284
2270
|
const previousText = getDirectHostTextChild(hostFiberChildrenProp(previous));
|
|
2285
2271
|
const nextText = getDirectHostTextChild(next.children);
|
|
@@ -2379,17 +2365,15 @@ function isRowTextOnlyUpdate(previous: unknown, next: Record<string, unknown>):
|
|
|
2379
2365
|
}
|
|
2380
2366
|
|
|
2381
2367
|
const previousProps = previous as Record<string, unknown>;
|
|
2382
|
-
const previousMeta = getHostOwnPropsMeta(previousProps);
|
|
2383
|
-
const nextMeta = getHostOwnPropsMeta(next);
|
|
2384
|
-
|
|
2385
|
-
if (previousMeta === undefined || previousMeta !== nextMeta) {
|
|
2386
|
-
return false;
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
2368
|
const previousText = getDirectHostTextChild(previousProps.children);
|
|
2390
2369
|
const nextText = getDirectHostTextChild(next.children);
|
|
2391
2370
|
|
|
2392
|
-
return
|
|
2371
|
+
return (
|
|
2372
|
+
previousText !== undefined &&
|
|
2373
|
+
nextText !== undefined &&
|
|
2374
|
+
previousText !== nextText &&
|
|
2375
|
+
hostOwnPropsEqual(previousProps, next)
|
|
2376
|
+
);
|
|
2393
2377
|
}
|
|
2394
2378
|
|
|
2395
2379
|
function hostFiberChildrenProp(props: unknown): unknown {
|
|
@@ -2404,10 +2388,33 @@ function getDirectHostTextChild(children: unknown): string | undefined {
|
|
|
2404
2388
|
: undefined;
|
|
2405
2389
|
}
|
|
2406
2390
|
|
|
2391
|
+
// This package has no Node type dependency; declare the minimal process
|
|
2392
|
+
// shape needed for the literal process.env.NODE_ENV expression below.
|
|
2393
|
+
declare const process: { env: Record<string, string | undefined> };
|
|
2394
|
+
|
|
2395
|
+
type HostFastPathMode = "static-fast" | "dynamic";
|
|
2396
|
+
|
|
2397
|
+
const hostFastPathMode: HostFastPathMode = (() => {
|
|
2398
|
+
try {
|
|
2399
|
+
// The literal process.env.NODE_ENV member expression is what bundler
|
|
2400
|
+
// define rewriting matches; a globalThis.process indirection is never
|
|
2401
|
+
// rewritten and leaves deployed browser bundles without any fast path.
|
|
2402
|
+
return process.env.NODE_ENV === "production" ? "static-fast" : "dynamic";
|
|
2403
|
+
} catch {
|
|
2404
|
+
// No process global at all: an unbundled browser runtime. Treat it as
|
|
2405
|
+
// production rather than running every host update on the slow path.
|
|
2406
|
+
return "static-fast";
|
|
2407
|
+
}
|
|
2408
|
+
})();
|
|
2409
|
+
|
|
2407
2410
|
function shouldUseDirectHostTextChild(): boolean {
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
+
if (hostFastPathMode === "static-fast") {
|
|
2412
|
+
return true;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Node dev/test environments keep the per-call env read so test harnesses
|
|
2416
|
+
// can flip NODE_ENV (vi.stubEnv) without re-importing this module.
|
|
2417
|
+
return process.env.NODE_ENV === "production";
|
|
2411
2418
|
}
|
|
2412
2419
|
|
|
2413
2420
|
function syncDirectHostTextChild(element: Element, text: string): Text {
|
package/src/index.ts
CHANGED
|
@@ -91,6 +91,6 @@ export {
|
|
|
91
91
|
useTransition,
|
|
92
92
|
version,
|
|
93
93
|
} from "./hooks.js";
|
|
94
|
-
export { renderToString } from "./server-render.js";
|
|
94
|
+
export { renderChildToString, renderToString } from "./server-render.js";
|
|
95
95
|
export type { StartTransition, TransitionScope } from "./hooks.js";
|
|
96
96
|
export { default } from "./react-default.js";
|
package/src/react-default.ts
CHANGED
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
useTransition,
|
|
67
67
|
version,
|
|
68
68
|
} from "./hooks.js";
|
|
69
|
-
import { renderToString } from "./server-render.js";
|
|
69
|
+
import { renderChildToString, renderToString } from "./server-render.js";
|
|
70
70
|
|
|
71
71
|
const ReactCompat = {
|
|
72
72
|
Component,
|
|
@@ -123,6 +123,7 @@ const ReactCompat = {
|
|
|
123
123
|
cache,
|
|
124
124
|
cacheSignal,
|
|
125
125
|
captureOwnerStack,
|
|
126
|
+
renderChildToString,
|
|
126
127
|
renderToString,
|
|
127
128
|
startTransition,
|
|
128
129
|
unstable_useCacheRefresh,
|
package/src/server-render.ts
CHANGED
|
@@ -73,6 +73,30 @@ export function renderToString<TProps>(
|
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Renders a single child value the way the interpreter renders expression
|
|
77
|
+
// children: primitives escape, null/undefined/boolean render nothing, and
|
|
78
|
+
// react nodes fall back to the interpreter. Compiled compat pages call this
|
|
79
|
+
// for expression children whose runtime type is unknown.
|
|
80
|
+
export function renderChildToString(value: unknown): string {
|
|
81
|
+
if (value === null || value === undefined || typeof value === "boolean") {
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
86
|
+
return escapeHtml(value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const runtime = createRootRuntime(() => undefined, { idMode: "server" });
|
|
90
|
+
|
|
91
|
+
return runWithCacheScope(createCacheScope(), () => {
|
|
92
|
+
try {
|
|
93
|
+
return renderNodeToString(value as ReactCompatNode, runtime, "0.0");
|
|
94
|
+
} finally {
|
|
95
|
+
runtime.dispose();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
77
101
|
return (
|
|
78
102
|
typeof value === "object" &&
|
|
@@ -131,7 +155,14 @@ function renderElementToString(
|
|
|
131
155
|
return `<${element.type}${attributes}/>`;
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
|
|
158
|
+
// Primitive children dominate real markup; serializing them inline skips
|
|
159
|
+
// one recursive call and one child-path allocation per text leaf.
|
|
160
|
+
const children = element.props.children;
|
|
161
|
+
if (typeof children === "string" || typeof children === "number") {
|
|
162
|
+
return `<${element.type}${attributes}>${escapeHtml(children)}</${element.type}>`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return `<${element.type}${attributes}>${renderNodeToString(children, runtime, `${path}.children`)}</${element.type}>`;
|
|
135
166
|
}
|
|
136
167
|
|
|
137
168
|
if (element.type === Fragment) {
|
|
@@ -216,37 +247,26 @@ function renderElementToString(
|
|
|
216
247
|
}
|
|
217
248
|
|
|
218
249
|
function renderAttributesToString(props: Record<string, unknown>): string {
|
|
219
|
-
const
|
|
220
|
-
const entries = Object.entries(sanitizedProps);
|
|
221
|
-
if (
|
|
222
|
-
entries.length === 0 ||
|
|
223
|
-
(entries.length === 1 && entries[0]?.[0] === "children")
|
|
224
|
-
) {
|
|
225
|
-
return "";
|
|
226
|
-
}
|
|
250
|
+
const skipUnsafeMetaRefreshContent = hasUnsafeMetaRefreshProps(props);
|
|
227
251
|
|
|
228
252
|
let attributes = "";
|
|
229
|
-
for (const
|
|
230
|
-
|
|
253
|
+
for (const name in props) {
|
|
254
|
+
if (skipUnsafeMetaRefreshContent && name === "content") {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
attributes += renderHtmlAttribute(name, props[name]);
|
|
231
258
|
}
|
|
232
259
|
return attributes;
|
|
233
260
|
}
|
|
234
261
|
|
|
235
|
-
function
|
|
236
|
-
props: Record<string, unknown>,
|
|
237
|
-
): Record<string, unknown> {
|
|
238
|
-
const httpEquiv = props["http-equiv"] ?? props.httpEquiv;
|
|
262
|
+
function hasUnsafeMetaRefreshProps(props: Record<string, unknown>): boolean {
|
|
239
263
|
const content = props.content;
|
|
240
|
-
if (typeof
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
|
-
if (!isUnsafeMetaRefreshContent(httpEquiv, content)) {
|
|
244
|
-
return props;
|
|
264
|
+
if (typeof content !== "string") {
|
|
265
|
+
return false;
|
|
245
266
|
}
|
|
246
267
|
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
return sanitized;
|
|
268
|
+
const httpEquiv = props["http-equiv"] ?? props.httpEquiv;
|
|
269
|
+
return typeof httpEquiv === "string" && isUnsafeMetaRefreshContent(httpEquiv, content);
|
|
250
270
|
}
|
|
251
271
|
|
|
252
272
|
function isClassComponentType(
|
|
@@ -339,15 +359,24 @@ function renderInputAttributesToString(props: Record<string, unknown>): string {
|
|
|
339
359
|
.join("");
|
|
340
360
|
}
|
|
341
361
|
|
|
362
|
+
// Matches the /^on/i prefix without allocating a fresh regex per attribute.
|
|
363
|
+
function isEventHandlerName(name: string): boolean {
|
|
364
|
+
return (
|
|
365
|
+
name.length > 1 &&
|
|
366
|
+
(name.charCodeAt(0) | 32) === 111 &&
|
|
367
|
+
(name.charCodeAt(1) | 32) === 110
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
342
371
|
function renderHtmlAttribute(name: string, value: unknown): string {
|
|
343
372
|
if (
|
|
373
|
+
value === null ||
|
|
374
|
+
value === undefined ||
|
|
375
|
+
typeof value === "function" ||
|
|
344
376
|
name === "children" ||
|
|
345
377
|
name === "key" ||
|
|
346
378
|
name === "ref" ||
|
|
347
|
-
|
|
348
|
-
value === null ||
|
|
349
|
-
value === undefined ||
|
|
350
|
-
typeof value === "function"
|
|
379
|
+
isEventHandlerName(name)
|
|
351
380
|
) {
|
|
352
381
|
return "";
|
|
353
382
|
}
|
|
@@ -363,7 +392,7 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
363
392
|
return "";
|
|
364
393
|
}
|
|
365
394
|
|
|
366
|
-
if (
|
|
395
|
+
if (isEventHandlerName(attributeName)) {
|
|
367
396
|
return "";
|
|
368
397
|
}
|
|
369
398
|
|
|
@@ -404,13 +433,14 @@ function renderHtmlAttribute(name: string, value: unknown): string {
|
|
|
404
433
|
|
|
405
434
|
const VALID_ATTRIBUTE_NAME = /^[A-Za-z_][\w.\-:]*$/;
|
|
406
435
|
|
|
407
|
-
function isBooleanishStringAttribute(
|
|
408
|
-
|
|
409
|
-
|
|
436
|
+
function isBooleanishStringAttribute(attributeName: string): boolean {
|
|
437
|
+
// Callers pass the already-mapped HTML attribute name.
|
|
438
|
+
const lowerCased = attributeName.toLowerCase();
|
|
439
|
+
return lowerCased.startsWith("aria-") || BOOLEANISH_STRING_ATTRIBUTES.has(lowerCased);
|
|
410
440
|
}
|
|
411
441
|
|
|
412
|
-
function isDataAttribute(
|
|
413
|
-
return
|
|
442
|
+
function isDataAttribute(attributeName: string): boolean {
|
|
443
|
+
return attributeName.toLowerCase().startsWith("data-");
|
|
414
444
|
}
|
|
415
445
|
|
|
416
446
|
const BOOLEANISH_STRING_ATTRIBUTES = new Set<string>([
|
|
@@ -470,17 +500,24 @@ function renderStyleAttribute(value: unknown): string {
|
|
|
470
500
|
return "";
|
|
471
501
|
}
|
|
472
502
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
propertyValue
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
)
|
|
483
|
-
|
|
503
|
+
const styleProps = value as Record<string, unknown>;
|
|
504
|
+
let css = "";
|
|
505
|
+
for (const name in styleProps) {
|
|
506
|
+
const propertyValue = styleProps[name];
|
|
507
|
+
if (
|
|
508
|
+
propertyValue === null ||
|
|
509
|
+
propertyValue === undefined ||
|
|
510
|
+
typeof propertyValue === "boolean" ||
|
|
511
|
+
propertyValue === ""
|
|
512
|
+
) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
css += css === ""
|
|
517
|
+
? `${toKebabCase(name)}:${renderCssValue(name, propertyValue)}`
|
|
518
|
+
: `;${toKebabCase(name)}:${renderCssValue(name, propertyValue)}`;
|
|
519
|
+
}
|
|
520
|
+
return css;
|
|
484
521
|
}
|
|
485
522
|
|
|
486
523
|
function renderCssValue(name: string, value: unknown): string {
|
|
@@ -491,8 +528,30 @@ function renderCssValue(name: string, value: unknown): string {
|
|
|
491
528
|
return `${value}px`;
|
|
492
529
|
}
|
|
493
530
|
|
|
531
|
+
const UPPERCASE_LETTER = /[A-Z]/;
|
|
532
|
+
const UPPERCASE_LETTER_GLOBAL = /[A-Z]/g;
|
|
533
|
+
// Distinct camelCase style names per app are few; the cap only guards
|
|
534
|
+
// pathological dynamically-generated property names.
|
|
535
|
+
const KEBAB_CASE_CACHE = new Map<string, string>();
|
|
536
|
+
const KEBAB_CASE_CACHE_LIMIT = 512;
|
|
537
|
+
|
|
538
|
+
function kebabReplace(letter: string): string {
|
|
539
|
+
return `-${letter.toLowerCase()}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
494
542
|
function toKebabCase(value: string): string {
|
|
495
|
-
|
|
543
|
+
if (!UPPERCASE_LETTER.test(value)) {
|
|
544
|
+
return value;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let cached = KEBAB_CASE_CACHE.get(value);
|
|
548
|
+
if (cached === undefined) {
|
|
549
|
+
cached = value.replace(UPPERCASE_LETTER_GLOBAL, kebabReplace);
|
|
550
|
+
if (KEBAB_CASE_CACHE.size < KEBAB_CASE_CACHE_LIMIT) {
|
|
551
|
+
KEBAB_CASE_CACHE.set(value, cached);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return cached;
|
|
496
555
|
}
|
|
497
556
|
|
|
498
557
|
function isUnitlessCssProperty(name: string): boolean {
|