@reckona/mreact-compat 0.0.81 → 0.0.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dom-props.d.ts.map +1 -1
- package/dist/dom-props.js +1 -2
- package/dist/dom-props.js.map +1 -1
- package/dist/fiber-host.d.ts +1 -15
- package/dist/fiber-host.d.ts.map +1 -1
- package/dist/fiber-host.js +1 -891
- package/dist/fiber-host.js.map +1 -1
- package/dist/host-event-binder.d.ts +3 -0
- package/dist/host-event-binder.d.ts.map +1 -0
- package/dist/host-event-binder.js +3 -0
- package/dist/host-event-binder.js.map +1 -0
- package/dist/host-reconciler.d.ts +16 -0
- package/dist/host-reconciler.d.ts.map +1 -0
- package/dist/host-reconciler.js +892 -0
- package/dist/host-reconciler.js.map +1 -0
- package/package.json +2 -2
- package/src/dom-props.ts +3 -2
- package/src/fiber-host.ts +7 -1564
- package/src/host-event-binder.ts +14 -0
- package/src/host-reconciler.ts +1564 -0
|
@@ -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
|
+
}
|