@reckona/mreact-compat 0.0.65 → 0.0.67
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/package.json +4 -3
- package/src/class-component.ts +386 -0
- package/src/context.ts +140 -0
- package/src/devtools.ts +1275 -0
- package/src/dom-children.ts +34 -0
- package/src/dom-props.ts +408 -0
- package/src/element.ts +317 -0
- package/src/event-listeners.ts +27 -0
- package/src/event-priority.ts +1 -0
- package/src/event-replay.ts +154 -0
- package/src/event-types.ts +18 -0
- package/src/events.ts +384 -0
- package/src/fiber-child.ts +364 -0
- package/src/fiber-commit.ts +83 -0
- package/src/fiber-flags.ts +21 -0
- package/src/fiber-host.ts +1564 -0
- package/src/fiber-lanes.ts +99 -0
- package/src/fiber-reconciler.ts +639 -0
- package/src/fiber-scheduler.ts +435 -0
- package/src/fiber-work-loop.ts +224 -0
- package/src/fiber.ts +148 -0
- package/src/flight-decoder.ts +205 -0
- package/src/flight-element-builder.ts +110 -0
- package/src/flight-parser.ts +698 -0
- package/src/flight-protocol.ts +71 -0
- package/src/flight-types.ts +148 -0
- package/src/flight.ts +162 -0
- package/src/hooks.ts +1940 -0
- package/src/hydration.ts +314 -0
- package/src/index.ts +95 -0
- package/src/internal.ts +7 -0
- package/src/jsx-dev-runtime.ts +40 -0
- package/src/jsx-runtime.ts +119 -0
- package/src/prop-comparison.ts +50 -0
- package/src/reconcile-types.ts +26 -0
- package/src/reconciler.ts +692 -0
- package/src/render.ts +29 -0
- package/src/root.ts +493 -0
- package/src/scheduler.ts +157 -0
- package/src/suspense.ts +317 -0
- package/src/thenable.ts +7 -0
- package/src/url-safety.ts +7 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Activity,
|
|
3
|
+
Fragment,
|
|
4
|
+
ERROR_BOUNDARY_TYPE,
|
|
5
|
+
FORWARD_REF_TYPE,
|
|
6
|
+
Profiler,
|
|
7
|
+
Suspense,
|
|
8
|
+
SuspenseList,
|
|
9
|
+
LAZY_TYPE,
|
|
10
|
+
MEMO_TYPE,
|
|
11
|
+
STRICT_MODE_TYPE,
|
|
12
|
+
isReactCompatElement,
|
|
13
|
+
isReactCompatPortal,
|
|
14
|
+
type ReactCompatElement,
|
|
15
|
+
type ReactCompatNode,
|
|
16
|
+
type ReactCompatPortal,
|
|
17
|
+
} from "./element.js";
|
|
18
|
+
import {
|
|
19
|
+
isReactCompatConsumer,
|
|
20
|
+
isReactCompatProvider,
|
|
21
|
+
renderWithContextProvider,
|
|
22
|
+
useContext,
|
|
23
|
+
} from "./context.js";
|
|
24
|
+
import {
|
|
25
|
+
hasStableExternalStores,
|
|
26
|
+
restoreRuntimeSnapshot,
|
|
27
|
+
renderWithProfiler,
|
|
28
|
+
renderWithStrictMode,
|
|
29
|
+
renderWithRootRuntime,
|
|
30
|
+
takeRuntimeSnapshot,
|
|
31
|
+
type RootRuntime,
|
|
32
|
+
} from "./hooks.js";
|
|
33
|
+
import { commitDevToolsRoot } from "./devtools.js";
|
|
34
|
+
import { applyPostChildFormProps, applyProps } from "./dom-props.js";
|
|
35
|
+
import { syncChildNodes, syncScopedChildNodes } from "./dom-children.js";
|
|
36
|
+
import { setLogicalEventParent } from "./events.js";
|
|
37
|
+
import {
|
|
38
|
+
getHydrationScope,
|
|
39
|
+
reportElementTextMismatch,
|
|
40
|
+
reportExtraHydrationNodes,
|
|
41
|
+
reportHydrationNodeTypeMismatch,
|
|
42
|
+
reportMissingHydrationNode,
|
|
43
|
+
reportRecoverable,
|
|
44
|
+
type RenderOptions,
|
|
45
|
+
withHydrationComponentStack,
|
|
46
|
+
} from "./hydration.js";
|
|
47
|
+
import {
|
|
48
|
+
isClassComponentType,
|
|
49
|
+
reconcileClassComponent,
|
|
50
|
+
reconcileErrorBoundary,
|
|
51
|
+
} from "./class-component.js";
|
|
52
|
+
import {
|
|
53
|
+
isSuspensePrimaryRenderActive,
|
|
54
|
+
reconcileSuspense,
|
|
55
|
+
reconcileSuspenseList,
|
|
56
|
+
} from "./suspense.js";
|
|
57
|
+
import { areMemoPropsEqual } from "./prop-comparison.js";
|
|
58
|
+
import type { ReconcileResult } from "./reconcile-types.js";
|
|
59
|
+
|
|
60
|
+
const nodeKeys = new WeakMap<Node, string>();
|
|
61
|
+
|
|
62
|
+
interface MemoRenderState {
|
|
63
|
+
props: Record<string, unknown>;
|
|
64
|
+
nodeCount: number;
|
|
65
|
+
instanceKeys: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const memoRenderStates = new WeakMap<RootRuntime, Map<string, MemoRenderState>>();
|
|
69
|
+
|
|
70
|
+
export function renderIntoContainer(
|
|
71
|
+
container: Element,
|
|
72
|
+
element: unknown,
|
|
73
|
+
runtime: RootRuntime,
|
|
74
|
+
options: RenderOptions & {
|
|
75
|
+
resumeId?: string;
|
|
76
|
+
consumeResumeMarkers?: boolean;
|
|
77
|
+
} = {},
|
|
78
|
+
): void {
|
|
79
|
+
for (let attempt = 0; attempt < 25; attempt += 1) {
|
|
80
|
+
runtime.beginRender();
|
|
81
|
+
let committed = false;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
for (const portalContainer of runtime.portalContainers) {
|
|
85
|
+
portalContainer.replaceChildren();
|
|
86
|
+
}
|
|
87
|
+
runtime.portalContainers.clear();
|
|
88
|
+
|
|
89
|
+
const scope = getHydrationScope(container, options.resumeId);
|
|
90
|
+
const renderOptions = { ...options, eventRoot: container };
|
|
91
|
+
const nodes = reconcileNodeList(
|
|
92
|
+
scope.parent,
|
|
93
|
+
scope.previousNodes,
|
|
94
|
+
element as ReactCompatNode,
|
|
95
|
+
runtime,
|
|
96
|
+
"0",
|
|
97
|
+
renderOptions,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (!hasStableExternalStores(runtime)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
syncScopedChildNodes(scope.parent, scope.before, scope.after, nodes);
|
|
105
|
+
|
|
106
|
+
if (options.consumeResumeMarkers === true) {
|
|
107
|
+
scope.before?.parentNode?.removeChild(scope.before);
|
|
108
|
+
scope.after?.parentNode?.removeChild(scope.after);
|
|
109
|
+
}
|
|
110
|
+
committed = true;
|
|
111
|
+
} finally {
|
|
112
|
+
runtime.endRender(committed);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
runtime.flushEffects();
|
|
116
|
+
commitDevToolsRoot(container, element as ReactCompatNode);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
throw new Error("Store unstable.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function reconcileNodeList(
|
|
124
|
+
parent: ParentNode,
|
|
125
|
+
previousNodes: readonly Node[],
|
|
126
|
+
node: ReactCompatNode,
|
|
127
|
+
runtime: RootRuntime,
|
|
128
|
+
path: string,
|
|
129
|
+
options: RenderOptions = {},
|
|
130
|
+
): Node[] {
|
|
131
|
+
const result = reconcileNode(parent, previousNodes, node, runtime, path, options);
|
|
132
|
+
return result.nodes;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function reconcileNode(
|
|
136
|
+
parent: ParentNode,
|
|
137
|
+
previousNodes: readonly Node[],
|
|
138
|
+
node: ReactCompatNode,
|
|
139
|
+
runtime: RootRuntime,
|
|
140
|
+
path: string,
|
|
141
|
+
options: RenderOptions = {},
|
|
142
|
+
): ReconcileResult {
|
|
143
|
+
if (node === null || node === undefined || typeof node === "boolean") {
|
|
144
|
+
return { nodes: [], consumed: 0 };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
148
|
+
const existing = previousNodes[0];
|
|
149
|
+
const text =
|
|
150
|
+
existing instanceof Text ? existing : document.createTextNode("");
|
|
151
|
+
if (existing === undefined) {
|
|
152
|
+
reportMissingHydrationNode(options, path);
|
|
153
|
+
} else if (!(existing instanceof Text)) {
|
|
154
|
+
reportHydrationNodeTypeMismatch(options, path, "text", existing);
|
|
155
|
+
}
|
|
156
|
+
if (existing instanceof Text && existing.data !== String(node)) {
|
|
157
|
+
reportRecoverable(
|
|
158
|
+
options,
|
|
159
|
+
"text",
|
|
160
|
+
path,
|
|
161
|
+
new Error("Hydration text mismatch."),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
text.data = String(node);
|
|
165
|
+
return { nodes: [text], consumed: existing instanceof Text ? 1 : 0 };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (Array.isArray(node)) {
|
|
169
|
+
return reconcileSequence(parent, previousNodes, node, runtime, path, options);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!isReactCompatElement(node)) {
|
|
173
|
+
if (isReactCompatPortal(node)) {
|
|
174
|
+
return reconcilePortal(node, parent, runtime, path, options);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
throw new Error("Invalid react-compat element.");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return reconcileElement(parent, previousNodes, node, runtime, path, options);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function reconcilePortal(
|
|
184
|
+
portal: ReactCompatPortal,
|
|
185
|
+
parent: ParentNode,
|
|
186
|
+
runtime: RootRuntime,
|
|
187
|
+
path: string,
|
|
188
|
+
options: RenderOptions = {},
|
|
189
|
+
): ReconcileResult {
|
|
190
|
+
runtime.portalContainers.add(portal.container);
|
|
191
|
+
setLogicalEventParent(portal.container, parent);
|
|
192
|
+
const nodes = reconcileNodeList(
|
|
193
|
+
portal.container,
|
|
194
|
+
Array.from(portal.container.childNodes),
|
|
195
|
+
portal.children,
|
|
196
|
+
runtime,
|
|
197
|
+
`${path}.portal`,
|
|
198
|
+
{ ...options, eventRoot: portal.container },
|
|
199
|
+
);
|
|
200
|
+
syncChildNodes(portal.container, nodes);
|
|
201
|
+
return { nodes: [], consumed: 0 };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function reconcileSequence(
|
|
205
|
+
parent: ParentNode,
|
|
206
|
+
previousNodes: readonly Node[],
|
|
207
|
+
children: readonly ReactCompatNode[],
|
|
208
|
+
runtime: RootRuntime,
|
|
209
|
+
path: string,
|
|
210
|
+
options: RenderOptions = {},
|
|
211
|
+
): ReconcileResult {
|
|
212
|
+
const keyedNodes = collectKeyedNodes(previousNodes);
|
|
213
|
+
const nodes: Node[] = [];
|
|
214
|
+
let previousIndex = 0;
|
|
215
|
+
|
|
216
|
+
children.forEach((child, index) => {
|
|
217
|
+
const key = getNodeKey(child);
|
|
218
|
+
const previousForChild =
|
|
219
|
+
key === undefined
|
|
220
|
+
? previousNodes.slice(previousIndex)
|
|
221
|
+
: keyedNodes.size === 0
|
|
222
|
+
? previousNodes.slice(previousIndex)
|
|
223
|
+
: keyedNodes.get(key) === undefined
|
|
224
|
+
? []
|
|
225
|
+
: [keyedNodes.get(key) as Node];
|
|
226
|
+
const result = reconcileNode(
|
|
227
|
+
parent,
|
|
228
|
+
previousForChild,
|
|
229
|
+
child,
|
|
230
|
+
runtime,
|
|
231
|
+
`${path}.${getNodePathSegment(child, index)}`,
|
|
232
|
+
options,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
if (key === undefined || keyedNodes.size === 0) {
|
|
236
|
+
previousIndex += result.consumed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const childNode of result.nodes) {
|
|
240
|
+
if (key !== undefined) {
|
|
241
|
+
nodeKeys.set(childNode, key);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
nodes.push(childNode);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return { nodes, consumed: nodes.length };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function reconcileElement(
|
|
252
|
+
parent: ParentNode,
|
|
253
|
+
previousNodes: readonly Node[],
|
|
254
|
+
element: ReactCompatElement,
|
|
255
|
+
runtime: RootRuntime,
|
|
256
|
+
path: string,
|
|
257
|
+
options: RenderOptions = {},
|
|
258
|
+
): ReconcileResult {
|
|
259
|
+
if (element.type === Fragment) {
|
|
260
|
+
return reconcileNode(
|
|
261
|
+
parent,
|
|
262
|
+
previousNodes,
|
|
263
|
+
element.props.children,
|
|
264
|
+
runtime,
|
|
265
|
+
`${path}.f`,
|
|
266
|
+
options,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (element.type === Activity) {
|
|
271
|
+
const children =
|
|
272
|
+
(element.props as { mode?: unknown }).mode === "hidden"
|
|
273
|
+
? null
|
|
274
|
+
: element.props.children;
|
|
275
|
+
return reconcileNode(
|
|
276
|
+
parent,
|
|
277
|
+
previousNodes,
|
|
278
|
+
children,
|
|
279
|
+
runtime,
|
|
280
|
+
`${path}.activity`,
|
|
281
|
+
options,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (element.type === Profiler) {
|
|
286
|
+
return renderWithProfiler(
|
|
287
|
+
runtime,
|
|
288
|
+
`${path}.profiler`,
|
|
289
|
+
element.props,
|
|
290
|
+
() =>
|
|
291
|
+
reconcileNode(
|
|
292
|
+
parent,
|
|
293
|
+
previousNodes,
|
|
294
|
+
element.props.children,
|
|
295
|
+
runtime,
|
|
296
|
+
`${path}.profiler`,
|
|
297
|
+
options,
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (element.type === STRICT_MODE_TYPE) {
|
|
303
|
+
const snapshot = takeRuntimeSnapshot(runtime);
|
|
304
|
+
try {
|
|
305
|
+
reconcileNode(
|
|
306
|
+
parent,
|
|
307
|
+
[],
|
|
308
|
+
element.props.children,
|
|
309
|
+
runtime,
|
|
310
|
+
`${path}.strict.preview`,
|
|
311
|
+
options,
|
|
312
|
+
);
|
|
313
|
+
} finally {
|
|
314
|
+
restoreRuntimeSnapshot(runtime, snapshot);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return renderWithStrictMode(runtime, () =>
|
|
318
|
+
reconcileNode(
|
|
319
|
+
parent,
|
|
320
|
+
previousNodes,
|
|
321
|
+
element.props.children,
|
|
322
|
+
runtime,
|
|
323
|
+
`${path}.strict`,
|
|
324
|
+
options,
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (element.type === Suspense) {
|
|
330
|
+
return reconcileSuspense(
|
|
331
|
+
parent,
|
|
332
|
+
previousNodes,
|
|
333
|
+
element,
|
|
334
|
+
runtime,
|
|
335
|
+
path,
|
|
336
|
+
options,
|
|
337
|
+
reconcileNode,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (element.type === SuspenseList) {
|
|
342
|
+
return reconcileSuspenseList(
|
|
343
|
+
parent,
|
|
344
|
+
previousNodes,
|
|
345
|
+
element,
|
|
346
|
+
runtime,
|
|
347
|
+
path,
|
|
348
|
+
options,
|
|
349
|
+
reconcileNode,
|
|
350
|
+
reconcileSequence,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (element.type === ERROR_BOUNDARY_TYPE) {
|
|
355
|
+
return reconcileErrorBoundary(
|
|
356
|
+
parent,
|
|
357
|
+
previousNodes,
|
|
358
|
+
element,
|
|
359
|
+
runtime,
|
|
360
|
+
path,
|
|
361
|
+
options,
|
|
362
|
+
reconcileNode,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const elementType = element.type;
|
|
367
|
+
|
|
368
|
+
if (isReactCompatProvider(elementType)) {
|
|
369
|
+
return renderWithContextProvider(
|
|
370
|
+
elementType,
|
|
371
|
+
element.props.value,
|
|
372
|
+
() =>
|
|
373
|
+
reconcileNode(
|
|
374
|
+
parent,
|
|
375
|
+
previousNodes,
|
|
376
|
+
element.props.children,
|
|
377
|
+
runtime,
|
|
378
|
+
`${path}.p`,
|
|
379
|
+
options,
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (isReactCompatConsumer(elementType)) {
|
|
385
|
+
const children = element.props.children;
|
|
386
|
+
const render =
|
|
387
|
+
typeof children === "function"
|
|
388
|
+
? (children as (value: unknown) => ReactCompatNode)
|
|
389
|
+
: () => null;
|
|
390
|
+
return reconcileNode(
|
|
391
|
+
parent,
|
|
392
|
+
previousNodes,
|
|
393
|
+
render(useContext(elementType.context)),
|
|
394
|
+
runtime,
|
|
395
|
+
`${path}.consumer`,
|
|
396
|
+
options,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (isForwardRefType(elementType)) {
|
|
401
|
+
return renderWithRootRuntime(runtime, path, () =>
|
|
402
|
+
reconcileNode(
|
|
403
|
+
parent,
|
|
404
|
+
previousNodes,
|
|
405
|
+
elementType.render(element.props, element.ref),
|
|
406
|
+
runtime,
|
|
407
|
+
`${path}.forwardRef`,
|
|
408
|
+
options,
|
|
409
|
+
),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (isMemoType(elementType)) {
|
|
414
|
+
const memoPath = `${path}.memo`;
|
|
415
|
+
const memoStates = getMemoRenderStates(runtime);
|
|
416
|
+
const previousMemoState = memoStates.get(memoPath);
|
|
417
|
+
|
|
418
|
+
if (
|
|
419
|
+
previousMemoState !== undefined &&
|
|
420
|
+
previousNodes.length >= previousMemoState.nodeCount &&
|
|
421
|
+
!hasDirtyInstance(runtime, previousMemoState.instanceKeys) &&
|
|
422
|
+
areMemoPropsEqual(elementType, previousMemoState.props, element.props)
|
|
423
|
+
) {
|
|
424
|
+
markActiveInstanceKeys(runtime, previousMemoState.instanceKeys);
|
|
425
|
+
return {
|
|
426
|
+
nodes: previousNodes.slice(0, previousMemoState.nodeCount),
|
|
427
|
+
consumed: previousMemoState.nodeCount,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const result = reconcileElement(
|
|
432
|
+
parent,
|
|
433
|
+
previousNodes,
|
|
434
|
+
{
|
|
435
|
+
...element,
|
|
436
|
+
type: elementType.type,
|
|
437
|
+
},
|
|
438
|
+
runtime,
|
|
439
|
+
memoPath,
|
|
440
|
+
options,
|
|
441
|
+
);
|
|
442
|
+
memoStates.set(memoPath, {
|
|
443
|
+
props: { ...element.props },
|
|
444
|
+
nodeCount: result.nodes.length,
|
|
445
|
+
instanceKeys: collectInstanceKeys(runtime, memoPath),
|
|
446
|
+
});
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (isLazyType(elementType)) {
|
|
451
|
+
if (elementType.status === "resolved" && elementType.resolved !== undefined) {
|
|
452
|
+
return reconcileElement(
|
|
453
|
+
parent,
|
|
454
|
+
previousNodes,
|
|
455
|
+
{ ...element, type: elementType.resolved },
|
|
456
|
+
runtime,
|
|
457
|
+
`${path}.lazy`,
|
|
458
|
+
options,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (elementType.status === "rejected") {
|
|
463
|
+
throw elementType.error;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (elementType.status === "uninitialized") {
|
|
467
|
+
elementType.status = "pending";
|
|
468
|
+
elementType.promise = elementType
|
|
469
|
+
.load()
|
|
470
|
+
.then((module) => {
|
|
471
|
+
elementType.status = "resolved";
|
|
472
|
+
elementType.resolved = module.default;
|
|
473
|
+
runtime.rerender();
|
|
474
|
+
})
|
|
475
|
+
.catch((error: unknown) => {
|
|
476
|
+
elementType.status = "rejected";
|
|
477
|
+
elementType.error = error;
|
|
478
|
+
runtime.rerender();
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (isSuspensePrimaryRenderActive()) {
|
|
483
|
+
throw elementType.promise;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return { nodes: [], consumed: 0 };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (isClassComponentType(elementType)) {
|
|
490
|
+
return reconcileClassComponent(
|
|
491
|
+
parent,
|
|
492
|
+
previousNodes,
|
|
493
|
+
elementType,
|
|
494
|
+
element.props,
|
|
495
|
+
runtime,
|
|
496
|
+
path,
|
|
497
|
+
options,
|
|
498
|
+
reconcileNode,
|
|
499
|
+
(instance) => {
|
|
500
|
+
applyRef(element.ref, instance);
|
|
501
|
+
},
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (typeof elementType === "function") {
|
|
506
|
+
const functionComponent = elementType as (
|
|
507
|
+
props: Record<string, unknown>,
|
|
508
|
+
) => ReactCompatNode;
|
|
509
|
+
return renderWithRootRuntime(runtime, path, () =>
|
|
510
|
+
reconcileNode(
|
|
511
|
+
parent,
|
|
512
|
+
previousNodes,
|
|
513
|
+
functionComponent(element.props),
|
|
514
|
+
runtime,
|
|
515
|
+
`${path}.0`,
|
|
516
|
+
withHydrationComponentStack(options, getComponentName(functionComponent)),
|
|
517
|
+
),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (typeof elementType !== "string") {
|
|
522
|
+
throw new Error("Invalid react-compat element type.");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const existing = previousNodes[0];
|
|
526
|
+
if (existing === undefined) {
|
|
527
|
+
reportMissingHydrationNode(options, path);
|
|
528
|
+
} else if (!(existing instanceof HTMLElement)) {
|
|
529
|
+
reportHydrationNodeTypeMismatch(options, path, `<${elementType}>`, existing);
|
|
530
|
+
}
|
|
531
|
+
if (
|
|
532
|
+
existing instanceof HTMLElement &&
|
|
533
|
+
existing.tagName.toLowerCase() !== elementType
|
|
534
|
+
) {
|
|
535
|
+
reportRecoverable(
|
|
536
|
+
options,
|
|
537
|
+
"tag",
|
|
538
|
+
path,
|
|
539
|
+
new Error(
|
|
540
|
+
`Hydration tag mismatch: expected <${elementType}> but found <${existing.tagName.toLowerCase()}>.`,
|
|
541
|
+
),
|
|
542
|
+
);
|
|
543
|
+
reportElementTextMismatch(options, `${path}.c`, existing, element.props.children);
|
|
544
|
+
}
|
|
545
|
+
const domElement =
|
|
546
|
+
existing instanceof HTMLElement &&
|
|
547
|
+
existing.tagName.toLowerCase() === elementType
|
|
548
|
+
? existing
|
|
549
|
+
: document.createElement(elementType);
|
|
550
|
+
|
|
551
|
+
applyProps(domElement, element.props, path, {
|
|
552
|
+
...options,
|
|
553
|
+
preserveHydrationAttributes:
|
|
554
|
+
options.hydration !== undefined &&
|
|
555
|
+
existing instanceof HTMLElement &&
|
|
556
|
+
existing.tagName.toLowerCase() === elementType,
|
|
557
|
+
});
|
|
558
|
+
const previousChildNodes = Array.from(domElement.childNodes);
|
|
559
|
+
const childResult = reconcileNode(
|
|
560
|
+
domElement,
|
|
561
|
+
previousChildNodes,
|
|
562
|
+
element.props.children,
|
|
563
|
+
runtime,
|
|
564
|
+
`${path}.c`,
|
|
565
|
+
options,
|
|
566
|
+
);
|
|
567
|
+
reportExtraHydrationNodes(
|
|
568
|
+
options,
|
|
569
|
+
`${path}.c`,
|
|
570
|
+
previousChildNodes,
|
|
571
|
+
childResult.consumed,
|
|
572
|
+
);
|
|
573
|
+
syncChildNodes(domElement, childResult.nodes);
|
|
574
|
+
applyPostChildFormProps(domElement, element.props);
|
|
575
|
+
applyRef(element.ref, domElement);
|
|
576
|
+
return { nodes: [domElement], consumed: existing === undefined ? 0 : 1 };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function collectKeyedNodes(nodes: readonly Node[]): Map<string, Node> {
|
|
580
|
+
const keyedNodes = new Map<string, Node>();
|
|
581
|
+
|
|
582
|
+
for (const node of nodes) {
|
|
583
|
+
const key = nodeKeys.get(node);
|
|
584
|
+
|
|
585
|
+
if (key !== undefined) {
|
|
586
|
+
keyedNodes.set(key, node);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return keyedNodes;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function getNodePathSegment(node: ReactCompatNode, index: number): string {
|
|
594
|
+
const key = getNodeKey(node);
|
|
595
|
+
return key === undefined ? String(index) : `k:${key}`;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function getNodeKey(node: ReactCompatNode): string | undefined {
|
|
599
|
+
return isReactCompatElement(node) && node.key !== null
|
|
600
|
+
? node.key
|
|
601
|
+
: undefined;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function getComponentName(component: Function): string {
|
|
605
|
+
return component.name === "" ? "Anonymous" : component.name;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function isForwardRefType(
|
|
609
|
+
value: unknown,
|
|
610
|
+
): value is { $$typeof: typeof FORWARD_REF_TYPE; render: (props: Record<string, unknown>, ref: unknown) => ReactCompatNode } {
|
|
611
|
+
return (
|
|
612
|
+
typeof value === "object" &&
|
|
613
|
+
value !== null &&
|
|
614
|
+
(value as { $$typeof?: unknown }).$$typeof === FORWARD_REF_TYPE
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function isMemoType(
|
|
619
|
+
value: unknown,
|
|
620
|
+
): value is {
|
|
621
|
+
$$typeof: typeof MEMO_TYPE;
|
|
622
|
+
type: ReactCompatElement["type"];
|
|
623
|
+
compare?: (
|
|
624
|
+
previous: Record<string, unknown>,
|
|
625
|
+
next: Record<string, unknown>,
|
|
626
|
+
) => boolean;
|
|
627
|
+
} {
|
|
628
|
+
return (
|
|
629
|
+
typeof value === "object" &&
|
|
630
|
+
value !== null &&
|
|
631
|
+
(value as { $$typeof?: unknown }).$$typeof === MEMO_TYPE
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function getMemoRenderStates(runtime: RootRuntime): Map<string, MemoRenderState> {
|
|
636
|
+
const existing = memoRenderStates.get(runtime);
|
|
637
|
+
|
|
638
|
+
if (existing !== undefined) {
|
|
639
|
+
return existing;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const created = new Map<string, MemoRenderState>();
|
|
643
|
+
memoRenderStates.set(runtime, created);
|
|
644
|
+
return created;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function collectInstanceKeys(runtime: RootRuntime, prefix: string): string[] {
|
|
648
|
+
return Array.from(runtime.instances.keys()).filter((key) =>
|
|
649
|
+
key === prefix || key.startsWith(`${prefix}.`),
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function markActiveInstanceKeys(runtime: RootRuntime, keys: readonly string[]): void {
|
|
654
|
+
for (const key of keys) {
|
|
655
|
+
runtime.activeInstanceKeys?.add(key);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function hasDirtyInstance(runtime: RootRuntime, keys: readonly string[]): boolean {
|
|
660
|
+
return keys.some(
|
|
661
|
+
(key) =>
|
|
662
|
+
(runtime.instances.get(key) as { dirty?: boolean } | undefined)?.dirty === true,
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function isLazyType(
|
|
667
|
+
value: unknown,
|
|
668
|
+
): value is {
|
|
669
|
+
$$typeof: typeof LAZY_TYPE;
|
|
670
|
+
load: () => Promise<{ default: ReactCompatElement["type"] }>;
|
|
671
|
+
status: "uninitialized" | "pending" | "resolved" | "rejected";
|
|
672
|
+
promise?: Promise<void>;
|
|
673
|
+
resolved?: ReactCompatElement["type"];
|
|
674
|
+
error?: unknown;
|
|
675
|
+
} {
|
|
676
|
+
return (
|
|
677
|
+
typeof value === "object" &&
|
|
678
|
+
value !== null &&
|
|
679
|
+
(value as { $$typeof?: unknown }).$$typeof === LAZY_TYPE
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function applyRef(ref: unknown, node: unknown): void {
|
|
684
|
+
if (typeof ref === "function") {
|
|
685
|
+
ref(node);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (typeof ref === "object" && ref !== null && "current" in ref) {
|
|
690
|
+
(ref as { current: unknown }).current = node;
|
|
691
|
+
}
|
|
692
|
+
}
|
package/src/render.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createRoot,
|
|
3
|
+
createStreamingHydrationRoot,
|
|
4
|
+
flushSync,
|
|
5
|
+
hydrateRoot,
|
|
6
|
+
render,
|
|
7
|
+
unmountComponentAtNode,
|
|
8
|
+
} from "./root.js";
|
|
9
|
+
export type {
|
|
10
|
+
HydrateRootOptions,
|
|
11
|
+
Root,
|
|
12
|
+
RootOptions,
|
|
13
|
+
SelectiveHydrationBoundary,
|
|
14
|
+
SelectiveHydrationOptions,
|
|
15
|
+
StreamingHydrationRoot,
|
|
16
|
+
StreamingHydrationRootOptions,
|
|
17
|
+
} from "./root.js";
|
|
18
|
+
export { applyStreamingHydrationFragments } from "./hydration.js";
|
|
19
|
+
export {
|
|
20
|
+
enableEventHydrationManifestReplay,
|
|
21
|
+
enableHydrationEventReplay,
|
|
22
|
+
queueHydrationEvent,
|
|
23
|
+
readEventHydrationManifest,
|
|
24
|
+
} from "./event-replay.js";
|
|
25
|
+
export type {
|
|
26
|
+
EventHydrationManifest,
|
|
27
|
+
EventHydrationManifestEntry,
|
|
28
|
+
} from "./event-replay.js";
|
|
29
|
+
export type { HydrationRecoverableErrorInfo } from "./hydration.js";
|