@reckona/mreact-compat 0.0.171 → 0.0.173
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/devtools.js +2 -0
- package/dist/devtools.js.map +1 -1
- package/dist/dom-host-rules.d.ts.map +1 -1
- package/dist/dom-host-rules.js +4 -1
- package/dist/dom-host-rules.js.map +1 -1
- package/dist/dom-props.js +2 -1
- package/dist/dom-props.js.map +1 -1
- package/dist/element.d.ts +13 -1
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +30 -2
- package/dist/element.js.map +1 -1
- package/dist/fiber-commit.js +2 -1
- package/dist/fiber-commit.js.map +1 -1
- package/dist/fiber-host.d.ts +1 -1
- package/dist/fiber-host.d.ts.map +1 -1
- package/dist/fiber-host.js +1 -1
- package/dist/fiber-host.js.map +1 -1
- package/dist/fiber.d.ts +2 -1
- package/dist/fiber.d.ts.map +1 -1
- package/dist/fiber.js +2 -0
- package/dist/fiber.js.map +1 -1
- package/dist/hooks.d.ts +9 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +127 -44
- package/dist/hooks.js.map +1 -1
- package/dist/host-reconciler.d.ts +2 -0
- package/dist/host-reconciler.d.ts.map +1 -1
- package/dist/host-reconciler.js +696 -55
- package/dist/host-reconciler.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +3 -1
- package/dist/jsx-dev-runtime.d.ts.map +1 -1
- package/dist/jsx-dev-runtime.js +3 -1
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +4 -2
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js +9 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/reactive-prop-cell.d.ts +13 -0
- package/dist/reactive-prop-cell.d.ts.map +1 -0
- package/dist/reactive-prop-cell.js +36 -0
- package/dist/reactive-prop-cell.js.map +1 -0
- package/dist/root.d.ts.map +1 -1
- package/dist/root.js +8 -5
- package/dist/root.js.map +1 -1
- package/package.json +3 -3
- package/src/devtools.ts +2 -0
- package/src/dom-host-rules.ts +4 -1
- package/src/dom-props.ts +2 -1
- package/src/element.ts +60 -3
- package/src/fiber-commit.ts +3 -1
- package/src/fiber-host.ts +2 -0
- package/src/fiber.ts +4 -0
- package/src/hooks.ts +187 -46
- package/src/host-reconciler.ts +1031 -70
- package/src/jsx-dev-runtime.ts +4 -0
- package/src/jsx-runtime.ts +16 -0
- package/src/reactive-prop-cell.ts +67 -0
- package/src/root.ts +14 -4
package/src/host-reconciler.ts
CHANGED
|
@@ -7,16 +7,25 @@ import {
|
|
|
7
7
|
LAZY_TYPE,
|
|
8
8
|
MEMO_TYPE,
|
|
9
9
|
Profiler,
|
|
10
|
+
REACTIVE_DOM_BLOCK_TYPE,
|
|
10
11
|
REACTIVE_TEXT_BINDING_META,
|
|
11
12
|
STRICT_MODE_TYPE,
|
|
12
13
|
Suspense,
|
|
13
14
|
SuspenseList,
|
|
14
15
|
type ReactCompatElement,
|
|
15
16
|
type ReactCompatPortal,
|
|
17
|
+
type ReactiveDomBlockProps,
|
|
18
|
+
type ReactiveDomBlockResult,
|
|
16
19
|
isReactCompatElement,
|
|
17
20
|
isReactCompatPortal,
|
|
18
21
|
type ReactCompatNode,
|
|
19
22
|
} from "./element.js";
|
|
23
|
+
import {
|
|
24
|
+
createReactivePropCell,
|
|
25
|
+
createReactivePropProxy,
|
|
26
|
+
setReactivePropCell,
|
|
27
|
+
type ReactiveDomBlockState,
|
|
28
|
+
} from "./reactive-prop-cell.js";
|
|
20
29
|
import {
|
|
21
30
|
consumerContext,
|
|
22
31
|
isReactCompatConsumer,
|
|
@@ -36,6 +45,7 @@ import {
|
|
|
36
45
|
namespaceForHostChildren,
|
|
37
46
|
namespaceForHostElement,
|
|
38
47
|
type CustomHostDocument,
|
|
48
|
+
type HostElement,
|
|
39
49
|
type HostNamespace,
|
|
40
50
|
} from "./dom-host-rules.js";
|
|
41
51
|
import { createFiber, createWorkInProgress, type Fiber, type FiberRoot } from "./fiber.js";
|
|
@@ -96,6 +106,7 @@ interface SuspenseFiberState {
|
|
|
96
106
|
|
|
97
107
|
const committedPortalContainers = new Set<Element>();
|
|
98
108
|
const pendingHostRefUpdates: { ref: unknown; node: unknown }[] = [];
|
|
109
|
+
const emptyInstanceKeys: string[] = [];
|
|
99
110
|
|
|
100
111
|
interface FiberHydrationOptions extends RenderOptions {
|
|
101
112
|
previousNodes?: readonly Node[];
|
|
@@ -174,6 +185,10 @@ export function canRenderHostFiber(node: ReactCompatNode): boolean {
|
|
|
174
185
|
return true;
|
|
175
186
|
}
|
|
176
187
|
|
|
188
|
+
if (node.type === REACTIVE_DOM_BLOCK_TYPE) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
177
192
|
if (isReactCompatProvider(node.type)) {
|
|
178
193
|
return canRenderHostFiber(node.props.children as ReactCompatNode);
|
|
179
194
|
}
|
|
@@ -258,7 +273,28 @@ export function commitHostFiberRoot(
|
|
|
258
273
|
pendingHostRefUpdates.length = 0;
|
|
259
274
|
const commitPath = getRootCommitPath(options);
|
|
260
275
|
if (!hasChildListMutation(finishedWork)) {
|
|
261
|
-
|
|
276
|
+
commitHostDirtyChildrenOf(
|
|
277
|
+
finishedWork,
|
|
278
|
+
finishedWork.child,
|
|
279
|
+
root.container,
|
|
280
|
+
root.container,
|
|
281
|
+
commitPath,
|
|
282
|
+
options,
|
|
283
|
+
);
|
|
284
|
+
committed = true;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (
|
|
289
|
+
finishedWork.childListChanged &&
|
|
290
|
+
commitHostKeyedChildListMutationFiber(
|
|
291
|
+
finishedWork,
|
|
292
|
+
root.container,
|
|
293
|
+
root.container,
|
|
294
|
+
commitPath,
|
|
295
|
+
options,
|
|
296
|
+
)
|
|
297
|
+
) {
|
|
262
298
|
committed = true;
|
|
263
299
|
return;
|
|
264
300
|
}
|
|
@@ -317,6 +353,74 @@ export function commitHydratingHostFiberRoot(
|
|
|
317
353
|
}
|
|
318
354
|
}
|
|
319
355
|
|
|
356
|
+
export function disposeHostFiberResources(fiber: Fiber | undefined): void {
|
|
357
|
+
if (fiber === undefined || fiber.hasDisposableResources !== true) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Dispose this fiber and its SUBTREE only — not its siblings. This is called
|
|
362
|
+
// once per deleted fiber, and deleted siblings are disposed by their own
|
|
363
|
+
// calls; walking siblings here re-walked the whole deleted list per deletion
|
|
364
|
+
// (O(n^2) on a cleared 1k-row reactive list). The dedupe set is allocated
|
|
365
|
+
// once for the subtree walk.
|
|
366
|
+
const seen = new Set<unknown>();
|
|
367
|
+
if (fiber.tag === "reactive-dom-block") {
|
|
368
|
+
disposeReactiveDomBlockState(fiber.stateNode, seen);
|
|
369
|
+
}
|
|
370
|
+
disposeHostFiberChildResources(fiber.child, seen);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function disposeUnretainedHostFiberResources(
|
|
374
|
+
fiber: Fiber | undefined,
|
|
375
|
+
retained: ReadonlySet<Fiber>,
|
|
376
|
+
): void {
|
|
377
|
+
if (fiber === undefined || fiber.hasDisposableResources !== true || retained.has(fiber)) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const seen = new Set<unknown>();
|
|
382
|
+
disposeUnretainedHostFiberSubtreeResources(fiber, seen, retained);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function disposeHostFiberChildResources(
|
|
386
|
+
fiber: Fiber | undefined,
|
|
387
|
+
seen: Set<unknown>,
|
|
388
|
+
): void {
|
|
389
|
+
let cursor = fiber;
|
|
390
|
+
|
|
391
|
+
while (cursor !== undefined) {
|
|
392
|
+
if (cursor.hasDisposableResources === true) {
|
|
393
|
+
if (cursor.tag === "reactive-dom-block") {
|
|
394
|
+
disposeReactiveDomBlockState(cursor.stateNode, seen);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
disposeHostFiberChildResources(cursor.child, seen);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
cursor = cursor.sibling;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function disposeUnretainedHostFiberSubtreeResources(
|
|
405
|
+
fiber: Fiber | undefined,
|
|
406
|
+
seen: Set<unknown>,
|
|
407
|
+
retained: ReadonlySet<Fiber>,
|
|
408
|
+
): void {
|
|
409
|
+
if (fiber === undefined || retained.has(fiber) || fiber.hasDisposableResources !== true) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (fiber.tag === "reactive-dom-block") {
|
|
414
|
+
disposeReactiveDomBlockState(fiber.stateNode, seen);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let child = fiber.child;
|
|
418
|
+
while (child !== undefined) {
|
|
419
|
+
disposeUnretainedHostFiberSubtreeResources(child, seen, retained);
|
|
420
|
+
child = child.sibling;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
320
424
|
function reconcileHostChild(
|
|
321
425
|
parent: Fiber,
|
|
322
426
|
currentFirstChild: Fiber | undefined,
|
|
@@ -342,7 +446,15 @@ function reconcileHostChild(
|
|
|
342
446
|
const rowResult =
|
|
343
447
|
children === undefined
|
|
344
448
|
? undefined
|
|
345
|
-
: reconcileKeyedRowHostChildren(parent, currentFirstChild, children, options)
|
|
449
|
+
: reconcileKeyedRowHostChildren(parent, currentFirstChild, children, options) ??
|
|
450
|
+
reconcileKeyedMemoRowHostChildren(
|
|
451
|
+
parent,
|
|
452
|
+
currentFirstChild,
|
|
453
|
+
children,
|
|
454
|
+
runtime,
|
|
455
|
+
path,
|
|
456
|
+
options,
|
|
457
|
+
);
|
|
346
458
|
if (rowResult !== undefined) {
|
|
347
459
|
return rowResult;
|
|
348
460
|
}
|
|
@@ -359,6 +471,7 @@ function reconcileHostChild(
|
|
|
359
471
|
let skipRemainingKeyedLookup = false;
|
|
360
472
|
let sequentialCurrentCursor = currentFirstChild;
|
|
361
473
|
let childListOrderChanged = false;
|
|
474
|
+
let dirtyChildren: Fiber[] | undefined;
|
|
362
475
|
let usedCurrentChildren =
|
|
363
476
|
currentFirstChild === undefined || hasKeyedChildren ? undefined : new Set<Fiber>();
|
|
364
477
|
const ensureUsedCurrentChildren = (): Set<Fiber> => {
|
|
@@ -404,11 +517,28 @@ function reconcileHostChild(
|
|
|
404
517
|
currentKeyed?.sibling?.key === key &&
|
|
405
518
|
canSkipSingleDeletedKeyedFiber(children, index, currentKeyed.sibling)
|
|
406
519
|
) {
|
|
520
|
+
const deleted = currentKeyed;
|
|
521
|
+
const matched = currentKeyed.sibling;
|
|
522
|
+
const suffixResult = tryReuseDependencyFreeMemoRemovalSuffix(
|
|
523
|
+
parent,
|
|
524
|
+
children,
|
|
525
|
+
index,
|
|
526
|
+
deleted,
|
|
527
|
+
matched,
|
|
528
|
+
runtime,
|
|
529
|
+
options,
|
|
530
|
+
first,
|
|
531
|
+
previous,
|
|
532
|
+
consumed,
|
|
533
|
+
);
|
|
534
|
+
if (suffixResult !== undefined) {
|
|
535
|
+
return suffixResult;
|
|
536
|
+
}
|
|
407
537
|
childListOrderChanged = true;
|
|
408
538
|
ensureUsedCurrentChildren();
|
|
409
|
-
matchedCurrent =
|
|
539
|
+
matchedCurrent = matched;
|
|
410
540
|
canReuseMatchedCurrentFiber = false;
|
|
411
|
-
currentKeyed =
|
|
541
|
+
currentKeyed = matched.sibling;
|
|
412
542
|
} else {
|
|
413
543
|
if (
|
|
414
544
|
children !== undefined &&
|
|
@@ -489,6 +619,14 @@ function reconcileHostChild(
|
|
|
489
619
|
&& fiber.memoizedState === undefined
|
|
490
620
|
) {
|
|
491
621
|
fiber.memoizedState = index;
|
|
622
|
+
} else if (
|
|
623
|
+
fiber.tag === "memo" &&
|
|
624
|
+
(fiber.stateNode === undefined || typeof fiber.stateNode === "number")
|
|
625
|
+
) {
|
|
626
|
+
fiber.stateNode = index;
|
|
627
|
+
}
|
|
628
|
+
if (hasHostCommitWork(fiber)) {
|
|
629
|
+
(dirtyChildren ??= []).push(fiber);
|
|
492
630
|
}
|
|
493
631
|
previous = fiber;
|
|
494
632
|
}
|
|
@@ -500,6 +638,7 @@ function reconcileHostChild(
|
|
|
500
638
|
}
|
|
501
639
|
parent.childListChanged =
|
|
502
640
|
childListOrderChanged || childFiberListShapeChanged(currentFirstChild, first);
|
|
641
|
+
recordDirtyChildCommitHints(parent, dirtyChildren);
|
|
503
642
|
|
|
504
643
|
return { fiber: first, consumed };
|
|
505
644
|
}
|
|
@@ -528,6 +667,7 @@ function reconcileKeyedRowHostChildren(
|
|
|
528
667
|
let subtreeFlags = NoFlags;
|
|
529
668
|
let subtreeChildListChanged = false;
|
|
530
669
|
let hasRefSubtree = false;
|
|
670
|
+
let hasDisposableResources = false;
|
|
531
671
|
let appendSuffix: AppendSuffixCommitHint | undefined;
|
|
532
672
|
const canReuseUnchangedRows = hasSameKeyOrderPrefix(currentFirstChild, children);
|
|
533
673
|
const row = createKeyedRowHostElementScratch();
|
|
@@ -593,6 +733,9 @@ function reconcileKeyedRowHostChildren(
|
|
|
593
733
|
if (fiber.hasRefSubtree) {
|
|
594
734
|
hasRefSubtree = true;
|
|
595
735
|
}
|
|
736
|
+
if (fiber.hasDisposableResources) {
|
|
737
|
+
hasDisposableResources = true;
|
|
738
|
+
}
|
|
596
739
|
subtreeFlags |= fiber.flags | fiber.subtreeFlags;
|
|
597
740
|
subtreeChildListChanged =
|
|
598
741
|
subtreeChildListChanged ||
|
|
@@ -610,6 +753,7 @@ function reconcileKeyedRowHostChildren(
|
|
|
610
753
|
}
|
|
611
754
|
|
|
612
755
|
parent.hasRefSubtree = hasRefSubtree;
|
|
756
|
+
parent.hasDisposableResources = hasDisposableResources;
|
|
613
757
|
parent.subtreeFlags = subtreeFlags;
|
|
614
758
|
parent.subtreeChildListChanged = subtreeChildListChanged;
|
|
615
759
|
parent.childListChanged = listShapeChanged;
|
|
@@ -619,6 +763,382 @@ function reconcileKeyedRowHostChildren(
|
|
|
619
763
|
return { fiber: first, consumed: 0 };
|
|
620
764
|
}
|
|
621
765
|
|
|
766
|
+
function isKeyedMemoRowCandidate(node: ReactCompatNode): boolean {
|
|
767
|
+
return (
|
|
768
|
+
isReactCompatElement(node) &&
|
|
769
|
+
node.key !== null &&
|
|
770
|
+
node.ref === null &&
|
|
771
|
+
isMemoType(node.type)
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Marker the compiler stamps on a lowered host-only component that returns its
|
|
776
|
+
// props verbatim as a reactive block (`createReactiveDomBlock(render, props)`).
|
|
777
|
+
// Such a component is pure and structurally static, so on a props change the
|
|
778
|
+
// reconciler can drive its committed block straight through the prop cell instead
|
|
779
|
+
// of re-invoking the component (the block's bound DOM updates via subscriptions).
|
|
780
|
+
const STATIC_REACTIVE_BLOCK_MARKER = "__mreactStaticBlock";
|
|
781
|
+
|
|
782
|
+
function isStaticReactiveBlockComponent(type: unknown): boolean {
|
|
783
|
+
return (
|
|
784
|
+
typeof type === "function" &&
|
|
785
|
+
(type as unknown as Record<string, unknown>)[STATIC_REACTIVE_BLOCK_MARKER] === true
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// In-place bailout for a keyed memo row whose props are unchanged and which has
|
|
790
|
+
// no hook/context/effect dependencies. The caller has proven the key order is
|
|
791
|
+
// unchanged (hasSameKeyOrderPrefix), so the fiber keeps its position and can be
|
|
792
|
+
// reused WITHOUT createWorkInProgress — no allocation, and the retained subtree
|
|
793
|
+
// (including any reactive-dom-block subscriptions) is left untouched. Returns
|
|
794
|
+
// undefined when the row must re-render or has dependencies (handled by the
|
|
795
|
+
// general per-child path instead). Mirrors tryReuseDependencyFreeMemoBailout +
|
|
796
|
+
// getMemoBailoutFiber's in-place branch, but is safe to reuse a fiber with
|
|
797
|
+
// disposable resources because a stable position means nothing is disposed.
|
|
798
|
+
function tryReuseDependencyFreeKeyedMemoRow(
|
|
799
|
+
matched: Fiber,
|
|
800
|
+
child: ReactCompatElement,
|
|
801
|
+
): Fiber | undefined {
|
|
802
|
+
if (
|
|
803
|
+
matched.tag !== "memo" ||
|
|
804
|
+
matched.type !== child.type ||
|
|
805
|
+
matched.hasRefSubtree === true ||
|
|
806
|
+
matched.hydrateExisting === true
|
|
807
|
+
) {
|
|
808
|
+
return undefined;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const state = matched.memoizedState as MemoFiberState | undefined;
|
|
812
|
+
|
|
813
|
+
if (
|
|
814
|
+
state === undefined ||
|
|
815
|
+
state.hasDirtyInstanceDependencies !== false ||
|
|
816
|
+
state.hasUnflushedEffectDependencies !== false ||
|
|
817
|
+
state.hasRetainedInstanceDependencies !== false ||
|
|
818
|
+
!areMemoPropsEqual(
|
|
819
|
+
child.type as { compare?: (a: Record<string, unknown>, b: Record<string, unknown>) => boolean },
|
|
820
|
+
state.props,
|
|
821
|
+
child.props as Record<string, unknown>,
|
|
822
|
+
)
|
|
823
|
+
) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
matched.pendingProps = child.props;
|
|
828
|
+
matched.flags = NoFlags;
|
|
829
|
+
matched.subtreeFlags = NoFlags;
|
|
830
|
+
matched.childListChanged = false;
|
|
831
|
+
matched.subtreeChildListChanged = false;
|
|
832
|
+
matched.hostChildListChanged = false;
|
|
833
|
+
matched.child = getSkippedChild(matched);
|
|
834
|
+
return matched;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// In-place CHANGED-row update for a keyed memo row whose inner component is a
|
|
838
|
+
// compiler-marked static reactive block (returns its props verbatim as the block
|
|
839
|
+
// props). Instead of re-invoking the component to rebuild a throwaway block
|
|
840
|
+
// element, push the new props straight into the committed block's prop cell — the
|
|
841
|
+
// bound DOM updates via subscriptions, exactly as the normal block re-render path
|
|
842
|
+
// would, but without the memo/component render machinery. Same-order is proven by
|
|
843
|
+
// the caller, so the fiber is reused in place. Returns undefined when this is not
|
|
844
|
+
// a marked, dependency-free, single-block memo row (the general path handles it).
|
|
845
|
+
function tryCellUpdateStaticBlockMemoRow(
|
|
846
|
+
matched: Fiber,
|
|
847
|
+
child: ReactCompatElement,
|
|
848
|
+
): Fiber | undefined {
|
|
849
|
+
if (
|
|
850
|
+
matched.tag !== "memo" ||
|
|
851
|
+
matched.type !== child.type ||
|
|
852
|
+
matched.hasRefSubtree === true ||
|
|
853
|
+
matched.hydrateExisting === true ||
|
|
854
|
+
!isStaticReactiveBlockComponent((matched.type as { type?: unknown }).type)
|
|
855
|
+
) {
|
|
856
|
+
return undefined;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const state = matched.memoizedState as MemoFiberState | undefined;
|
|
860
|
+
|
|
861
|
+
if (
|
|
862
|
+
state === undefined ||
|
|
863
|
+
state.hasDirtyInstanceDependencies !== false ||
|
|
864
|
+
state.hasUnflushedEffectDependencies !== false ||
|
|
865
|
+
state.hasRetainedInstanceDependencies !== false
|
|
866
|
+
) {
|
|
867
|
+
return undefined;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Navigate memo -> component fiber -> reactive-dom-block fiber. The marked
|
|
871
|
+
// component renders exactly one static block, so anything else is unexpected.
|
|
872
|
+
const componentFiber = matched.child;
|
|
873
|
+
if (componentFiber === undefined || componentFiber.sibling !== undefined) {
|
|
874
|
+
return undefined;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const blockFiber = componentFiber.child;
|
|
878
|
+
if (
|
|
879
|
+
blockFiber === undefined ||
|
|
880
|
+
blockFiber.tag !== "reactive-dom-block" ||
|
|
881
|
+
blockFiber.sibling !== undefined
|
|
882
|
+
) {
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const blockState = blockFiber.stateNode as ReactiveDomBlockState | undefined;
|
|
887
|
+
if (blockState?.propCell === undefined) {
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Drive the bound DOM through the prop cell (the marker guarantees the block's
|
|
892
|
+
// props are the component's props verbatim), then reuse the memo fiber in place.
|
|
893
|
+
setReactivePropCell(blockState.propCell, child.props as Record<string, unknown>);
|
|
894
|
+
matched.memoizedState = { ...state, props: child.props as Record<string, unknown> };
|
|
895
|
+
matched.pendingProps = child.props;
|
|
896
|
+
matched.flags = NoFlags;
|
|
897
|
+
matched.subtreeFlags = NoFlags;
|
|
898
|
+
matched.childListChanged = false;
|
|
899
|
+
matched.subtreeChildListChanged = false;
|
|
900
|
+
matched.hostChildListChanged = false;
|
|
901
|
+
matched.child = getSkippedChild(matched);
|
|
902
|
+
return matched;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Fast path for a keyed list of memo-wrapped rows (e.g. `<RowMemo key={id} />`)
|
|
906
|
+
// whose key order is UNCHANGED between renders — the js-framework-benchmark
|
|
907
|
+
// "select row" and "partial update" shapes. Walks current fibers and new
|
|
908
|
+
// children in lockstep: unchanged rows bail in place (no allocation, no general
|
|
909
|
+
// keyed-reconcile machinery), changed rows re-render through the proven
|
|
910
|
+
// per-child path. Any reorder / insert / delete / non-memo child / length change
|
|
911
|
+
// makes it return undefined so the general reconcile takes over.
|
|
912
|
+
function reconcileKeyedMemoRowHostChildren(
|
|
913
|
+
parent: Fiber,
|
|
914
|
+
currentFirstChild: Fiber | undefined,
|
|
915
|
+
children: readonly ReactCompatNode[],
|
|
916
|
+
runtime: RootRuntime | undefined,
|
|
917
|
+
path: string,
|
|
918
|
+
options: FiberHydrationOptions,
|
|
919
|
+
): FiberReconcileResult | undefined {
|
|
920
|
+
if (
|
|
921
|
+
children.length === 0 ||
|
|
922
|
+
currentFirstChild === undefined ||
|
|
923
|
+
runtime === undefined ||
|
|
924
|
+
options.previousNodes !== undefined ||
|
|
925
|
+
!isKeyedMemoRowCandidate(children[0]) ||
|
|
926
|
+
!hasSameKeyOrderPrefix(currentFirstChild, children)
|
|
927
|
+
) {
|
|
928
|
+
return undefined;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
let currentKeyed: Fiber | undefined = currentFirstChild;
|
|
932
|
+
let first: Fiber | undefined;
|
|
933
|
+
let previous: Fiber | undefined;
|
|
934
|
+
let subtreeFlags = NoFlags;
|
|
935
|
+
let subtreeChildListChanged = false;
|
|
936
|
+
let hasRefSubtree = false;
|
|
937
|
+
let hasDisposableResources = false;
|
|
938
|
+
let dirtyChildren: Fiber[] | undefined;
|
|
939
|
+
|
|
940
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
941
|
+
const child = children[index];
|
|
942
|
+
|
|
943
|
+
if (currentKeyed === undefined || !isKeyedMemoRowCandidate(child)) {
|
|
944
|
+
return undefined;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const matched = currentKeyed;
|
|
948
|
+
currentKeyed = currentKeyed.sibling;
|
|
949
|
+
const childElement = child as ReactCompatElement;
|
|
950
|
+
|
|
951
|
+
let fiber =
|
|
952
|
+
tryReuseDependencyFreeKeyedMemoRow(matched, childElement) ??
|
|
953
|
+
tryCellUpdateStaticBlockMemoRow(matched, childElement);
|
|
954
|
+
if (fiber === undefined) {
|
|
955
|
+
const result = createHostFiber(
|
|
956
|
+
parent,
|
|
957
|
+
matched,
|
|
958
|
+
child,
|
|
959
|
+
childElement.key ?? undefined,
|
|
960
|
+
runtime,
|
|
961
|
+
getReconcileChildPath(path, child, index, options),
|
|
962
|
+
options,
|
|
963
|
+
false,
|
|
964
|
+
);
|
|
965
|
+
fiber = result.fiber;
|
|
966
|
+
|
|
967
|
+
if (fiber === undefined) {
|
|
968
|
+
return undefined;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (fiber !== matched && fiber.alternate !== matched) {
|
|
972
|
+
markOptimizedChildForDeletion(parent, matched);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (first === undefined) {
|
|
977
|
+
first = fiber;
|
|
978
|
+
} else if (previous !== undefined) {
|
|
979
|
+
previous.sibling = fiber;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
fiber.return = parent;
|
|
983
|
+
fiber.sibling = undefined;
|
|
984
|
+
if (fiber.hasRefSubtree) {
|
|
985
|
+
hasRefSubtree = true;
|
|
986
|
+
}
|
|
987
|
+
if (fiber.hasDisposableResources) {
|
|
988
|
+
hasDisposableResources = true;
|
|
989
|
+
}
|
|
990
|
+
subtreeFlags |= fiber.flags | fiber.subtreeFlags;
|
|
991
|
+
subtreeChildListChanged =
|
|
992
|
+
subtreeChildListChanged || fiber.childListChanged || fiber.subtreeChildListChanged;
|
|
993
|
+
if (
|
|
994
|
+
fiber.tag === "memo" &&
|
|
995
|
+
(fiber.stateNode === undefined || typeof fiber.stateNode === "number")
|
|
996
|
+
) {
|
|
997
|
+
fiber.stateNode = index;
|
|
998
|
+
}
|
|
999
|
+
if (hasHostCommitWork(fiber)) {
|
|
1000
|
+
(dirtyChildren ??= []).push(fiber);
|
|
1001
|
+
}
|
|
1002
|
+
previous = fiber;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (currentKeyed !== undefined) {
|
|
1006
|
+
return undefined;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
parent.hasRefSubtree = hasRefSubtree;
|
|
1010
|
+
parent.hasDisposableResources = hasDisposableResources;
|
|
1011
|
+
parent.subtreeFlags = subtreeFlags;
|
|
1012
|
+
parent.subtreeChildListChanged = subtreeChildListChanged;
|
|
1013
|
+
parent.childListChanged = false;
|
|
1014
|
+
recordDirtyChildCommitHints(parent, dirtyChildren);
|
|
1015
|
+
return { fiber: first, consumed: 0 };
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function canReuseDependencyFreeMemoAtKey(
|
|
1019
|
+
current: Fiber | undefined,
|
|
1020
|
+
node: ReactCompatElement,
|
|
1021
|
+
key: string,
|
|
1022
|
+
): boolean {
|
|
1023
|
+
return (
|
|
1024
|
+
current !== undefined &&
|
|
1025
|
+
current.key === key &&
|
|
1026
|
+
current.tag === "memo" &&
|
|
1027
|
+
current.type === node.type &&
|
|
1028
|
+
current.hasRefSubtree !== true &&
|
|
1029
|
+
current.hasDisposableResources !== true &&
|
|
1030
|
+
current.hydrateExisting !== true &&
|
|
1031
|
+
isMemoType(node.type)
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function tryReuseDependencyFreeMemoRemovalSuffix(
|
|
1036
|
+
parent: Fiber,
|
|
1037
|
+
children: readonly ReactCompatNode[],
|
|
1038
|
+
startIndex: number,
|
|
1039
|
+
removed: Fiber,
|
|
1040
|
+
matchedCurrent: Fiber,
|
|
1041
|
+
runtime: RootRuntime | undefined,
|
|
1042
|
+
options: FiberHydrationOptions,
|
|
1043
|
+
prefixFirst: Fiber | undefined,
|
|
1044
|
+
prefixPrevious: Fiber | undefined,
|
|
1045
|
+
consumed: number,
|
|
1046
|
+
): FiberReconcileResult | undefined {
|
|
1047
|
+
if (
|
|
1048
|
+
runtime === undefined ||
|
|
1049
|
+
options.previousNodes !== undefined ||
|
|
1050
|
+
removed.hasRefSubtree
|
|
1051
|
+
) {
|
|
1052
|
+
return undefined;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const suffix: Fiber[] = [];
|
|
1056
|
+
let current: Fiber | undefined = matchedCurrent;
|
|
1057
|
+
|
|
1058
|
+
for (let index = startIndex; index < children.length; index += 1) {
|
|
1059
|
+
const child = children[index];
|
|
1060
|
+
const key = getNodeKey(child);
|
|
1061
|
+
|
|
1062
|
+
if (key === undefined || !isReactCompatElement(child) || !isMemoType(child.type)) {
|
|
1063
|
+
return undefined;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (!canReuseDependencyFreeMemoAtKey(current, child, key)) {
|
|
1067
|
+
return undefined;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
if (current === undefined) {
|
|
1071
|
+
return undefined;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const matched: Fiber = current;
|
|
1075
|
+
const fiber = reuseDependencyFreeMemoFiber(matched, child);
|
|
1076
|
+
|
|
1077
|
+
if (fiber === undefined) {
|
|
1078
|
+
return undefined;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
suffix.push(fiber);
|
|
1082
|
+
current = matched.sibling;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (current !== undefined) {
|
|
1086
|
+
return undefined;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
let first = prefixFirst;
|
|
1090
|
+
let previous = prefixPrevious;
|
|
1091
|
+
|
|
1092
|
+
for (const fiber of suffix) {
|
|
1093
|
+
if (first === undefined) {
|
|
1094
|
+
first = fiber;
|
|
1095
|
+
} else if (previous !== undefined) {
|
|
1096
|
+
previous.sibling = fiber;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
fiber.return = parent;
|
|
1100
|
+
fiber.sibling = undefined;
|
|
1101
|
+
previous = fiber;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
parent.childListChanged = true;
|
|
1105
|
+
parent.deletions = [removed];
|
|
1106
|
+
markOptimizedChildForDeletion(parent, removed);
|
|
1107
|
+
return { fiber: first, consumed };
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function reuseDependencyFreeMemoFiber(
|
|
1111
|
+
current: Fiber,
|
|
1112
|
+
node: ReactCompatElement,
|
|
1113
|
+
): Fiber | undefined {
|
|
1114
|
+
if (!isMemoType(node.type)) {
|
|
1115
|
+
return undefined;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const previousMemoState = current.memoizedState as MemoFiberState | undefined;
|
|
1119
|
+
|
|
1120
|
+
if (
|
|
1121
|
+
previousMemoState === undefined ||
|
|
1122
|
+
previousMemoState.hasDirtyInstanceDependencies !== false ||
|
|
1123
|
+
previousMemoState.hasUnflushedEffectDependencies !== false ||
|
|
1124
|
+
previousMemoState.hasRetainedInstanceDependencies !== false ||
|
|
1125
|
+
!areMemoPropsEqual(node.type, previousMemoState.props, node.props)
|
|
1126
|
+
) {
|
|
1127
|
+
return undefined;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
current.pendingProps = node.props;
|
|
1131
|
+
current.flags = NoFlags;
|
|
1132
|
+
current.subtreeFlags = NoFlags;
|
|
1133
|
+
current.childListChanged = false;
|
|
1134
|
+
current.subtreeChildListChanged = false;
|
|
1135
|
+
current.hostChildListChanged = false;
|
|
1136
|
+
current.type = node.type;
|
|
1137
|
+
current.child = getSkippedChild(current);
|
|
1138
|
+
current.memoizedState = previousMemoState;
|
|
1139
|
+
return current;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
622
1142
|
function canStoreAppendSuffixCommitHint(parent: Fiber): boolean {
|
|
623
1143
|
return (
|
|
624
1144
|
parent.tag === "fragment" ||
|
|
@@ -712,6 +1232,7 @@ function getReusableKeyedRowHostFiber(
|
|
|
712
1232
|
current.subtreeChildListChanged = false;
|
|
713
1233
|
current.hostChildListChanged = false;
|
|
714
1234
|
current.hasRefSubtree = false;
|
|
1235
|
+
current.hasDisposableResources = false;
|
|
715
1236
|
return current;
|
|
716
1237
|
}
|
|
717
1238
|
|
|
@@ -793,6 +1314,7 @@ function createKeyedRowHostFiber(
|
|
|
793
1314
|
fiber.pendingProps = node.props;
|
|
794
1315
|
fiber.hostChildListChanged = false;
|
|
795
1316
|
fiber.hasRefSubtree = false;
|
|
1317
|
+
fiber.hasDisposableResources = false;
|
|
796
1318
|
|
|
797
1319
|
if (current === undefined || fiber.alternate !== current) {
|
|
798
1320
|
fiber.flags |= Placement;
|
|
@@ -956,6 +1478,9 @@ function bubbleHostChild(parent: Fiber, child: Fiber): void {
|
|
|
956
1478
|
if (child.hasRefSubtree) {
|
|
957
1479
|
parent.hasRefSubtree = true;
|
|
958
1480
|
}
|
|
1481
|
+
if (child.hasDisposableResources) {
|
|
1482
|
+
parent.hasDisposableResources = true;
|
|
1483
|
+
}
|
|
959
1484
|
parent.subtreeFlags |= child.flags | child.subtreeFlags;
|
|
960
1485
|
parent.subtreeChildListChanged =
|
|
961
1486
|
parent.subtreeChildListChanged ||
|
|
@@ -963,8 +1488,21 @@ function bubbleHostChild(parent: Fiber, child: Fiber): void {
|
|
|
963
1488
|
child.subtreeChildListChanged;
|
|
964
1489
|
}
|
|
965
1490
|
|
|
1491
|
+
function recordDirtyChildCommitHints(
|
|
1492
|
+
parent: Fiber,
|
|
1493
|
+
dirtyChildren: Fiber[] | undefined,
|
|
1494
|
+
): void {
|
|
1495
|
+
if (parent.childListChanged || dirtyChildren === undefined || dirtyChildren.length === 0) {
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Reuse the effect-list slot only when there are no child-list deletions.
|
|
1500
|
+
parent.deletions = dirtyChildren;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
966
1503
|
function resetFiberRefSubtree(fiber: Fiber): void {
|
|
967
1504
|
fiber.hasRefSubtree = false;
|
|
1505
|
+
fiber.hasDisposableResources = false;
|
|
968
1506
|
}
|
|
969
1507
|
|
|
970
1508
|
function includeNodeRef(fiber: Fiber, node: ReactCompatNode): void {
|
|
@@ -1142,6 +1680,13 @@ function createHostFiberImpl(
|
|
|
1142
1680
|
return { fiber: undefined, consumed: 0 };
|
|
1143
1681
|
}
|
|
1144
1682
|
|
|
1683
|
+
// Host elements (string type) are by far the most common node. Dispatch them
|
|
1684
|
+
// before the component-type checks below, none of which a string can match,
|
|
1685
|
+
// so each reconciled host element skips ~12 type comparisons / probes.
|
|
1686
|
+
if (typeof node.type === "string") {
|
|
1687
|
+
return createHostComponentFiber(parent, current, node, key, runtime, path, options);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1145
1690
|
if (node.type === Fragment) {
|
|
1146
1691
|
const fiber =
|
|
1147
1692
|
current?.tag === "fragment"
|
|
@@ -1269,6 +1814,37 @@ function createHostFiberImpl(
|
|
|
1269
1814
|
return { fiber, consumed: options.previousNodes?.length ?? 0 };
|
|
1270
1815
|
}
|
|
1271
1816
|
|
|
1817
|
+
if (node.type === REACTIVE_DOM_BLOCK_TYPE) {
|
|
1818
|
+
const blockProps = (node.props as unknown as ReactiveDomBlockProps).blockProps;
|
|
1819
|
+
if (current?.tag === "reactive-dom-block") {
|
|
1820
|
+
// Re-render: reuse the committed DOM/subscriptions and push the new props
|
|
1821
|
+
// into the prop cell instead of re-running render(). Bound text/attributes
|
|
1822
|
+
// update via their reactive subscriptions; the subtree is never reconciled.
|
|
1823
|
+
const fiber = createWorkInProgress(current, node.props);
|
|
1824
|
+
fiber.type = node.type;
|
|
1825
|
+
fiber.hasDisposableResources = true;
|
|
1826
|
+
const previousState = current.stateNode as ReactiveDomBlockState | undefined;
|
|
1827
|
+
if (previousState?.propCell !== undefined && blockProps !== undefined) {
|
|
1828
|
+
setReactivePropCell(previousState.propCell, blockProps);
|
|
1829
|
+
}
|
|
1830
|
+
fiber.stateNode = previousState;
|
|
1831
|
+
return { fiber, consumed: options.previousNodes?.length ?? 0 };
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const fiber = createFiber("reactive-dom-block", node.props, key);
|
|
1835
|
+
fiber.type = node.type;
|
|
1836
|
+
fiber.hasDisposableResources = true;
|
|
1837
|
+
const render = (node.props as unknown as ReactiveDomBlockProps).render;
|
|
1838
|
+
if (blockProps !== undefined) {
|
|
1839
|
+
const propCell = createReactivePropCell(blockProps);
|
|
1840
|
+
const result = render(createReactivePropProxy(propCell));
|
|
1841
|
+
fiber.stateNode = { node: result.node, dispose: result.dispose, propCell };
|
|
1842
|
+
} else {
|
|
1843
|
+
fiber.stateNode = (render as () => ReactiveDomBlockResult)();
|
|
1844
|
+
}
|
|
1845
|
+
return { fiber, consumed: options.previousNodes?.length ?? 0 };
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1272
1848
|
if (isReactCompatProvider(node.type)) {
|
|
1273
1849
|
const fiber =
|
|
1274
1850
|
current?.tag === "context-provider" && current.type === node.type
|
|
@@ -1390,8 +1966,11 @@ function createHostFiberImpl(
|
|
|
1390
1966
|
fiber.type = memoType;
|
|
1391
1967
|
|
|
1392
1968
|
const renderedElement: ReactCompatElement = {
|
|
1393
|
-
|
|
1969
|
+
$$typeof: node.$$typeof,
|
|
1394
1970
|
type: memoType.type,
|
|
1971
|
+
key: node.key,
|
|
1972
|
+
ref: node.ref,
|
|
1973
|
+
props: node.props,
|
|
1395
1974
|
};
|
|
1396
1975
|
const childResult = createHostFiber(
|
|
1397
1976
|
fiber,
|
|
@@ -1408,7 +1987,7 @@ function createHostFiberImpl(
|
|
|
1408
1987
|
fiber.child.sibling = undefined;
|
|
1409
1988
|
bubbleHostChild(fiber, fiber.child);
|
|
1410
1989
|
}
|
|
1411
|
-
const instanceKeys =
|
|
1990
|
+
const instanceKeys = collectMemoInstanceKeys(runtime, memoPath);
|
|
1412
1991
|
const hasClassDescendant = hasClassComponentDescendant(fiber.child);
|
|
1413
1992
|
fiber.memoizedState = {
|
|
1414
1993
|
props: node.props as Record<string, unknown>,
|
|
@@ -1439,8 +2018,11 @@ function createHostFiberImpl(
|
|
|
1439
2018
|
|
|
1440
2019
|
if (lazyType.status === "resolved" && lazyType.resolved !== undefined) {
|
|
1441
2020
|
const renderedElement: ReactCompatElement = {
|
|
1442
|
-
|
|
2021
|
+
$$typeof: node.$$typeof,
|
|
1443
2022
|
type: lazyType.resolved,
|
|
2023
|
+
key: node.key,
|
|
2024
|
+
ref: node.ref,
|
|
2025
|
+
props: node.props,
|
|
1444
2026
|
};
|
|
1445
2027
|
const childResult = createHostFiber(
|
|
1446
2028
|
fiber,
|
|
@@ -1625,61 +2207,95 @@ function createHostFiberImpl(
|
|
|
1625
2207
|
const instanceKeys = collectInstanceKeys(runtime, path);
|
|
1626
2208
|
fiber.stateNode = {
|
|
1627
2209
|
element: node,
|
|
1628
|
-
props:
|
|
2210
|
+
props: node.props as Record<string, unknown>,
|
|
1629
2211
|
instanceKeys,
|
|
1630
2212
|
hasContextDependencies: hasContextDependency(runtime, instanceKeys),
|
|
1631
2213
|
} satisfies FunctionFiberState;
|
|
1632
2214
|
return { fiber, consumed: childResult.consumed };
|
|
1633
2215
|
}
|
|
1634
2216
|
|
|
2217
|
+
return { fiber: undefined, consumed: 0 };
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// The host-component reconcile, split out of createHostFiberImpl so host
|
|
2221
|
+
// elements can be dispatched before the component-type checks. Only called with
|
|
2222
|
+
// a string element type.
|
|
2223
|
+
function createHostComponentFiber(
|
|
2224
|
+
parent: Fiber,
|
|
2225
|
+
current: Fiber | undefined,
|
|
2226
|
+
node: ReactCompatElement,
|
|
2227
|
+
key: string | undefined,
|
|
2228
|
+
runtime: RootRuntime | undefined,
|
|
2229
|
+
path: string,
|
|
2230
|
+
options: FiberHydrationOptions,
|
|
2231
|
+
): FiberReconcileResult {
|
|
1635
2232
|
if (typeof node.type !== "string") {
|
|
1636
2233
|
return { fiber: undefined, consumed: 0 };
|
|
1637
2234
|
}
|
|
1638
2235
|
|
|
2236
|
+
const initialHostOnlyFiber = tryCreateInitialHostOnlyFiber(
|
|
2237
|
+
current,
|
|
2238
|
+
node,
|
|
2239
|
+
key,
|
|
2240
|
+
options,
|
|
2241
|
+
);
|
|
2242
|
+
if (initialHostOnlyFiber !== undefined) {
|
|
2243
|
+
return { fiber: initialHostOnlyFiber, consumed: 0 };
|
|
2244
|
+
}
|
|
2245
|
+
|
|
1639
2246
|
const elementNamespace = namespaceForHostElement(options.namespace ?? "html", node.type);
|
|
1640
2247
|
const childNamespace = namespaceForHostChildren(elementNamespace, node.type);
|
|
2248
|
+
const reusableCurrent =
|
|
2249
|
+
current?.tag === "host-component" && current.type === node.type ? current : undefined;
|
|
1641
2250
|
const fiber =
|
|
1642
|
-
|
|
1643
|
-
? createWorkInProgress(
|
|
2251
|
+
reusableCurrent !== undefined
|
|
2252
|
+
? createWorkInProgress(reusableCurrent, node.props)
|
|
1644
2253
|
: createFiber("host-component", node.props, key);
|
|
2254
|
+
// The hydration node bookkeeping only matters when hydrating. Skipping it for
|
|
2255
|
+
// the common (non-hydration) render avoids an isHostElement(undefined) probe
|
|
2256
|
+
// and the mismatch checks on every reconciled element.
|
|
1645
2257
|
const existing = options.previousNodes?.[0];
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2258
|
+
let existingElement: HostElement | undefined;
|
|
2259
|
+
let tagMatches = false;
|
|
2260
|
+
if (options.previousNodes !== undefined) {
|
|
2261
|
+
existingElement = isHostElement(existing) ? existing : undefined;
|
|
2262
|
+
tagMatches =
|
|
2263
|
+
existingElement !== undefined &&
|
|
2264
|
+
hostElementMatches(existingElement, node.type, elementNamespace);
|
|
2265
|
+
|
|
2266
|
+
if (existing === undefined) {
|
|
2267
|
+
reportMissingHydrationNode(options, path);
|
|
2268
|
+
} else if (!isHostElement(existing)) {
|
|
2269
|
+
reportHydrationNodeTypeMismatch(options, path, `<${node.type}>`, existing);
|
|
2270
|
+
}
|
|
1656
2271
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
2272
|
+
if (existingElement !== undefined && !tagMatches) {
|
|
2273
|
+
reportRecoverable(
|
|
2274
|
+
options,
|
|
2275
|
+
"tag",
|
|
2276
|
+
path,
|
|
2277
|
+
new Error(
|
|
2278
|
+
`Hydration tag mismatch: expected <${node.type}> but found <${existingElement.tagName.toLowerCase()}>.`,
|
|
2279
|
+
),
|
|
2280
|
+
);
|
|
2281
|
+
reportElementTextMismatch(options, `${path}.c`, existingElement, node.props.children);
|
|
2282
|
+
}
|
|
1667
2283
|
}
|
|
1668
2284
|
|
|
1669
2285
|
fiber.type = node.type;
|
|
2286
|
+
// When reusing a same-type current fiber, its stateNode was created for this
|
|
2287
|
+
// exact tag and namespace (same tree position), so the hostElementMatches
|
|
2288
|
+
// re-check (localName + namespaceURI reads) is redundant.
|
|
1670
2289
|
fiber.stateNode =
|
|
1671
2290
|
tagMatches
|
|
1672
2291
|
? existingElement
|
|
1673
|
-
:
|
|
1674
|
-
|
|
1675
|
-
isHostElement(current.stateNode) &&
|
|
1676
|
-
hostElementMatches(current.stateNode, node.type, elementNamespace)
|
|
1677
|
-
? current.stateNode
|
|
2292
|
+
: reusableCurrent !== undefined && isHostElement(reusableCurrent.stateNode)
|
|
2293
|
+
? reusableCurrent.stateNode
|
|
1678
2294
|
: createHostElement(getDocumentRef(options), node.type, options.namespace ?? "html");
|
|
1679
|
-
fiber.hydrateExisting = tagMatches
|
|
2295
|
+
fiber.hydrateExisting = tagMatches;
|
|
1680
2296
|
const previousChildNodes =
|
|
1681
2297
|
tagMatches && existingElement !== undefined
|
|
1682
|
-
? Array.from(existingElement.childNodes)
|
|
2298
|
+
? Array.from((existingElement as Element).childNodes)
|
|
1683
2299
|
: undefined;
|
|
1684
2300
|
const directTextChild =
|
|
1685
2301
|
shouldUseDirectHostTextChild() && previousChildNodes === undefined
|
|
@@ -1713,11 +2329,7 @@ function createHostFiberImpl(
|
|
|
1713
2329
|
node.props.children as ReactCompatNode,
|
|
1714
2330
|
runtime,
|
|
1715
2331
|
`${path}.c`,
|
|
1716
|
-
|
|
1717
|
-
...options,
|
|
1718
|
-
namespace: childNamespace,
|
|
1719
|
-
...(previousChildNodes === undefined ? {} : { previousNodes: previousChildNodes }),
|
|
1720
|
-
},
|
|
2332
|
+
getHostChildFiberOptions(options, childNamespace, previousChildNodes),
|
|
1721
2333
|
);
|
|
1722
2334
|
fiber.child = childResult.fiber;
|
|
1723
2335
|
if (previousChildNodes !== undefined) {
|
|
@@ -1737,6 +2349,185 @@ function isFunctionComponentType(value: unknown): value is (
|
|
|
1737
2349
|
);
|
|
1738
2350
|
}
|
|
1739
2351
|
|
|
2352
|
+
function tryCreateInitialHostOnlyFiber(
|
|
2353
|
+
current: Fiber | undefined,
|
|
2354
|
+
node: ReactCompatElement,
|
|
2355
|
+
key: string | undefined,
|
|
2356
|
+
options: FiberHydrationOptions,
|
|
2357
|
+
): Fiber | undefined {
|
|
2358
|
+
if (
|
|
2359
|
+
current !== undefined ||
|
|
2360
|
+
options.previousNodes !== undefined ||
|
|
2361
|
+
!shouldUseDirectHostTextChild() ||
|
|
2362
|
+
typeof node.type !== "string" ||
|
|
2363
|
+
!canCreateInitialHostOnlyNode(node)
|
|
2364
|
+
) {
|
|
2365
|
+
return undefined;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
return createInitialHostOnlyElementFiber(
|
|
2369
|
+
node,
|
|
2370
|
+
key,
|
|
2371
|
+
options.namespace ?? "html",
|
|
2372
|
+
getDocumentRef(options),
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
function createInitialHostOnlyElementFiber(
|
|
2377
|
+
node: ReactCompatElement,
|
|
2378
|
+
key: string | undefined,
|
|
2379
|
+
namespace: HostNamespace,
|
|
2380
|
+
documentRef: Document | CustomHostDocument,
|
|
2381
|
+
): Fiber {
|
|
2382
|
+
const elementType = node.type as string;
|
|
2383
|
+
const elementNamespace = namespaceForHostElement(namespace, elementType);
|
|
2384
|
+
const childNamespace = namespaceForHostChildren(elementNamespace, elementType);
|
|
2385
|
+
const fiber = createFiber("host-component", node.props, key);
|
|
2386
|
+
fiber.type = elementType;
|
|
2387
|
+
fiber.stateNode = createHostElement(documentRef, elementType, namespace);
|
|
2388
|
+
fiber.flags |= Placement;
|
|
2389
|
+
fiber.hostChildListChanged = true;
|
|
2390
|
+
|
|
2391
|
+
if (getDirectHostTextChild(node.props.children) === undefined) {
|
|
2392
|
+
fiber.child = createInitialHostOnlyChildList(
|
|
2393
|
+
fiber,
|
|
2394
|
+
node.props.children as ReactCompatNode,
|
|
2395
|
+
childNamespace,
|
|
2396
|
+
documentRef,
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
return fiber;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
function createInitialHostOnlyChildList(
|
|
2404
|
+
parent: Fiber,
|
|
2405
|
+
children: ReactCompatNode,
|
|
2406
|
+
namespace: HostNamespace,
|
|
2407
|
+
documentRef: Document | CustomHostDocument,
|
|
2408
|
+
): Fiber | undefined {
|
|
2409
|
+
const normalized = normalizeChildren(children);
|
|
2410
|
+
let first: Fiber | undefined;
|
|
2411
|
+
let previous: Fiber | undefined;
|
|
2412
|
+
|
|
2413
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
2414
|
+
const child = normalized[index];
|
|
2415
|
+
const fiber = createInitialHostOnlyChildFiber(child, namespace, documentRef);
|
|
2416
|
+
|
|
2417
|
+
if (fiber === undefined) {
|
|
2418
|
+
continue;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
fiber.return = parent;
|
|
2422
|
+
fiber.memoizedState = index;
|
|
2423
|
+
|
|
2424
|
+
if (first === undefined) {
|
|
2425
|
+
first = fiber;
|
|
2426
|
+
} else if (previous !== undefined) {
|
|
2427
|
+
previous.sibling = fiber;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
bubbleHostChild(parent, fiber);
|
|
2431
|
+
previous = fiber;
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
return first;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
function createInitialHostOnlyChildFiber(
|
|
2438
|
+
node: ReactCompatNode,
|
|
2439
|
+
namespace: HostNamespace,
|
|
2440
|
+
documentRef: Document | CustomHostDocument,
|
|
2441
|
+
): Fiber | undefined {
|
|
2442
|
+
if (node === null || node === undefined || typeof node === "boolean") {
|
|
2443
|
+
return undefined;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
2447
|
+
const fiber = createFiber("host-text", String(node));
|
|
2448
|
+
fiber.stateNode = createHostTextNode(documentRef);
|
|
2449
|
+
fiber.flags |= Placement;
|
|
2450
|
+
return fiber;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
if (Array.isArray(node)) {
|
|
2454
|
+
const fiber = createFiber("fragment", node);
|
|
2455
|
+
fiber.child = createInitialHostOnlyChildList(fiber, node, namespace, documentRef);
|
|
2456
|
+
return fiber.child === undefined ? undefined : fiber;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
if (!isReactCompatElement(node) || typeof node.type !== "string") {
|
|
2460
|
+
return undefined;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
return createInitialHostOnlyElementFiber(
|
|
2464
|
+
node,
|
|
2465
|
+
node.key === null ? undefined : node.key,
|
|
2466
|
+
namespace,
|
|
2467
|
+
documentRef,
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
function canCreateInitialHostOnlyNode(node: ReactCompatNode): boolean {
|
|
2472
|
+
if (
|
|
2473
|
+
node === null ||
|
|
2474
|
+
node === undefined ||
|
|
2475
|
+
typeof node === "boolean" ||
|
|
2476
|
+
typeof node === "string" ||
|
|
2477
|
+
typeof node === "number"
|
|
2478
|
+
) {
|
|
2479
|
+
return true;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
if (Array.isArray(node)) {
|
|
2483
|
+
return node.every(canCreateInitialHostOnlyNode);
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
if (
|
|
2487
|
+
!isReactCompatElement(node) ||
|
|
2488
|
+
typeof node.type !== "string" ||
|
|
2489
|
+
node.ref !== null ||
|
|
2490
|
+
hasInitialHostOnlyExcludedProps(node.props)
|
|
2491
|
+
) {
|
|
2492
|
+
return false;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
return canCreateInitialHostOnlyNode(node.props.children as ReactCompatNode);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
function hasInitialHostOnlyExcludedProps(props: Record<string, unknown>): boolean {
|
|
2499
|
+
return (
|
|
2500
|
+
hasOwnProperty.call(props, REACTIVE_TEXT_BINDING_META) ||
|
|
2501
|
+
props.dangerouslySetInnerHTML !== undefined ||
|
|
2502
|
+
props.contentEditable === true ||
|
|
2503
|
+
props.suppressContentEditableWarning === true ||
|
|
2504
|
+
props.value !== undefined ||
|
|
2505
|
+
props.defaultValue !== undefined ||
|
|
2506
|
+
props.checked !== undefined ||
|
|
2507
|
+
props.defaultChecked !== undefined
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
function getHostChildFiberOptions(
|
|
2512
|
+
options: FiberHydrationOptions,
|
|
2513
|
+
namespace: HostNamespace,
|
|
2514
|
+
previousNodes: readonly Node[] | undefined,
|
|
2515
|
+
): FiberHydrationOptions {
|
|
2516
|
+
const namespaceUnchanged =
|
|
2517
|
+
options.namespace === namespace ||
|
|
2518
|
+
(options.namespace === undefined && namespace === "html");
|
|
2519
|
+
|
|
2520
|
+
if (previousNodes === undefined && namespaceUnchanged) {
|
|
2521
|
+
return options;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
return {
|
|
2525
|
+
...options,
|
|
2526
|
+
namespace,
|
|
2527
|
+
...(previousNodes === undefined ? {} : { previousNodes }),
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
|
|
1740
2531
|
function commitHostChildren(
|
|
1741
2532
|
fiber: Fiber | undefined,
|
|
1742
2533
|
parent: ParentNode,
|
|
@@ -1778,6 +2569,49 @@ function commitHostDirtyChildren(
|
|
|
1778
2569
|
}
|
|
1779
2570
|
}
|
|
1780
2571
|
|
|
2572
|
+
function commitHostDirtyChildrenOf(
|
|
2573
|
+
owner: Fiber,
|
|
2574
|
+
fiber: Fiber | undefined,
|
|
2575
|
+
parent: ParentNode,
|
|
2576
|
+
eventRoot: Element,
|
|
2577
|
+
path: string,
|
|
2578
|
+
options: RenderOptions = {},
|
|
2579
|
+
): void {
|
|
2580
|
+
const dirtyChildren = readDirtyChildCommitHints(owner);
|
|
2581
|
+
|
|
2582
|
+
if (dirtyChildren === undefined) {
|
|
2583
|
+
commitHostDirtyChildren(fiber, parent, eventRoot, path, options);
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
for (let index = 0; index < dirtyChildren.length; index += 1) {
|
|
2588
|
+
const dirtyChild = dirtyChildren[index];
|
|
2589
|
+
|
|
2590
|
+
if (dirtyChild !== undefined && hasHostCommitWork(dirtyChild)) {
|
|
2591
|
+
commitHostDirtyFiber(
|
|
2592
|
+
dirtyChild,
|
|
2593
|
+
parent,
|
|
2594
|
+
eventRoot,
|
|
2595
|
+
joinCommitPath(path, String(getDirtyChildCommitIndex(dirtyChild, index))),
|
|
2596
|
+
options,
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
owner.deletions = undefined;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function readDirtyChildCommitHints(fiber: Fiber): Fiber[] | undefined {
|
|
2604
|
+
return fiber.childListChanged ? undefined : fiber.deletions;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
function getDirtyChildCommitIndex(fiber: Fiber, fallback: number): number {
|
|
2608
|
+
if (typeof fiber.memoizedState === "number") {
|
|
2609
|
+
return fiber.memoizedState;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
return typeof fiber.stateNode === "number" ? fiber.stateNode : fallback;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
1781
2615
|
function commitHostDirtyFiber(
|
|
1782
2616
|
fiber: Fiber,
|
|
1783
2617
|
parent: ParentNode,
|
|
@@ -1798,6 +2632,7 @@ function commitHostDirtyFiber(
|
|
|
1798
2632
|
return;
|
|
1799
2633
|
}
|
|
1800
2634
|
|
|
2635
|
+
const isDomElement = isDomHostElement(element);
|
|
1801
2636
|
const props = fiber.pendingProps as Record<string, unknown>;
|
|
1802
2637
|
const previousProps = fiber.memoizedProps as Record<string, unknown> | undefined;
|
|
1803
2638
|
const directTextChild =
|
|
@@ -1823,7 +2658,7 @@ function commitHostDirtyFiber(
|
|
|
1823
2658
|
fiber.hydrateExisting !== true &&
|
|
1824
2659
|
isRowTextOnlyUpdate(fiber.memoizedProps, props);
|
|
1825
2660
|
|
|
1826
|
-
if (
|
|
2661
|
+
if (isDomElement && !propsAreUnchanged && !propsAreChildrenOnly && !textOnlyRowUpdate) {
|
|
1827
2662
|
applyProps(element, props, path, {
|
|
1828
2663
|
...options,
|
|
1829
2664
|
eventRoot,
|
|
@@ -1841,16 +2676,16 @@ function commitHostDirtyFiber(
|
|
|
1841
2676
|
) {
|
|
1842
2677
|
const childNodes = commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
|
|
1843
2678
|
if (
|
|
1844
|
-
!(
|
|
1845
|
-
!(
|
|
2679
|
+
!(isDomElement && childNodes.length === 0 && committedPortalContainers.has(element)) &&
|
|
2680
|
+
!(isDomElement && shouldPreserveContentEditableChildren(element, props, childNodes))
|
|
1846
2681
|
) {
|
|
1847
2682
|
syncChildNodes(element as ParentNode, childNodes);
|
|
1848
2683
|
}
|
|
1849
2684
|
} else if (fiber.subtreeFlags !== NoFlags) {
|
|
1850
|
-
|
|
2685
|
+
commitHostDirtyChildrenOf(fiber, fiber.child, element, eventRoot, `${path}.c`, options);
|
|
1851
2686
|
}
|
|
1852
2687
|
|
|
1853
|
-
if (
|
|
2688
|
+
if (isDomElement && isFormHostType(fiber.type)) {
|
|
1854
2689
|
applyPostChildFormProps(element, props, previousProps);
|
|
1855
2690
|
}
|
|
1856
2691
|
applyChangedRef(previousProps?.ref, props.ref, element);
|
|
@@ -1872,7 +2707,7 @@ function commitHostDirtyFiber(
|
|
|
1872
2707
|
: container instanceof Element
|
|
1873
2708
|
? container
|
|
1874
2709
|
: eventRoot;
|
|
1875
|
-
const portalOptions =
|
|
2710
|
+
const portalOptions = withPortalHostOptions(options, container);
|
|
1876
2711
|
|
|
1877
2712
|
if (
|
|
1878
2713
|
fiber.childListChanged ||
|
|
@@ -1890,7 +2725,8 @@ function commitHostDirtyFiber(
|
|
|
1890
2725
|
syncOwnedChildNodes(container as ParentNode, previousNodes, childNodes);
|
|
1891
2726
|
fiber.memoizedState = childNodes;
|
|
1892
2727
|
} else {
|
|
1893
|
-
|
|
2728
|
+
commitHostDirtyChildrenOf(
|
|
2729
|
+
fiber,
|
|
1894
2730
|
fiber.child,
|
|
1895
2731
|
container as ParentNode,
|
|
1896
2732
|
portalEventRoot,
|
|
@@ -1904,8 +2740,14 @@ function commitHostDirtyFiber(
|
|
|
1904
2740
|
return;
|
|
1905
2741
|
}
|
|
1906
2742
|
|
|
2743
|
+
if (fiber.tag === "reactive-dom-block") {
|
|
2744
|
+
fiber.memoizedProps = fiber.pendingProps;
|
|
2745
|
+
finishCommittedFiber(fiber);
|
|
2746
|
+
return;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
1907
2749
|
if (fiber.subtreeFlags !== NoFlags) {
|
|
1908
|
-
|
|
2750
|
+
commitHostDirtyChildrenOf(fiber, fiber.child, parent, eventRoot, path, options);
|
|
1909
2751
|
}
|
|
1910
2752
|
fiber.memoizedProps = fiber.pendingProps;
|
|
1911
2753
|
finishCommittedFiber(fiber);
|
|
@@ -2072,7 +2914,10 @@ function readAppendSuffixCommitHint(value: unknown): AppendSuffixCommitHint | un
|
|
|
2072
2914
|
}
|
|
2073
2915
|
|
|
2074
2916
|
function commitHostSingleRemoval(fiber: Fiber, parent: ParentNode): boolean {
|
|
2075
|
-
const removed =
|
|
2917
|
+
const removed =
|
|
2918
|
+
fiber.deletions?.length === 1
|
|
2919
|
+
? fiber.deletions[0]
|
|
2920
|
+
: getSingleRemovedFiber(fiber.alternate?.child, fiber.child);
|
|
2076
2921
|
|
|
2077
2922
|
if (removed === undefined) {
|
|
2078
2923
|
return false;
|
|
@@ -2089,6 +2934,10 @@ function commitHostSingleRemoval(fiber: Fiber, parent: ParentNode): boolean {
|
|
|
2089
2934
|
removedAny = true;
|
|
2090
2935
|
}
|
|
2091
2936
|
|
|
2937
|
+
if (removedAny) {
|
|
2938
|
+
fiber.deletions = undefined;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2092
2941
|
return removedAny;
|
|
2093
2942
|
}
|
|
2094
2943
|
|
|
@@ -2166,6 +3015,11 @@ function isSameFiberSlot(current: Fiber, next: Fiber): boolean {
|
|
|
2166
3015
|
}
|
|
2167
3016
|
|
|
2168
3017
|
function collectCommittedHostNodes(fiber: Fiber): Node[] {
|
|
3018
|
+
if (fiber.tag === "reactive-dom-block") {
|
|
3019
|
+
const node = getReactiveDomBlockNode(fiber.stateNode);
|
|
3020
|
+
return node === undefined ? [] : [node];
|
|
3021
|
+
}
|
|
3022
|
+
|
|
2169
3023
|
if (
|
|
2170
3024
|
(fiber.tag === "host-component" || fiber.tag === "host-text") &&
|
|
2171
3025
|
fiber.stateNode instanceof Node
|
|
@@ -2185,6 +3039,10 @@ function collectCommittedHostNodes(fiber: Fiber): Node[] {
|
|
|
2185
3039
|
}
|
|
2186
3040
|
|
|
2187
3041
|
function hasCommittedHostNode(fiber: Fiber): boolean {
|
|
3042
|
+
if (fiber.tag === "reactive-dom-block") {
|
|
3043
|
+
return getReactiveDomBlockNode(fiber.stateNode) !== undefined;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
2188
3046
|
if (
|
|
2189
3047
|
(fiber.tag === "host-component" || fiber.tag === "host-text") &&
|
|
2190
3048
|
fiber.stateNode instanceof Node
|
|
@@ -2249,6 +3107,13 @@ function commitHostFiber(
|
|
|
2249
3107
|
return [text];
|
|
2250
3108
|
}
|
|
2251
3109
|
|
|
3110
|
+
if (fiber.tag === "reactive-dom-block") {
|
|
3111
|
+
const node = getReactiveDomBlockNode(fiber.stateNode);
|
|
3112
|
+
fiber.memoizedProps = fiber.pendingProps;
|
|
3113
|
+
finishCommittedFiber(fiber);
|
|
3114
|
+
return node === undefined ? [] : [node];
|
|
3115
|
+
}
|
|
3116
|
+
|
|
2252
3117
|
if (fiber.tag === "host-component") {
|
|
2253
3118
|
const element = fiber.stateNode;
|
|
2254
3119
|
|
|
@@ -2269,6 +3134,7 @@ function commitHostFiber(
|
|
|
2269
3134
|
return [element];
|
|
2270
3135
|
}
|
|
2271
3136
|
|
|
3137
|
+
const isDomElement = isDomHostElement(element);
|
|
2272
3138
|
const props = fiber.pendingProps as Record<string, unknown>;
|
|
2273
3139
|
const previousProps = fiber.memoizedProps as Record<string, unknown> | undefined;
|
|
2274
3140
|
const directTextChild =
|
|
@@ -2294,7 +3160,7 @@ function commitHostFiber(
|
|
|
2294
3160
|
fiber.hydrateExisting !== true &&
|
|
2295
3161
|
isRowTextOnlyUpdate(fiber.memoizedProps, props);
|
|
2296
3162
|
|
|
2297
|
-
if (
|
|
3163
|
+
if (isDomElement && !propsAreUnchanged && !propsAreChildrenOnly && !textOnlyRowUpdate) {
|
|
2298
3164
|
applyProps(element, props, path, {
|
|
2299
3165
|
...options,
|
|
2300
3166
|
eventRoot,
|
|
@@ -2313,8 +3179,8 @@ function commitHostFiber(
|
|
|
2313
3179
|
) {
|
|
2314
3180
|
const childNodes = commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
|
|
2315
3181
|
if (
|
|
2316
|
-
!(
|
|
2317
|
-
!(
|
|
3182
|
+
!(isDomElement && childNodes.length === 0 && committedPortalContainers.has(element)) &&
|
|
3183
|
+
!(isDomElement && shouldPreserveContentEditableChildren(element, props, childNodes))
|
|
2318
3184
|
) {
|
|
2319
3185
|
syncChildNodes(element as ParentNode, childNodes);
|
|
2320
3186
|
}
|
|
@@ -2322,7 +3188,7 @@ function commitHostFiber(
|
|
|
2322
3188
|
commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
|
|
2323
3189
|
}
|
|
2324
3190
|
|
|
2325
|
-
if (
|
|
3191
|
+
if (isDomElement && isFormHostType(fiber.type)) {
|
|
2326
3192
|
applyPostChildFormProps(element, props, previousProps);
|
|
2327
3193
|
}
|
|
2328
3194
|
applyChangedRef(previousProps?.ref, props.ref, element);
|
|
@@ -2432,7 +3298,7 @@ function commitHostFiber(
|
|
|
2432
3298
|
: container instanceof Element
|
|
2433
3299
|
? container
|
|
2434
3300
|
: eventRoot;
|
|
2435
|
-
const portalOptions =
|
|
3301
|
+
const portalOptions = withPortalHostOptions(options, container);
|
|
2436
3302
|
const childNodes = commitHostChildren(
|
|
2437
3303
|
fiber.child,
|
|
2438
3304
|
container as ParentNode,
|
|
@@ -2460,6 +3326,36 @@ function finishCommittedFiber(fiber: Fiber): void {
|
|
|
2460
3326
|
fiber.hostChildListChanged = false;
|
|
2461
3327
|
}
|
|
2462
3328
|
|
|
3329
|
+
function getReactiveDomBlockNode(state: unknown): ChildNode | undefined {
|
|
3330
|
+
if (
|
|
3331
|
+
typeof state === "object" &&
|
|
3332
|
+
state !== null &&
|
|
3333
|
+
"node" in state &&
|
|
3334
|
+
(state as { node?: unknown }).node instanceof Node
|
|
3335
|
+
) {
|
|
3336
|
+
return (state as { node: ChildNode }).node;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
return undefined;
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
function disposeReactiveDomBlockState(
|
|
3343
|
+
state: unknown,
|
|
3344
|
+
seen: Set<unknown>,
|
|
3345
|
+
): void {
|
|
3346
|
+
if (typeof state !== "object" || state === null || seen.has(state)) {
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
seen.add(state);
|
|
3351
|
+
const dispose = (state as { dispose?: unknown }).dispose;
|
|
3352
|
+
|
|
3353
|
+
if (typeof dispose === "function") {
|
|
3354
|
+
dispose();
|
|
3355
|
+
(state as { dispose?: unknown }).dispose = undefined;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
2463
3359
|
function hasChildListMutation(fiber: Fiber): boolean {
|
|
2464
3360
|
return fiber.childListChanged || fiber.subtreeChildListChanged;
|
|
2465
3361
|
}
|
|
@@ -2659,6 +3555,13 @@ function getDirectHostTextChild(children: unknown): string | undefined {
|
|
|
2659
3555
|
: undefined;
|
|
2660
3556
|
}
|
|
2661
3557
|
|
|
3558
|
+
// Only these host tags carry post-child form value/checked semantics. Gating on
|
|
3559
|
+
// the (cheap, already-known) type string lets the commit skip applyPostChildFormProps
|
|
3560
|
+
// and its instanceof probes for every other element.
|
|
3561
|
+
function isFormHostType(type: unknown): boolean {
|
|
3562
|
+
return type === "input" || type === "textarea" || type === "select";
|
|
3563
|
+
}
|
|
3564
|
+
|
|
2662
3565
|
// This package has no Node type dependency; declare the minimal process
|
|
2663
3566
|
// shape needed for the literal process.env.NODE_ENV expression below.
|
|
2664
3567
|
declare const process: { env: Record<string, string | undefined> };
|
|
@@ -3138,13 +4041,26 @@ function tryReuseMemoBailout(
|
|
|
3138
4041
|
return undefined;
|
|
3139
4042
|
}
|
|
3140
4043
|
|
|
3141
|
-
|
|
3142
|
-
runtime,
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
canReuseCurrentFiber
|
|
3147
|
-
|
|
4044
|
+
if (memoStateNeedsActiveInstanceMark(previousMemoState)) {
|
|
4045
|
+
markActiveInstanceKeys(runtime, previousMemoState.instanceKeys);
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
const fiber =
|
|
4049
|
+
canReuseCurrentFiber &&
|
|
4050
|
+
current.hasRefSubtree !== true &&
|
|
4051
|
+
current.hasDisposableResources !== true &&
|
|
4052
|
+
current.hydrateExisting !== true
|
|
4053
|
+
? current
|
|
4054
|
+
: createWorkInProgress(current, node.props);
|
|
4055
|
+
|
|
4056
|
+
if (fiber === current) {
|
|
4057
|
+
current.pendingProps = node.props;
|
|
4058
|
+
current.flags = NoFlags;
|
|
4059
|
+
current.subtreeFlags = NoFlags;
|
|
4060
|
+
current.childListChanged = false;
|
|
4061
|
+
current.subtreeChildListChanged = false;
|
|
4062
|
+
current.hostChildListChanged = false;
|
|
4063
|
+
}
|
|
3148
4064
|
fiber.type = node.type;
|
|
3149
4065
|
fiber.child = getSkippedChild(current);
|
|
3150
4066
|
fiber.memoizedState = previousMemoState;
|
|
@@ -3336,7 +4252,11 @@ function createPortalFiber(
|
|
|
3336
4252
|
portal.children,
|
|
3337
4253
|
runtime,
|
|
3338
4254
|
`${path}.portal`,
|
|
3339
|
-
{
|
|
4255
|
+
{
|
|
4256
|
+
...options,
|
|
4257
|
+
documentRef: portal.container.ownerDocument,
|
|
4258
|
+
namespace: namespaceForPortalContainer(portal.container),
|
|
4259
|
+
},
|
|
3340
4260
|
);
|
|
3341
4261
|
fiber.child = childResult.fiber;
|
|
3342
4262
|
fiber.return = parent;
|
|
@@ -3511,6 +4431,35 @@ function collectInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
|
|
|
3511
4431
|
return collectRuntimeInstanceKeys(runtime, prefix);
|
|
3512
4432
|
}
|
|
3513
4433
|
|
|
4434
|
+
function collectMemoInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
|
|
4435
|
+
return readDependencyFreeMemoInstanceKey(runtime, prefix) === undefined
|
|
4436
|
+
? collectInstanceKeys(runtime, prefix)
|
|
4437
|
+
: emptyInstanceKeys;
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
function readDependencyFreeMemoInstanceKey(
|
|
4441
|
+
runtime: RootRuntime,
|
|
4442
|
+
prefix: string,
|
|
4443
|
+
): string | undefined {
|
|
4444
|
+
const keys = runtime.instanceKeysByPrefix.get(prefix);
|
|
4445
|
+
|
|
4446
|
+
if (keys === undefined || keys.size !== 1 || !keys.has(prefix)) {
|
|
4447
|
+
return undefined;
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
const instance = runtime.instances.get(prefix) as RuntimeInstanceLike | undefined;
|
|
4451
|
+
|
|
4452
|
+
if (
|
|
4453
|
+
instance === undefined ||
|
|
4454
|
+
instance.contextDependencies !== undefined ||
|
|
4455
|
+
(instance.hooks !== undefined && instance.hooks.length > 0)
|
|
4456
|
+
) {
|
|
4457
|
+
return undefined;
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
return prefix;
|
|
4461
|
+
}
|
|
4462
|
+
|
|
3514
4463
|
function markActiveInstanceKeys(runtime: RootRuntime, keys: readonly string[]): void {
|
|
3515
4464
|
for (const key of keys) {
|
|
3516
4465
|
runtime.activeInstanceKeys?.add(key);
|
|
@@ -3550,6 +4499,10 @@ function getMemoBailoutFiber(
|
|
|
3550
4499
|
state: MemoFiberState,
|
|
3551
4500
|
canReuseCurrentFiber: boolean,
|
|
3552
4501
|
): Fiber {
|
|
4502
|
+
if (memoStateNeedsActiveInstanceMark(state)) {
|
|
4503
|
+
markActiveInstanceKeys(runtime, state.instanceKeys);
|
|
4504
|
+
}
|
|
4505
|
+
|
|
3553
4506
|
if (canReuseCurrentFiber && canReuseMemoBailoutFiber(current, state)) {
|
|
3554
4507
|
current.pendingProps = pendingProps;
|
|
3555
4508
|
current.flags = NoFlags;
|
|
@@ -3561,9 +4514,6 @@ function getMemoBailoutFiber(
|
|
|
3561
4514
|
}
|
|
3562
4515
|
|
|
3563
4516
|
const fiber = createWorkInProgress(current, pendingProps);
|
|
3564
|
-
if (memoStateNeedsActiveInstanceMark(state)) {
|
|
3565
|
-
markActiveInstanceKeys(runtime, state.instanceKeys);
|
|
3566
|
-
}
|
|
3567
4517
|
return fiber;
|
|
3568
4518
|
}
|
|
3569
4519
|
|
|
@@ -3571,6 +4521,7 @@ function canReuseMemoBailoutFiber(current: Fiber, state: MemoFiberState): boolea
|
|
|
3571
4521
|
return (
|
|
3572
4522
|
state.hasRetainedInstanceDependencies === false &&
|
|
3573
4523
|
current.hasRefSubtree !== true &&
|
|
4524
|
+
current.hasDisposableResources !== true &&
|
|
3574
4525
|
current.hydrateExisting !== true
|
|
3575
4526
|
);
|
|
3576
4527
|
}
|
|
@@ -3782,10 +4733,11 @@ function isPortalHostContainer(value: unknown): value is ParentNode {
|
|
|
3782
4733
|
);
|
|
3783
4734
|
}
|
|
3784
4735
|
|
|
3785
|
-
function
|
|
4736
|
+
function withPortalHostOptions(
|
|
3786
4737
|
options: RenderOptions,
|
|
3787
4738
|
container: ParentNode,
|
|
3788
|
-
): RenderOptions & { documentRef?: Document | CustomHostDocument } {
|
|
4739
|
+
): RenderOptions & { documentRef?: Document | CustomHostDocument; namespace: HostNamespace } {
|
|
4740
|
+
const namespace = namespaceForPortalContainer(container);
|
|
3789
4741
|
const ownerDocument = (container as { ownerDocument?: unknown }).ownerDocument;
|
|
3790
4742
|
if (
|
|
3791
4743
|
typeof ownerDocument === "object" &&
|
|
@@ -3795,10 +4747,19 @@ function withPortalDocumentRef(
|
|
|
3795
4747
|
return {
|
|
3796
4748
|
...options,
|
|
3797
4749
|
documentRef: ownerDocument as Document | CustomHostDocument,
|
|
4750
|
+
namespace,
|
|
3798
4751
|
};
|
|
3799
4752
|
}
|
|
3800
4753
|
|
|
3801
|
-
return options;
|
|
4754
|
+
return { ...options, namespace };
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
function namespaceForPortalContainer(container: ParentNode): HostNamespace {
|
|
4758
|
+
if (container instanceof SVGElement && container.localName !== "foreignObject") {
|
|
4759
|
+
return "svg";
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
return "html";
|
|
3802
4763
|
}
|
|
3803
4764
|
|
|
3804
4765
|
function committedHostNodesFromState(state: unknown): Node[] {
|