@reckona/mreact-compat 0.0.82 → 0.0.84

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.
@@ -0,0 +1,1564 @@
1
+ import {
2
+ Activity,
3
+ ERROR_BOUNDARY_TYPE,
4
+ FORWARD_REF_TYPE,
5
+ Fragment,
6
+ LAZY_TYPE,
7
+ MEMO_TYPE,
8
+ Profiler,
9
+ STRICT_MODE_TYPE,
10
+ Suspense,
11
+ SuspenseList,
12
+ type ReactCompatElement,
13
+ type ReactCompatPortal,
14
+ isReactCompatElement,
15
+ isReactCompatPortal,
16
+ type ReactCompatNode,
17
+ } from "./element.js";
18
+ import {
19
+ isReactCompatConsumer,
20
+ isReactCompatProvider,
21
+ renderWithContextProvider,
22
+ useContext,
23
+ } from "./context.js";
24
+ import { applyPostChildFormProps, applyProps } from "./dom-props.js";
25
+ import { syncChildNodes, syncScopedChildNodes } from "./dom-children.js";
26
+ import { setLogicalEventParent } from "./host-event-binder.js";
27
+ import { createFiber, createWorkInProgress, type Fiber, type FiberRoot } from "./fiber.js";
28
+ import {
29
+ renderWithRootRuntime,
30
+ renderWithProfiler,
31
+ renderWithStrictMode,
32
+ restoreRuntimeSnapshot,
33
+ takeRuntimeSnapshot,
34
+ getDevToolsHookState,
35
+ type RootRuntime,
36
+ } from "./hooks.js";
37
+ import { isThenable } from "./thenable.js";
38
+ import {
39
+ isClassComponentType,
40
+ recoverClassComponentError,
41
+ renderClassComponentWithRuntime,
42
+ } from "./class-component.js";
43
+ import { areMemoPropsEqual, getPendingProps } from "./prop-comparison.js";
44
+ import {
45
+ reportElementTextMismatch,
46
+ reportExtraHydrationNodes,
47
+ reportHydrationNodeTypeMismatch,
48
+ reportMissingHydrationNode,
49
+ reportReactSuspenseServerError,
50
+ reportRecoverable,
51
+ type HydrationScope,
52
+ type RenderOptions,
53
+ withHydrationComponentStack,
54
+ } from "./hydration.js";
55
+
56
+ interface MemoFiberState {
57
+ props: Record<string, unknown>;
58
+ instanceKeys: string[];
59
+ }
60
+
61
+ interface SuspenseFiberState {
62
+ didSuspend: boolean;
63
+ }
64
+
65
+ interface FiberHydrationOptions extends RenderOptions {
66
+ previousNodes?: readonly Node[];
67
+ resumeId?: string;
68
+ consumeResumeMarkers?: boolean;
69
+ }
70
+
71
+ interface FiberReconcileResult {
72
+ fiber: Fiber | undefined;
73
+ consumed: number;
74
+ }
75
+
76
+ interface ReactSuspenseBoundary {
77
+ previousNodes?: Node[];
78
+ consumed: number;
79
+ serverError?: {
80
+ message: string;
81
+ componentStack?: string;
82
+ };
83
+ }
84
+
85
+ let suspensePrimaryRenderDepth = 0;
86
+
87
+ export function canRenderHostFiber(node: ReactCompatNode): boolean {
88
+ if (
89
+ node === null ||
90
+ node === undefined ||
91
+ typeof node === "boolean" ||
92
+ typeof node === "string" ||
93
+ typeof node === "number"
94
+ ) {
95
+ return true;
96
+ }
97
+
98
+ if (Array.isArray(node)) {
99
+ return node.every(canRenderHostFiber);
100
+ }
101
+
102
+ if (isReactCompatPortal(node)) {
103
+ return canRenderHostFiber(node.children);
104
+ }
105
+
106
+ if (!isReactCompatElement(node)) {
107
+ return false;
108
+ }
109
+
110
+ if (
111
+ node.type === Fragment ||
112
+ node.type === Profiler ||
113
+ node.type === STRICT_MODE_TYPE
114
+ ) {
115
+ return canRenderHostFiber(node.props.children as ReactCompatNode);
116
+ }
117
+
118
+ if (node.type === Activity) {
119
+ return (node.props as { mode?: unknown }).mode === "hidden"
120
+ ? true
121
+ : canRenderHostFiber(node.props.children as ReactCompatNode);
122
+ }
123
+
124
+ if (node.type === Suspense || node.type === SuspenseList) {
125
+ return true;
126
+ }
127
+
128
+ if (node.type === ERROR_BOUNDARY_TYPE) {
129
+ return true;
130
+ }
131
+
132
+ if (isReactCompatProvider(node.type)) {
133
+ return canRenderHostFiber(node.props.children as ReactCompatNode);
134
+ }
135
+
136
+ if (isReactCompatConsumer(node.type)) {
137
+ return true;
138
+ }
139
+
140
+ if (isForwardRefType(node.type)) {
141
+ return true;
142
+ }
143
+
144
+ if (isMemoType(node.type)) {
145
+ return true;
146
+ }
147
+
148
+ if (isLazyType(node.type)) {
149
+ return true;
150
+ }
151
+
152
+ if (isClassComponentType(node.type)) {
153
+ return true;
154
+ }
155
+
156
+ return (
157
+ typeof node.type === "string" &&
158
+ canRenderHostFiber(node.props.children as ReactCompatNode)
159
+ ) || isFunctionComponentType(node.type);
160
+ }
161
+
162
+ export function renderHostFiberRoot(
163
+ root: FiberRoot,
164
+ element: ReactCompatNode,
165
+ runtime?: RootRuntime,
166
+ options: FiberHydrationOptions = {},
167
+ ): Fiber {
168
+ const workInProgress = createWorkInProgress(root.current, { children: element });
169
+ const result = reconcileHostChild(
170
+ workInProgress,
171
+ root.current.child,
172
+ element,
173
+ runtime,
174
+ options.previousNodes === undefined ? "0" : "",
175
+ options,
176
+ );
177
+ workInProgress.child = result.fiber;
178
+ workInProgress.memoizedProps = { children: element };
179
+ return workInProgress;
180
+ }
181
+
182
+ export function renderHydratingHostFiberRoot(
183
+ root: FiberRoot,
184
+ element: ReactCompatNode,
185
+ runtime: RootRuntime,
186
+ scope: HydrationScope,
187
+ options: FiberHydrationOptions = {},
188
+ ): Fiber {
189
+ root.hydrationState = {
190
+ parent: scope.parent,
191
+ nextHydratableNode: scope.previousNodes[0] ?? null,
192
+ before: scope.before,
193
+ after: scope.after,
194
+ ...(options.resumeId === undefined ? {} : { resumeId: options.resumeId }),
195
+ };
196
+ return renderHostFiberRoot(root, element, runtime, {
197
+ ...options,
198
+ previousNodes: scope.previousNodes,
199
+ });
200
+ }
201
+
202
+ export function commitHostFiberRoot(
203
+ root: FiberRoot,
204
+ finishedWork: Fiber,
205
+ options: RenderOptions = {},
206
+ ): void {
207
+ const nodes = commitHostChildren(finishedWork.child, root.container, root.container, "0", options);
208
+ syncChildNodes(root.container, nodes);
209
+ }
210
+
211
+ export function commitHydratingHostFiberRoot(
212
+ root: FiberRoot,
213
+ finishedWork: Fiber,
214
+ scope: HydrationScope,
215
+ options: FiberHydrationOptions = {},
216
+ ): void {
217
+ const eventRoot = root.container;
218
+ const nodes = commitHostChildren(finishedWork.child, scope.parent, eventRoot, "", options);
219
+ syncScopedChildNodes(scope.parent, scope.before, scope.after, nodes);
220
+
221
+ if (options.consumeResumeMarkers === true) {
222
+ scope.before?.parentNode?.removeChild(scope.before);
223
+ scope.after?.parentNode?.removeChild(scope.after);
224
+ }
225
+ }
226
+
227
+ function reconcileHostChild(
228
+ parent: Fiber,
229
+ currentFirstChild: Fiber | undefined,
230
+ node: ReactCompatNode,
231
+ runtime: RootRuntime | undefined,
232
+ path: string,
233
+ options: FiberHydrationOptions = {},
234
+ ): FiberReconcileResult {
235
+ const children = normalizeChildren(node);
236
+ const existingByKey = collectExistingKeyedFibers(currentFirstChild);
237
+ let currentUnkeyed = currentFirstChild;
238
+ let first: Fiber | undefined;
239
+ let previous: Fiber | undefined;
240
+ let consumed = 0;
241
+
242
+ children.forEach((child, index) => {
243
+ const key = getNodeKey(child);
244
+ const matchedCurrent =
245
+ key === undefined ? currentUnkeyed : existingByKey.get(key);
246
+ const previousNodes =
247
+ options.previousNodes === undefined
248
+ ? undefined
249
+ : options.previousNodes.slice(consumed);
250
+ const result = createHostFiber(
251
+ parent,
252
+ matchedCurrent,
253
+ child,
254
+ key,
255
+ runtime,
256
+ joinPath(path, getNodePathSegment(child, index)),
257
+ previousNodes === undefined ? options : { ...options, previousNodes },
258
+ );
259
+ const fiber = result.fiber;
260
+
261
+ if (fiber === undefined) {
262
+ return;
263
+ }
264
+
265
+ if (key === undefined) {
266
+ currentUnkeyed = currentUnkeyed?.sibling;
267
+ }
268
+ consumed += result.consumed;
269
+
270
+ if (first === undefined) {
271
+ first = fiber;
272
+ } else if (previous !== undefined) {
273
+ previous.sibling = fiber;
274
+ }
275
+
276
+ fiber.return = parent;
277
+ fiber.sibling = undefined;
278
+ fiber.pendingProps = getPendingProps(child);
279
+ if (
280
+ fiber.tag !== "memo" &&
281
+ fiber.tag !== "function-component" &&
282
+ fiber.tag !== "forward-ref" &&
283
+ fiber.tag !== "profiler" &&
284
+ fiber.tag !== "suspense" &&
285
+ fiber.tag !== "suspense-list"
286
+ && fiber.memoizedState === undefined
287
+ ) {
288
+ fiber.memoizedState = index;
289
+ }
290
+ previous = fiber;
291
+ });
292
+
293
+ return { fiber: first, consumed };
294
+ }
295
+
296
+ function createHostFiber(
297
+ parent: Fiber,
298
+ current: Fiber | undefined,
299
+ node: ReactCompatNode,
300
+ key: string | undefined,
301
+ runtime: RootRuntime | undefined,
302
+ path: string,
303
+ options: FiberHydrationOptions = {},
304
+ ): FiberReconcileResult {
305
+ if (node === null || node === undefined || typeof node === "boolean") {
306
+ return { fiber: undefined, consumed: 0 };
307
+ }
308
+
309
+ if (typeof node === "string" || typeof node === "number") {
310
+ const existing = options.previousNodes?.[0];
311
+ const fiber =
312
+ current?.tag === "host-text"
313
+ ? createWorkInProgress(current, String(node))
314
+ : createFiber("host-text", String(node), key);
315
+ if (existing === undefined && options.previousNodes !== undefined) {
316
+ reportMissingHydrationNode(options, path);
317
+ } else if (existing !== undefined && !(existing instanceof Text)) {
318
+ reportHydrationNodeTypeMismatch(options, path, "text", existing);
319
+ }
320
+ fiber.stateNode =
321
+ existing instanceof Text
322
+ ? existing
323
+ : current?.tag === "host-text" && current.stateNode instanceof Text
324
+ ? current.stateNode
325
+ : document.createTextNode("");
326
+
327
+ if (existing instanceof Text && existing.data !== String(node)) {
328
+ reportRecoverable(
329
+ options,
330
+ "text",
331
+ path,
332
+ new Error("Hydration text mismatch."),
333
+ );
334
+ }
335
+
336
+ return { fiber, consumed: existing instanceof Text ? 1 : 0 };
337
+ }
338
+
339
+ if (Array.isArray(node)) {
340
+ const fiber =
341
+ current?.tag === "fragment"
342
+ ? createWorkInProgress(current, node)
343
+ : createFiber("fragment", node, key);
344
+ const childResult = reconcileHostChild(
345
+ fiber,
346
+ current?.child,
347
+ node,
348
+ runtime,
349
+ path,
350
+ options,
351
+ );
352
+ fiber.child = childResult.fiber;
353
+ return { fiber, consumed: childResult.consumed };
354
+ }
355
+
356
+ if (!isReactCompatElement(node)) {
357
+ if (isReactCompatPortal(node)) {
358
+ return createPortalFiber(parent, current, node, key, runtime, path, options);
359
+ }
360
+
361
+ return { fiber: undefined, consumed: 0 };
362
+ }
363
+
364
+ if (node.type === Fragment) {
365
+ const fiber =
366
+ current?.tag === "fragment"
367
+ ? createWorkInProgress(current, node.props.children)
368
+ : createFiber("fragment", node.props.children, key);
369
+ const childResult = reconcileHostChild(
370
+ fiber,
371
+ current?.child,
372
+ node.props.children as ReactCompatNode,
373
+ runtime,
374
+ `${path}.f`,
375
+ options,
376
+ );
377
+ fiber.child = childResult.fiber;
378
+ return { fiber, consumed: childResult.consumed };
379
+ }
380
+
381
+ if (node.type === Activity) {
382
+ const children =
383
+ (node.props as { mode?: unknown }).mode === "hidden"
384
+ ? null
385
+ : node.props.children;
386
+ const fiber =
387
+ current?.tag === "fragment"
388
+ ? createWorkInProgress(current, children)
389
+ : createFiber("fragment", children, key);
390
+ fiber.type = node.type;
391
+ const childResult = reconcileHostChild(
392
+ fiber,
393
+ current?.tag === "fragment" ? current.child : undefined,
394
+ children as ReactCompatNode,
395
+ runtime,
396
+ `${path}.activity`,
397
+ options,
398
+ );
399
+ fiber.child = childResult.fiber;
400
+ return { fiber, consumed: childResult.consumed };
401
+ }
402
+
403
+ if (node.type === Profiler) {
404
+ if (runtime === undefined) {
405
+ return { fiber: undefined, consumed: 0 };
406
+ }
407
+
408
+ const fiber =
409
+ current?.tag === "profiler" && current.type === node.type
410
+ ? createWorkInProgress(current, node.props)
411
+ : createFiber("profiler", node.props, key);
412
+ fiber.type = node.type;
413
+ const childResult = renderWithProfiler(
414
+ runtime,
415
+ `${path}.profiler`,
416
+ node.props,
417
+ () =>
418
+ reconcileHostChild(
419
+ fiber,
420
+ current?.tag === "profiler" ? current.child : undefined,
421
+ node.props.children as ReactCompatNode,
422
+ runtime,
423
+ `${path}.profiler`,
424
+ options,
425
+ ),
426
+ );
427
+ fiber.child = childResult.fiber;
428
+ return { fiber, consumed: childResult.consumed };
429
+ }
430
+
431
+ if (node.type === STRICT_MODE_TYPE) {
432
+ return createStrictModeFiber(current, node, key, runtime, path, options);
433
+ }
434
+
435
+ if (node.type === Suspense) {
436
+ return createSuspenseFiber(current, node, key, runtime, path, options);
437
+ }
438
+
439
+ if (node.type === SuspenseList) {
440
+ return createSuspenseListFiber(current, node, key, runtime, path, options);
441
+ }
442
+
443
+ if (node.type === ERROR_BOUNDARY_TYPE) {
444
+ const fiber =
445
+ current?.tag === "error-boundary"
446
+ ? createWorkInProgress(current, node.props)
447
+ : createFiber("error-boundary", node.props, key);
448
+ fiber.type = node.type;
449
+
450
+ try {
451
+ const childResult = reconcileHostChild(
452
+ fiber,
453
+ current?.tag === "error-boundary" ? current.child : undefined,
454
+ node.props.children as ReactCompatNode,
455
+ runtime,
456
+ `${path}.eb`,
457
+ options,
458
+ );
459
+ fiber.child = childResult.fiber;
460
+ } catch (error) {
461
+ if (isThenable(error)) {
462
+ throw error;
463
+ }
464
+
465
+ const normalizedError =
466
+ error instanceof Error ? error : new Error(String(error));
467
+ const onError = node.props.onError;
468
+
469
+ if (typeof onError === "function") {
470
+ (onError as (error: Error) => void)(normalizedError);
471
+ }
472
+
473
+ const fallback = node.props.fallback;
474
+ const fallbackNode =
475
+ typeof fallback === "function"
476
+ ? (fallback as (error: Error) => ReactCompatNode)(normalizedError)
477
+ : null;
478
+ const fallbackResult = reconcileHostChild(
479
+ fiber,
480
+ current?.tag === "error-boundary" ? current.child : undefined,
481
+ fallbackNode,
482
+ runtime,
483
+ `${path}.eb.fallback`,
484
+ options,
485
+ );
486
+ fiber.child = fallbackResult.fiber;
487
+ }
488
+ return { fiber, consumed: options.previousNodes?.length ?? 0 };
489
+ }
490
+
491
+ if (isReactCompatProvider(node.type)) {
492
+ const fiber =
493
+ current?.tag === "context-provider" && current.type === node.type
494
+ ? createWorkInProgress(current, node.props)
495
+ : createFiber("context-provider", node.props, key);
496
+ fiber.type = node.type;
497
+ const childResult = renderWithContextProvider(node.type, node.props.value, () =>
498
+ reconcileHostChild(
499
+ fiber,
500
+ current?.tag === "context-provider" ? current.child : undefined,
501
+ node.props.children as ReactCompatNode,
502
+ runtime,
503
+ `${path}.provider`,
504
+ options,
505
+ ),
506
+ );
507
+ fiber.child = childResult.fiber;
508
+ return { fiber, consumed: childResult.consumed };
509
+ }
510
+
511
+ if (isReactCompatConsumer(node.type)) {
512
+ const fiber =
513
+ current?.tag === "context-consumer" && current.type === node.type
514
+ ? createWorkInProgress(current, node.props)
515
+ : createFiber("context-consumer", node.props, key);
516
+ fiber.type = node.type;
517
+ const children = node.props.children;
518
+ const render =
519
+ typeof children === "function"
520
+ ? (children as (value: unknown) => ReactCompatNode)
521
+ : () => null;
522
+ const childResult = reconcileHostChild(
523
+ fiber,
524
+ current?.tag === "context-consumer" ? current.child : undefined,
525
+ render(useContext(node.type.context)),
526
+ runtime,
527
+ `${path}.consumer`,
528
+ options,
529
+ );
530
+ fiber.child = childResult.fiber;
531
+ return { fiber, consumed: childResult.consumed };
532
+ }
533
+
534
+ if (isForwardRefType(node.type)) {
535
+ if (runtime === undefined) {
536
+ return { fiber: undefined, consumed: 0 };
537
+ }
538
+
539
+ const forwardRefType = node.type;
540
+ const fiber =
541
+ current?.tag === "forward-ref" && current.type === forwardRefType
542
+ ? createWorkInProgress(current, node.props)
543
+ : createFiber("forward-ref", node.props, key);
544
+ fiber.type = forwardRefType;
545
+ const rendered = renderWithRootRuntime(runtime, path, () =>
546
+ forwardRefType.render(node.props, node.ref),
547
+ );
548
+ fiber.memoizedState = getDevToolsHookState(runtime, path);
549
+ const childOptions = withHydrationComponentStack(
550
+ options,
551
+ getComponentName(forwardRefType.render),
552
+ );
553
+ const childResult = reconcileHostChild(
554
+ fiber,
555
+ current?.tag === "forward-ref" ? current.child : undefined,
556
+ rendered,
557
+ runtime,
558
+ `${path}.forwardRef`,
559
+ childOptions,
560
+ );
561
+ fiber.child = childResult.fiber;
562
+ return { fiber, consumed: childResult.consumed };
563
+ }
564
+
565
+ if (isMemoType(node.type)) {
566
+ if (runtime === undefined) {
567
+ return { fiber: undefined, consumed: 0 };
568
+ }
569
+
570
+ const memoType = node.type;
571
+ const memoPath = `${path}.memo`;
572
+ const previousMemoState =
573
+ current?.tag === "memo"
574
+ ? (current.memoizedState as MemoFiberState | undefined)
575
+ : undefined;
576
+ const fiber =
577
+ current?.tag === "memo" && current.type === memoType
578
+ ? createWorkInProgress(current, node.props)
579
+ : createFiber("memo", node.props, key);
580
+ fiber.type = memoType;
581
+
582
+ if (
583
+ previousMemoState !== undefined &&
584
+ !hasDirtyInstance(runtime, previousMemoState.instanceKeys) &&
585
+ areMemoPropsEqual(memoType, previousMemoState.props, node.props)
586
+ ) {
587
+ markActiveInstanceKeys(runtime, previousMemoState.instanceKeys);
588
+ fiber.child = current?.child;
589
+ fiber.memoizedState = previousMemoState;
590
+ return { fiber, consumed: options.previousNodes?.length ?? 0 };
591
+ }
592
+
593
+ const renderedElement: ReactCompatElement = {
594
+ ...node,
595
+ type: memoType.type,
596
+ };
597
+ const childResult = createHostFiber(
598
+ fiber,
599
+ current?.tag === "memo" ? current.child : undefined,
600
+ renderedElement,
601
+ key,
602
+ runtime,
603
+ memoPath,
604
+ options,
605
+ );
606
+ fiber.child = childResult.fiber;
607
+ fiber.memoizedState = {
608
+ props: { ...node.props },
609
+ instanceKeys: collectInstanceKeys(runtime, memoPath),
610
+ };
611
+ return { fiber, consumed: childResult.consumed };
612
+ }
613
+
614
+ if (isLazyType(node.type)) {
615
+ if (runtime === undefined) {
616
+ return { fiber: undefined, consumed: 0 };
617
+ }
618
+
619
+ const lazyType = node.type;
620
+ const fiber =
621
+ current?.tag === "lazy" && current.type === lazyType
622
+ ? createWorkInProgress(current, node.props)
623
+ : createFiber("lazy", node.props, key);
624
+ fiber.type = lazyType;
625
+
626
+ if (lazyType.status === "resolved" && lazyType.resolved !== undefined) {
627
+ const renderedElement: ReactCompatElement = {
628
+ ...node,
629
+ type: lazyType.resolved,
630
+ };
631
+ const childResult = createHostFiber(
632
+ fiber,
633
+ current?.tag === "lazy" ? current.child : undefined,
634
+ renderedElement,
635
+ key,
636
+ runtime,
637
+ `${path}.lazy`,
638
+ options,
639
+ );
640
+ fiber.child = childResult.fiber;
641
+ return { fiber, consumed: childResult.consumed };
642
+ }
643
+
644
+ if (lazyType.status === "rejected") {
645
+ throw lazyType.error;
646
+ }
647
+
648
+ if (lazyType.status === "uninitialized") {
649
+ lazyType.status = "pending";
650
+ lazyType.promise = lazyType
651
+ .load()
652
+ .then((module) => {
653
+ lazyType.status = "resolved";
654
+ lazyType.resolved = module.default;
655
+ runtime.rerender();
656
+ })
657
+ .catch((error: unknown) => {
658
+ lazyType.status = "rejected";
659
+ lazyType.error = error;
660
+ runtime.rerender();
661
+ });
662
+ }
663
+
664
+ if (suspensePrimaryRenderDepth > 0) {
665
+ throw lazyType.promise;
666
+ }
667
+
668
+ fiber.child = undefined;
669
+ return { fiber, consumed: 0 };
670
+ }
671
+
672
+ if (isClassComponentType(node.type)) {
673
+ if (runtime === undefined) {
674
+ return { fiber: undefined, consumed: 0 };
675
+ }
676
+
677
+ const classType = node.type;
678
+ const fiber =
679
+ current?.tag === "class-component" && current.type === classType
680
+ ? createWorkInProgress(current, node.props)
681
+ : createFiber("class-component", node.props, key);
682
+ fiber.type = classType;
683
+ const rendered = renderClassComponentWithRuntime(
684
+ classType,
685
+ node.props,
686
+ runtime,
687
+ path,
688
+ );
689
+ applyRef(node.ref, rendered.kind === "skip" ? current?.stateNode : rendered.instance);
690
+
691
+ if (rendered.kind === "skip") {
692
+ fiber.child = current?.child;
693
+ return { fiber, consumed: options.previousNodes?.length ?? 0 };
694
+ }
695
+
696
+ const childOptions = withHydrationComponentStack(
697
+ options,
698
+ getComponentName(classType),
699
+ );
700
+
701
+ try {
702
+ const childResult = reconcileHostChild(
703
+ fiber,
704
+ current?.tag === "class-component" ? current.child : undefined,
705
+ rendered.node,
706
+ runtime,
707
+ `${path}.class`,
708
+ childOptions,
709
+ );
710
+ fiber.child = childResult.fiber;
711
+ fiber.stateNode = rendered.instance;
712
+ } catch (error) {
713
+ const fallbackNode = recoverClassComponentError(
714
+ rendered.type,
715
+ rendered.instance,
716
+ error,
717
+ );
718
+
719
+ if (fallbackNode === undefined) {
720
+ throw error;
721
+ }
722
+
723
+ const fallbackResult = reconcileHostChild(
724
+ fiber,
725
+ current?.tag === "class-component" ? current.child : undefined,
726
+ fallbackNode,
727
+ runtime,
728
+ `${path}.class.fallback`,
729
+ childOptions,
730
+ );
731
+ fiber.child = fallbackResult.fiber;
732
+ }
733
+ return { fiber, consumed: options.previousNodes?.length ?? 0 };
734
+ }
735
+
736
+ if (isFunctionComponentType(node.type)) {
737
+ if (runtime === undefined) {
738
+ return { fiber: undefined, consumed: 0 };
739
+ }
740
+
741
+ const fiber =
742
+ current?.tag === "function-component" && current.type === node.type
743
+ ? createWorkInProgress(current, node.props)
744
+ : createFiber("function-component", node.props, key);
745
+ fiber.type = node.type;
746
+ const rendered = renderWithRootRuntime(runtime, path, () =>
747
+ (node.type as (props: Record<string, unknown>) => ReactCompatNode)(node.props),
748
+ );
749
+ fiber.memoizedState = getDevToolsHookState(runtime, path);
750
+ const childOptions = withHydrationComponentStack(
751
+ options,
752
+ getComponentName(node.type as Function),
753
+ );
754
+ const childResult = reconcileHostChild(
755
+ fiber,
756
+ current?.tag === "function-component" ? current.child : undefined,
757
+ rendered,
758
+ runtime,
759
+ `${path}.0`,
760
+ childOptions,
761
+ );
762
+ fiber.child = childResult.fiber;
763
+ return { fiber, consumed: childResult.consumed };
764
+ }
765
+
766
+ if (typeof node.type !== "string") {
767
+ return { fiber: undefined, consumed: 0 };
768
+ }
769
+
770
+ const fiber =
771
+ current?.tag === "host-component" && current.type === node.type
772
+ ? createWorkInProgress(current, node.props)
773
+ : createFiber("host-component", node.props, key);
774
+ const existing = options.previousNodes?.[0];
775
+ const existingElement = existing instanceof HTMLElement ? existing : undefined;
776
+ const tagMatches =
777
+ existingElement !== undefined &&
778
+ existingElement.tagName.toLowerCase() === node.type;
779
+
780
+ if (existing === undefined && options.previousNodes !== undefined) {
781
+ reportMissingHydrationNode(options, path);
782
+ } else if (existing !== undefined && !(existing instanceof HTMLElement)) {
783
+ reportHydrationNodeTypeMismatch(options, path, `<${node.type}>`, existing);
784
+ }
785
+
786
+ if (existingElement !== undefined && !tagMatches) {
787
+ reportRecoverable(
788
+ options,
789
+ "tag",
790
+ path,
791
+ new Error(
792
+ `Hydration tag mismatch: expected <${node.type}> but found <${existingElement.tagName.toLowerCase()}>.`,
793
+ ),
794
+ );
795
+ reportElementTextMismatch(options, `${path}.c`, existingElement, node.props.children);
796
+ }
797
+
798
+ fiber.type = node.type;
799
+ fiber.stateNode =
800
+ tagMatches
801
+ ? existingElement
802
+ : current?.tag === "host-component" &&
803
+ current.type === node.type &&
804
+ current.stateNode instanceof HTMLElement
805
+ ? current.stateNode
806
+ : document.createElement(node.type);
807
+ fiber.hydrateExisting = tagMatches && options.previousNodes !== undefined;
808
+ const previousChildNodes =
809
+ tagMatches && existingElement !== undefined
810
+ ? Array.from(existingElement.childNodes)
811
+ : undefined;
812
+ const childResult = reconcileHostChild(
813
+ fiber,
814
+ current?.tag === "host-component" ? current.child : undefined,
815
+ node.props.children as ReactCompatNode,
816
+ runtime,
817
+ `${path}.c`,
818
+ previousChildNodes === undefined
819
+ ? options
820
+ : { ...options, previousNodes: previousChildNodes },
821
+ );
822
+ fiber.child = childResult.fiber;
823
+ if (previousChildNodes !== undefined) {
824
+ reportExtraHydrationNodes(options, `${path}.c`, previousChildNodes, childResult.consumed);
825
+ }
826
+ parent.child ??= fiber;
827
+ return { fiber, consumed: existing === undefined ? 0 : 1 };
828
+ }
829
+
830
+ function isFunctionComponentType(value: unknown): value is (
831
+ props: Record<string, unknown>,
832
+ ) => ReactCompatNode {
833
+ return (
834
+ typeof value === "function" &&
835
+ typeof (value as { prototype?: { render?: unknown } }).prototype?.render !==
836
+ "function"
837
+ );
838
+ }
839
+
840
+ function commitHostChildren(
841
+ fiber: Fiber | undefined,
842
+ parent: ParentNode,
843
+ eventRoot: Element,
844
+ path: string,
845
+ options: RenderOptions = {},
846
+ ): Node[] {
847
+ const nodes: Node[] = [];
848
+ let cursor = fiber;
849
+ let index = 0;
850
+
851
+ while (cursor !== undefined) {
852
+ nodes.push(...commitHostFiber(cursor, parent, eventRoot, joinPath(path, String(index)), options));
853
+ cursor = cursor.sibling;
854
+ index += 1;
855
+ }
856
+
857
+ return nodes;
858
+ }
859
+
860
+ function commitHostFiber(
861
+ fiber: Fiber,
862
+ parent: ParentNode,
863
+ eventRoot: Element,
864
+ path: string,
865
+ options: RenderOptions = {},
866
+ ): Node[] {
867
+ if (fiber.tag === "host-text") {
868
+ const text = fiber.stateNode;
869
+
870
+ if (!(text instanceof Text)) {
871
+ return [];
872
+ }
873
+
874
+ text.data = String(fiber.pendingProps);
875
+ fiber.memoizedProps = fiber.pendingProps;
876
+ return [text];
877
+ }
878
+
879
+ if (fiber.tag === "host-component") {
880
+ const element = fiber.stateNode;
881
+
882
+ if (!(element instanceof HTMLElement)) {
883
+ return [];
884
+ }
885
+
886
+ applyProps(element, fiber.pendingProps as Record<string, unknown>, path, {
887
+ ...options,
888
+ eventRoot,
889
+ preserveHydrationAttributes: fiber.hydrateExisting,
890
+ });
891
+ applyRef((fiber.pendingProps as { ref?: unknown }).ref, element);
892
+ const childNodes = commitHostChildren(fiber.child, element, eventRoot, `${path}.c`, options);
893
+ syncChildNodes(element, childNodes);
894
+ applyPostChildFormProps(element, fiber.pendingProps as Record<string, unknown>);
895
+ fiber.memoizedProps = fiber.pendingProps;
896
+ return [element];
897
+ }
898
+
899
+ if (fiber.tag === "fragment") {
900
+ fiber.memoizedProps = fiber.pendingProps;
901
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.f`, options);
902
+ }
903
+
904
+ if (fiber.tag === "profiler") {
905
+ fiber.memoizedProps = fiber.pendingProps;
906
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.profiler`, options);
907
+ }
908
+
909
+ if (fiber.tag === "strict-mode") {
910
+ fiber.memoizedProps = fiber.pendingProps;
911
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.strict`, options);
912
+ }
913
+
914
+ if (fiber.tag === "suspense") {
915
+ fiber.memoizedProps = fiber.pendingProps;
916
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.s`, options);
917
+ }
918
+
919
+ if (fiber.tag === "suspense-list") {
920
+ fiber.memoizedProps = fiber.pendingProps;
921
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.sl`, options);
922
+ }
923
+
924
+ if (fiber.tag === "context-provider" || fiber.tag === "context-consumer") {
925
+ fiber.memoizedProps = fiber.pendingProps;
926
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.ctx`, options);
927
+ }
928
+
929
+ if (fiber.tag === "function-component") {
930
+ fiber.memoizedProps = fiber.pendingProps;
931
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.fc`, options);
932
+ }
933
+
934
+ if (fiber.tag === "forward-ref") {
935
+ fiber.memoizedProps = fiber.pendingProps;
936
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.fr`, options);
937
+ }
938
+
939
+ if (fiber.tag === "memo") {
940
+ fiber.memoizedProps = fiber.pendingProps;
941
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.memo`, options);
942
+ }
943
+
944
+ if (fiber.tag === "lazy") {
945
+ fiber.memoizedProps = fiber.pendingProps;
946
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.lazy`, options);
947
+ }
948
+
949
+ if (fiber.tag === "error-boundary") {
950
+ fiber.memoizedProps = fiber.pendingProps;
951
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.eb`, options);
952
+ }
953
+
954
+ if (fiber.tag === "class-component") {
955
+ fiber.memoizedProps = fiber.pendingProps;
956
+ return commitHostChildren(fiber.child, parent, eventRoot, `${path}.class`, options);
957
+ }
958
+
959
+ if (fiber.tag === "portal") {
960
+ const container = fiber.stateNode;
961
+
962
+ if (!(container instanceof Element)) {
963
+ return [];
964
+ }
965
+
966
+ setLogicalEventParent(container, parent);
967
+ const childNodes = commitHostChildren(
968
+ fiber.child,
969
+ container,
970
+ container,
971
+ `${path}.portal`,
972
+ options,
973
+ );
974
+ syncChildNodes(container, childNodes);
975
+ fiber.memoizedProps = fiber.pendingProps;
976
+ return [];
977
+ }
978
+
979
+ return [];
980
+ }
981
+
982
+ function createSuspenseFiber(
983
+ current: Fiber | undefined,
984
+ element: ReactCompatElement,
985
+ key: string | undefined,
986
+ runtime: RootRuntime | undefined,
987
+ path: string,
988
+ options: FiberHydrationOptions = {},
989
+ ): FiberReconcileResult {
990
+ if (runtime === undefined) {
991
+ return { fiber: undefined, consumed: 0 };
992
+ }
993
+
994
+ const boundary = findReactSuspenseBoundary(options.previousNodes ?? []);
995
+ if (boundary?.serverError !== undefined) {
996
+ reportReactSuspenseServerError(
997
+ options,
998
+ path,
999
+ boundary.serverError.message,
1000
+ boundary.serverError.componentStack,
1001
+ );
1002
+ }
1003
+ const { previousNodes: _previousNodes, ...optionsWithoutPreviousNodes } = options;
1004
+ const boundaryOptions =
1005
+ boundary === undefined
1006
+ ? options
1007
+ : boundary.serverError === undefined
1008
+ ? boundary.previousNodes === undefined
1009
+ ? optionsWithoutPreviousNodes
1010
+ : { ...options, previousNodes: boundary.previousNodes }
1011
+ : optionsWithoutPreviousNodes;
1012
+ const fiber =
1013
+ current?.tag === "suspense" && current.type === element.type
1014
+ ? createWorkInProgress(current, element.props)
1015
+ : createFiber("suspense", element.props, key);
1016
+ fiber.type = element.type;
1017
+
1018
+ try {
1019
+ suspensePrimaryRenderDepth += 1;
1020
+ let childResult: FiberReconcileResult;
1021
+
1022
+ try {
1023
+ childResult = reconcileHostChild(
1024
+ fiber,
1025
+ current?.tag === "suspense" ? current.child : undefined,
1026
+ element.props.children as ReactCompatNode,
1027
+ runtime,
1028
+ `${path}.s`,
1029
+ boundaryOptions,
1030
+ );
1031
+ } finally {
1032
+ suspensePrimaryRenderDepth -= 1;
1033
+ }
1034
+
1035
+ fiber.child = childResult.fiber;
1036
+ fiber.memoizedState = { didSuspend: false } satisfies SuspenseFiberState;
1037
+ } catch (error) {
1038
+ if (!isThenable(error)) {
1039
+ throw error;
1040
+ }
1041
+
1042
+ error.then(
1043
+ () => runtime.rerender(),
1044
+ () => runtime.rerender(),
1045
+ );
1046
+ const fallbackResult = reconcileHostChild(
1047
+ fiber,
1048
+ current?.tag === "suspense" ? current.child : undefined,
1049
+ element.props.fallback as ReactCompatNode,
1050
+ runtime,
1051
+ `${path}.fallback`,
1052
+ boundaryOptions,
1053
+ );
1054
+ fiber.child = fallbackResult.fiber;
1055
+ fiber.memoizedState = { didSuspend: true } satisfies SuspenseFiberState;
1056
+ }
1057
+
1058
+ return {
1059
+ fiber,
1060
+ consumed: boundary?.consumed ?? options.previousNodes?.length ?? 0,
1061
+ };
1062
+ }
1063
+
1064
+ function createStrictModeFiber(
1065
+ current: Fiber | undefined,
1066
+ element: ReactCompatElement,
1067
+ key: string | undefined,
1068
+ runtime: RootRuntime | undefined,
1069
+ path: string,
1070
+ options: FiberHydrationOptions = {},
1071
+ ): FiberReconcileResult {
1072
+ if (runtime === undefined) {
1073
+ return { fiber: undefined, consumed: 0 };
1074
+ }
1075
+
1076
+ const fiber =
1077
+ current?.tag === "strict-mode" && current.type === element.type
1078
+ ? createWorkInProgress(current, element.props)
1079
+ : createFiber("strict-mode", element.props, key);
1080
+ fiber.type = element.type;
1081
+ const snapshot = takeRuntimeSnapshot(runtime);
1082
+
1083
+ try {
1084
+ createHostFiber(
1085
+ fiber,
1086
+ undefined,
1087
+ element.props.children as ReactCompatNode,
1088
+ undefined,
1089
+ runtime,
1090
+ `${path}.strict.preview`,
1091
+ options.previousNodes === undefined
1092
+ ? options
1093
+ : { ...options, previousNodes: [] },
1094
+ );
1095
+ } finally {
1096
+ restoreRuntimeSnapshot(runtime, snapshot);
1097
+ }
1098
+
1099
+ const childResult = renderWithStrictMode(
1100
+ runtime,
1101
+ () =>
1102
+ reconcileHostChild(
1103
+ fiber,
1104
+ current?.tag === "strict-mode" ? current.child : undefined,
1105
+ element.props.children as ReactCompatNode,
1106
+ runtime,
1107
+ `${path}.strict`,
1108
+ options,
1109
+ ),
1110
+ );
1111
+ fiber.child = childResult.fiber;
1112
+ return { fiber, consumed: childResult.consumed };
1113
+ }
1114
+
1115
+ function createSuspenseListFiber(
1116
+ current: Fiber | undefined,
1117
+ element: ReactCompatElement,
1118
+ key: string | undefined,
1119
+ runtime: RootRuntime | undefined,
1120
+ path: string,
1121
+ options: FiberHydrationOptions = {},
1122
+ ): FiberReconcileResult {
1123
+ if (runtime === undefined) {
1124
+ return { fiber: undefined, consumed: 0 };
1125
+ }
1126
+
1127
+ const fiber =
1128
+ current?.tag === "suspense-list" && current.type === element.type
1129
+ ? createWorkInProgress(current, element.props)
1130
+ : createFiber("suspense-list", element.props, key);
1131
+ fiber.type = element.type;
1132
+ const children = normalizeChildren(element.props.children as ReactCompatNode);
1133
+ const revealOrder = element.props.revealOrder;
1134
+
1135
+ if (revealOrder === "forwards") {
1136
+ const childResult = reconcileSuspenseListForwards(
1137
+ fiber,
1138
+ current?.tag === "suspense-list" ? current.child : undefined,
1139
+ children,
1140
+ runtime,
1141
+ path,
1142
+ options,
1143
+ );
1144
+ fiber.child = childResult.fiber;
1145
+ fiber.memoizedState = {
1146
+ didSuspend: hasSuspendedChild(fiber.child),
1147
+ } satisfies SuspenseFiberState;
1148
+ return { fiber, consumed: childResult.consumed };
1149
+ } else if (revealOrder === "backwards") {
1150
+ const childResult = reconcileSuspenseListBackwards(
1151
+ fiber,
1152
+ current?.tag === "suspense-list" ? current.child : undefined,
1153
+ children,
1154
+ runtime,
1155
+ path,
1156
+ options,
1157
+ );
1158
+ fiber.child = childResult.fiber;
1159
+ fiber.memoizedState = {
1160
+ didSuspend: hasSuspendedChild(fiber.child),
1161
+ } satisfies SuspenseFiberState;
1162
+ return { fiber, consumed: childResult.consumed };
1163
+ } else {
1164
+ const childResult = reconcileHostChild(
1165
+ fiber,
1166
+ current?.tag === "suspense-list" ? current.child : undefined,
1167
+ children,
1168
+ runtime,
1169
+ `${path}.sl`,
1170
+ options,
1171
+ );
1172
+ fiber.child = childResult.fiber;
1173
+ fiber.memoizedState = {
1174
+ didSuspend: hasSuspendedChild(fiber.child),
1175
+ } satisfies SuspenseFiberState;
1176
+ return { fiber, consumed: childResult.consumed };
1177
+ }
1178
+ }
1179
+
1180
+ function reconcileSuspenseListForwards(
1181
+ parent: Fiber,
1182
+ currentFirstChild: Fiber | undefined,
1183
+ children: readonly ReactCompatNode[],
1184
+ runtime: RootRuntime,
1185
+ path: string,
1186
+ options: FiberHydrationOptions = {},
1187
+ ): FiberReconcileResult {
1188
+ let first: Fiber | undefined;
1189
+ let previous: Fiber | undefined;
1190
+ let consumed = 0;
1191
+ const currentByKey = collectExistingKeyedFibers(currentFirstChild);
1192
+ let currentUnkeyed = currentFirstChild;
1193
+
1194
+ for (const [index, child] of children.entries()) {
1195
+ const key = getNodeKey(child);
1196
+ const current = key === undefined ? currentUnkeyed : currentByKey.get(key);
1197
+ const childOptions =
1198
+ options.previousNodes === undefined
1199
+ ? options
1200
+ : { ...options, previousNodes: options.previousNodes.slice(consumed) };
1201
+ const result = createHostFiber(
1202
+ parent,
1203
+ current,
1204
+ child,
1205
+ key,
1206
+ runtime,
1207
+ `${path}.sl.${getNodePathSegment(child, index)}`,
1208
+ childOptions,
1209
+ );
1210
+ const fiber = result.fiber;
1211
+
1212
+ if (key === undefined) {
1213
+ currentUnkeyed = currentUnkeyed?.sibling;
1214
+ }
1215
+ consumed += result.consumed;
1216
+
1217
+ if (fiber === undefined) {
1218
+ continue;
1219
+ }
1220
+
1221
+ fiber.return = parent;
1222
+ fiber.sibling = undefined;
1223
+
1224
+ if (first === undefined) {
1225
+ first = fiber;
1226
+ } else if (previous !== undefined) {
1227
+ previous.sibling = fiber;
1228
+ }
1229
+
1230
+ previous = fiber;
1231
+
1232
+ if (isSuspendedSuspenseFiber(fiber)) {
1233
+ break;
1234
+ }
1235
+ }
1236
+
1237
+ return { fiber: first, consumed };
1238
+ }
1239
+
1240
+ function reconcileSuspenseListBackwards(
1241
+ parent: Fiber,
1242
+ currentFirstChild: Fiber | undefined,
1243
+ children: readonly ReactCompatNode[],
1244
+ runtime: RootRuntime,
1245
+ path: string,
1246
+ options: FiberHydrationOptions = {},
1247
+ ): FiberReconcileResult {
1248
+ const fibers: Fiber[] = [];
1249
+ const currentByKey = collectExistingKeyedFibers(currentFirstChild);
1250
+ let consumed = 0;
1251
+
1252
+ for (let index = children.length - 1; index >= 0; index -= 1) {
1253
+ const child = children[index] as ReactCompatNode;
1254
+ const key = getNodeKey(child);
1255
+ const result = createHostFiber(
1256
+ parent,
1257
+ key === undefined ? undefined : currentByKey.get(key),
1258
+ child,
1259
+ key,
1260
+ runtime,
1261
+ `${path}.sl.${getNodePathSegment(child, index)}`,
1262
+ options.previousNodes === undefined
1263
+ ? options
1264
+ : { ...options, previousNodes: options.previousNodes.slice(consumed) },
1265
+ );
1266
+ const fiber = result.fiber;
1267
+ consumed += result.consumed;
1268
+
1269
+ if (fiber === undefined) {
1270
+ continue;
1271
+ }
1272
+
1273
+ fiber.return = parent;
1274
+ fiber.sibling = undefined;
1275
+ fibers.unshift(fiber);
1276
+
1277
+ if (isSuspendedSuspenseFiber(fiber)) {
1278
+ break;
1279
+ }
1280
+ }
1281
+
1282
+ return { fiber: linkFiberSiblings(fibers), consumed };
1283
+ }
1284
+
1285
+ function linkFiberSiblings(fibers: readonly Fiber[]): Fiber | undefined {
1286
+ let previous: Fiber | undefined;
1287
+
1288
+ for (const fiber of fibers) {
1289
+ if (previous !== undefined) {
1290
+ previous.sibling = fiber;
1291
+ }
1292
+ previous = fiber;
1293
+ }
1294
+
1295
+ return fibers[0];
1296
+ }
1297
+
1298
+ function hasSuspendedChild(fiber: Fiber | undefined): boolean {
1299
+ let cursor = fiber;
1300
+
1301
+ while (cursor !== undefined) {
1302
+ if (isSuspendedSuspenseFiber(cursor)) {
1303
+ return true;
1304
+ }
1305
+
1306
+ cursor = cursor.sibling;
1307
+ }
1308
+
1309
+ return false;
1310
+ }
1311
+
1312
+ function isSuspendedSuspenseFiber(fiber: Fiber): boolean {
1313
+ return (
1314
+ fiber.tag === "suspense" &&
1315
+ (fiber.memoizedState as SuspenseFiberState | undefined)?.didSuspend === true
1316
+ );
1317
+ }
1318
+
1319
+ function findReactSuspenseBoundary(
1320
+ previousNodes: readonly Node[],
1321
+ ): ReactSuspenseBoundary | undefined {
1322
+ const startIndex = previousNodes.findIndex(isReactSuspenseStartComment);
1323
+
1324
+ if (startIndex < 0) {
1325
+ return undefined;
1326
+ }
1327
+
1328
+ let depth = 0;
1329
+
1330
+ for (let index = startIndex; index < previousNodes.length; index += 1) {
1331
+ const node = previousNodes[index];
1332
+
1333
+ if (isReactSuspenseStartComment(node)) {
1334
+ depth += 1;
1335
+ continue;
1336
+ }
1337
+
1338
+ if (isReactSuspenseEndComment(node)) {
1339
+ depth -= 1;
1340
+
1341
+ if (depth === 0) {
1342
+ const start = previousNodes[startIndex] as Comment;
1343
+ const boundaryNodes = previousNodes.slice(startIndex + 1, index);
1344
+ const boundary: ReactSuspenseBoundary = {
1345
+ consumed: index - startIndex + 1,
1346
+ ...readReactSuspenseServerError(start, boundaryNodes),
1347
+ };
1348
+
1349
+ if (!isReactSuspenseErrorStartComment(start)) {
1350
+ boundary.previousNodes = isReactSuspensePendingStartComment(start)
1351
+ ? removeReactSuspensePendingTemplate(boundaryNodes)
1352
+ : boundaryNodes;
1353
+ }
1354
+
1355
+ return boundary;
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ return undefined;
1361
+ }
1362
+
1363
+ function isReactSuspenseStartComment(node: Node | undefined): node is Comment {
1364
+ return node instanceof Comment && reactSuspenseStartComments.has(node.data);
1365
+ }
1366
+
1367
+ function isReactSuspensePendingStartComment(node: Comment): boolean {
1368
+ return node.data === "$?" || node.data === "$!";
1369
+ }
1370
+
1371
+ function isReactSuspenseErrorStartComment(node: Comment): boolean {
1372
+ return node.data === "$!";
1373
+ }
1374
+
1375
+ function isReactSuspenseEndComment(node: Node | undefined): node is Comment {
1376
+ return node instanceof Comment && node.data === "/$";
1377
+ }
1378
+
1379
+ function removeReactSuspensePendingTemplate(nodes: readonly Node[]): Node[] {
1380
+ const [firstNode, ...remainingNodes] = nodes;
1381
+
1382
+ return firstNode instanceof HTMLTemplateElement
1383
+ ? remainingNodes
1384
+ : [...nodes];
1385
+ }
1386
+
1387
+ const reactSuspenseStartComments = new Set(["$", "$?", "$!"]);
1388
+
1389
+ function readReactSuspenseServerError(
1390
+ start: Comment,
1391
+ boundaryNodes: readonly Node[],
1392
+ ): { serverError: { message: string; componentStack?: string } } | {} {
1393
+ if (start.data !== "$!") {
1394
+ return {};
1395
+ }
1396
+
1397
+ const template = boundaryNodes[0];
1398
+ const message =
1399
+ template instanceof HTMLTemplateElement
1400
+ ? template.getAttribute("data-msg")
1401
+ : null;
1402
+ const componentStack =
1403
+ template instanceof HTMLTemplateElement
1404
+ ? template.getAttribute("data-stck")
1405
+ : null;
1406
+
1407
+ return {
1408
+ serverError: {
1409
+ message: message ?? "React Suspense server rendering error.",
1410
+ ...(componentStack === null ? {} : { componentStack }),
1411
+ },
1412
+ };
1413
+ }
1414
+
1415
+ function createPortalFiber(
1416
+ parent: Fiber,
1417
+ current: Fiber | undefined,
1418
+ portal: ReactCompatPortal,
1419
+ key: string | undefined,
1420
+ runtime: RootRuntime | undefined,
1421
+ path: string,
1422
+ options: FiberHydrationOptions = {},
1423
+ ): FiberReconcileResult {
1424
+ if (runtime === undefined) {
1425
+ return { fiber: undefined, consumed: 0 };
1426
+ }
1427
+
1428
+ runtime.portalContainers.add(portal.container);
1429
+ const fiber =
1430
+ current?.tag === "portal" && current.stateNode === portal.container
1431
+ ? createWorkInProgress(current, portal.children)
1432
+ : createFiber("portal", portal.children, key);
1433
+ fiber.stateNode = portal.container;
1434
+ const childResult = reconcileHostChild(
1435
+ fiber,
1436
+ current?.tag === "portal" ? current.child : undefined,
1437
+ portal.children,
1438
+ runtime,
1439
+ `${path}.portal`,
1440
+ options,
1441
+ );
1442
+ fiber.child = childResult.fiber;
1443
+ fiber.return = parent;
1444
+ return { fiber, consumed: childResult.consumed };
1445
+ }
1446
+
1447
+ function normalizeChildren(node: ReactCompatNode): ReactCompatNode[] {
1448
+ if (node === null || node === undefined || typeof node === "boolean") {
1449
+ return [];
1450
+ }
1451
+
1452
+ return Array.isArray(node) ? node : [node];
1453
+ }
1454
+
1455
+ function collectExistingKeyedFibers(
1456
+ firstChild: Fiber | undefined,
1457
+ ): Map<string, Fiber> {
1458
+ const keyed = new Map<string, Fiber>();
1459
+ let cursor = firstChild;
1460
+
1461
+ while (cursor !== undefined) {
1462
+ if (cursor.key !== undefined) {
1463
+ keyed.set(cursor.key, cursor);
1464
+ }
1465
+
1466
+ cursor = cursor.sibling;
1467
+ }
1468
+
1469
+ return keyed;
1470
+ }
1471
+
1472
+ function getNodeKey(node: ReactCompatNode): string | undefined {
1473
+ return isReactCompatElement(node) && node.key !== null ? node.key : undefined;
1474
+ }
1475
+
1476
+ function getNodePathSegment(node: ReactCompatNode, index: number): string {
1477
+ const key = getNodeKey(node);
1478
+ return key === undefined ? String(index) : `k:${key}`;
1479
+ }
1480
+
1481
+ function getComponentName(component: Function): string {
1482
+ return component.name === "" ? "Anonymous" : component.name;
1483
+ }
1484
+
1485
+ function joinPath(path: string, segment: string): string {
1486
+ return path === "" ? segment : `${path}.${segment}`;
1487
+ }
1488
+
1489
+ function isForwardRefType(
1490
+ value: unknown,
1491
+ ): value is {
1492
+ $$typeof: typeof FORWARD_REF_TYPE;
1493
+ render: (props: Record<string, unknown>, ref: unknown) => ReactCompatNode;
1494
+ } {
1495
+ return (
1496
+ typeof value === "object" &&
1497
+ value !== null &&
1498
+ (value as { $$typeof?: unknown }).$$typeof === FORWARD_REF_TYPE
1499
+ );
1500
+ }
1501
+
1502
+ function isMemoType(
1503
+ value: unknown,
1504
+ ): value is {
1505
+ $$typeof: typeof MEMO_TYPE;
1506
+ type: ReactCompatElement["type"];
1507
+ compare?: (
1508
+ previous: Record<string, unknown>,
1509
+ next: Record<string, unknown>,
1510
+ ) => boolean;
1511
+ } {
1512
+ return (
1513
+ typeof value === "object" &&
1514
+ value !== null &&
1515
+ (value as { $$typeof?: unknown }).$$typeof === MEMO_TYPE
1516
+ );
1517
+ }
1518
+
1519
+ function isLazyType(
1520
+ value: unknown,
1521
+ ): value is {
1522
+ $$typeof: typeof LAZY_TYPE;
1523
+ load: () => Promise<{ default: ReactCompatElement["type"] }>;
1524
+ status: "uninitialized" | "pending" | "resolved" | "rejected";
1525
+ promise?: Promise<void>;
1526
+ resolved?: ReactCompatElement["type"];
1527
+ error?: unknown;
1528
+ } {
1529
+ return (
1530
+ typeof value === "object" &&
1531
+ value !== null &&
1532
+ (value as { $$typeof?: unknown }).$$typeof === LAZY_TYPE
1533
+ );
1534
+ }
1535
+
1536
+ function collectInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
1537
+ return Array.from(runtime.instances.keys()).filter(
1538
+ (key) => key === prefix || key.startsWith(`${prefix}.`),
1539
+ );
1540
+ }
1541
+
1542
+ function markActiveInstanceKeys(runtime: RootRuntime, keys: readonly string[]): void {
1543
+ for (const key of keys) {
1544
+ runtime.activeInstanceKeys?.add(key);
1545
+ }
1546
+ }
1547
+
1548
+ function hasDirtyInstance(runtime: RootRuntime, keys: readonly string[]): boolean {
1549
+ return keys.some(
1550
+ (key) =>
1551
+ (runtime.instances.get(key) as { dirty?: boolean } | undefined)?.dirty === true,
1552
+ );
1553
+ }
1554
+
1555
+ function applyRef(ref: unknown, node: unknown): void {
1556
+ if (typeof ref === "function") {
1557
+ ref(node);
1558
+ return;
1559
+ }
1560
+
1561
+ if (typeof ref === "object" && ref !== null && "current" in ref) {
1562
+ (ref as { current: unknown }).current = node;
1563
+ }
1564
+ }