@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.
@@ -0,0 +1,639 @@
1
+ import {
2
+ ERROR_BOUNDARY_TYPE,
3
+ FORWARD_REF_TYPE,
4
+ Fragment,
5
+ LAZY_TYPE,
6
+ MEMO_TYPE,
7
+ STRICT_MODE_TYPE,
8
+ Suspense,
9
+ SuspenseList,
10
+ createElement,
11
+ isReactCompatElement,
12
+ type LazyType,
13
+ type MemoType,
14
+ type ReactCompatNode,
15
+ } from "./element.js";
16
+ import {
17
+ isReactCompatConsumer,
18
+ isReactCompatProvider,
19
+ popContextProvider,
20
+ pushContextProvider,
21
+ useContext,
22
+ type ReactCompatProvider,
23
+ } from "./context.js";
24
+ import { reconcileChildFibers } from "./fiber-child.js";
25
+ import { type Fiber, type FiberRoot } from "./fiber.js";
26
+ import { DidCapture } from "./fiber-flags.js";
27
+ import { mergeLanes, NoLanes } from "./fiber-lanes.js";
28
+ import { isThenable } from "./thenable.js";
29
+ import {
30
+ isClassComponentType,
31
+ type ClassComponentInstance,
32
+ type ClassComponentType,
33
+ } from "./class-component.js";
34
+ import { areMemoPropsEqual } from "./prop-comparison.js";
35
+
36
+ interface ContextProviderFiberState {
37
+ provider: ReactCompatProvider<unknown>;
38
+ pushed: boolean;
39
+ }
40
+
41
+ interface SuspenseFiberState {
42
+ didSuspend: boolean;
43
+ }
44
+
45
+ export function performUnitOfWork(
46
+ root: FiberRoot,
47
+ unit: Fiber,
48
+ ): Fiber | undefined {
49
+ let next: Fiber | undefined;
50
+
51
+ try {
52
+ next = beginWork(unit);
53
+ } catch (error) {
54
+ return captureThrownValue(root, unit, error);
55
+ }
56
+
57
+ if (next !== undefined) {
58
+ return next;
59
+ }
60
+
61
+ return completeUnitOfWork(root, unit);
62
+ }
63
+
64
+ export function beginWork(unit: Fiber): Fiber | undefined {
65
+ if (unit.tag === "host-root") {
66
+ const children = (unit.pendingProps as { children?: ReactCompatNode })
67
+ .children;
68
+ return reconcileChildFibers(
69
+ unit,
70
+ unit.alternate?.child,
71
+ children as ReactCompatNode,
72
+ );
73
+ }
74
+
75
+ if (unit.tag === "host-component") {
76
+ const children = (unit.pendingProps as { children?: ReactCompatNode })
77
+ .children;
78
+ return reconcileChildFibers(
79
+ unit,
80
+ unit.alternate?.child,
81
+ children as ReactCompatNode,
82
+ );
83
+ }
84
+
85
+ if (unit.tag === "fragment") {
86
+ return reconcileChildFibers(
87
+ unit,
88
+ unit.alternate?.child,
89
+ unit.pendingProps as ReactCompatNode,
90
+ );
91
+ }
92
+
93
+ if (unit.tag === "strict-mode") {
94
+ previewStrictModeNode(
95
+ (unit.pendingProps as { children?: ReactCompatNode }).children,
96
+ );
97
+ return reconcileChildFibers(
98
+ unit,
99
+ unit.alternate?.child,
100
+ (unit.pendingProps as { children?: ReactCompatNode }).children,
101
+ );
102
+ }
103
+
104
+ if (unit.tag === "function-component" && isFunctionComponentType(unit.type)) {
105
+ const children = unit.type(unit.pendingProps as Record<string, unknown>);
106
+ return reconcileChildFibers(unit, unit.alternate?.child, children);
107
+ }
108
+
109
+ if (unit.tag === "forward-ref" && isForwardRefType(unit.type)) {
110
+ const props = unit.pendingProps as Record<string, unknown>;
111
+ const children = unit.type.render(props, props.ref ?? null);
112
+ return reconcileChildFibers(unit, unit.alternate?.child, children);
113
+ }
114
+
115
+ if (unit.tag === "memo" && isMemoType(unit.type)) {
116
+ const previousProps = unit.alternate?.memoizedProps as
117
+ | Record<string, unknown>
118
+ | undefined;
119
+ const nextProps = unit.pendingProps as Record<string, unknown>;
120
+
121
+ if (
122
+ unit.alternate !== undefined &&
123
+ previousProps !== undefined &&
124
+ areMemoPropsEqual(unit.type, previousProps, nextProps)
125
+ ) {
126
+ unit.child = unit.alternate.child;
127
+ return undefined;
128
+ }
129
+
130
+ return reconcileChildFibers(
131
+ unit,
132
+ unit.alternate?.child,
133
+ createElement(unit.type.type, nextProps),
134
+ );
135
+ }
136
+
137
+ if (unit.tag === "context-provider" && isReactCompatProvider(unit.type)) {
138
+ const props = unit.pendingProps as {
139
+ value: unknown;
140
+ children?: ReactCompatNode;
141
+ };
142
+ pushContextProvider(unit.type, props.value);
143
+ unit.memoizedState = {
144
+ provider: unit.type,
145
+ pushed: true,
146
+ } satisfies ContextProviderFiberState;
147
+ return reconcileChildFibers(unit, unit.alternate?.child, props.children);
148
+ }
149
+
150
+ if (unit.tag === "context-consumer" && isReactCompatConsumer(unit.type)) {
151
+ const props = unit.pendingProps as {
152
+ children?: unknown;
153
+ };
154
+ const render =
155
+ typeof props.children === "function"
156
+ ? (props.children as (value: unknown) => ReactCompatNode)
157
+ : () => null;
158
+ return reconcileChildFibers(
159
+ unit,
160
+ unit.alternate?.child,
161
+ render(useContext(unit.type.context)),
162
+ );
163
+ }
164
+
165
+ if (unit.tag === "class-component" && isClassComponentType(unit.type)) {
166
+ return beginClassComponent(unit, unit.type);
167
+ }
168
+
169
+ if (unit.tag === "lazy" && isLazyType(unit.type)) {
170
+ const lazyType = unit.type;
171
+
172
+ if (lazyType.status === "resolved" && lazyType.resolved !== undefined) {
173
+ return reconcileChildFibers(
174
+ unit,
175
+ unit.alternate?.child,
176
+ createElement(lazyType.resolved, unit.pendingProps as Record<string, unknown>),
177
+ );
178
+ }
179
+
180
+ if (lazyType.status === "rejected") {
181
+ throw lazyType.error;
182
+ }
183
+
184
+ if (lazyType.status === "uninitialized") {
185
+ lazyType.status = "pending";
186
+ lazyType.promise = lazyType
187
+ .load()
188
+ .then((module) => {
189
+ lazyType.status = "resolved";
190
+ lazyType.resolved = module.default;
191
+ })
192
+ .catch((error: unknown) => {
193
+ lazyType.status = "rejected";
194
+ lazyType.error = error;
195
+ });
196
+ }
197
+
198
+ throw lazyType.promise;
199
+ }
200
+
201
+ if (unit.tag === "suspense" && unit.type === Suspense) {
202
+ unit.memoizedState = { didSuspend: false } satisfies SuspenseFiberState;
203
+ return reconcileChildFibers(
204
+ unit,
205
+ unit.alternate?.child,
206
+ (unit.pendingProps as { children?: ReactCompatNode }).children,
207
+ );
208
+ }
209
+
210
+ if (unit.tag === "suspense-list" && unit.type === SuspenseList) {
211
+ return reconcileChildFibers(
212
+ unit,
213
+ unit.alternate?.child,
214
+ (unit.pendingProps as { children?: ReactCompatNode }).children,
215
+ );
216
+ }
217
+
218
+ if (unit.tag === "error-boundary" && unit.type === ERROR_BOUNDARY_TYPE) {
219
+ return reconcileChildFibers(
220
+ unit,
221
+ unit.alternate?.child,
222
+ (unit.pendingProps as { children?: ReactCompatNode }).children,
223
+ );
224
+ }
225
+
226
+ return undefined;
227
+ }
228
+
229
+ function previewStrictModeNode(node: ReactCompatNode): void {
230
+ if (
231
+ node === null ||
232
+ node === undefined ||
233
+ typeof node === "boolean" ||
234
+ typeof node === "string" ||
235
+ typeof node === "number"
236
+ ) {
237
+ return;
238
+ }
239
+
240
+ if (Array.isArray(node)) {
241
+ for (const child of node) {
242
+ previewStrictModeNode(child);
243
+ }
244
+ return;
245
+ }
246
+
247
+ if (!isReactCompatElement(node)) {
248
+ return;
249
+ }
250
+
251
+ if (isClassComponentType(node.type)) {
252
+ const instance = new node.type(node.props as Record<string, unknown>);
253
+ previewStrictModeNode(instance.render());
254
+ return;
255
+ }
256
+
257
+ if (isForwardRefType(node.type)) {
258
+ previewStrictModeNode(
259
+ node.type.render(
260
+ node.props as Record<string, unknown>,
261
+ (node.props as { ref?: unknown }).ref ?? null,
262
+ ),
263
+ );
264
+ return;
265
+ }
266
+
267
+ if (isMemoType(node.type)) {
268
+ previewStrictModeNode(createElement(node.type.type, node.props));
269
+ return;
270
+ }
271
+
272
+ if (typeof node.type === "function") {
273
+ const component = node.type as (props: Record<string, unknown>) => ReactCompatNode;
274
+ previewStrictModeNode(
275
+ component(node.props as Record<string, unknown>),
276
+ );
277
+ return;
278
+ }
279
+
280
+ previewStrictModeNode(node.props.children as ReactCompatNode);
281
+ }
282
+
283
+ export function completeWork(unit: Fiber): void {
284
+ if (unit.tag === "context-provider") {
285
+ popPushedContextProvider(unit);
286
+ }
287
+
288
+ if (unit.tag === "host-component") {
289
+ const current = unit.alternate;
290
+
291
+ unit.stateNode =
292
+ current?.tag === "host-component" &&
293
+ current.type === unit.type &&
294
+ current.stateNode instanceof HTMLElement
295
+ ? current.stateNode
296
+ : document.createElement(String(unit.type));
297
+ return;
298
+ }
299
+
300
+ if (unit.tag === "host-text") {
301
+ const current = unit.alternate;
302
+
303
+ unit.stateNode =
304
+ current?.tag === "host-text" && current.stateNode instanceof Text
305
+ ? current.stateNode
306
+ : document.createTextNode("");
307
+ }
308
+ }
309
+
310
+ export function cleanupUnfinishedWork(unit: Fiber | undefined): void {
311
+ if (unit === undefined) {
312
+ return;
313
+ }
314
+
315
+ let cursor: Fiber | undefined = unit;
316
+
317
+ while (cursor !== undefined) {
318
+ cleanupUnfinishedWork(cursor.child);
319
+ popPushedContextProvider(cursor);
320
+ cursor = cursor.sibling;
321
+ }
322
+ }
323
+
324
+ function completeUnitOfWork(
325
+ root: FiberRoot,
326
+ completedWork: Fiber,
327
+ ): Fiber | undefined {
328
+ let unit: Fiber | undefined = completedWork;
329
+
330
+ while (unit !== undefined) {
331
+ completeWork(unit);
332
+ bubbleCompletedProperties(unit);
333
+
334
+ if (unit.sibling !== undefined) {
335
+ return unit.sibling;
336
+ }
337
+
338
+ if (unit.return === undefined) {
339
+ unit.memoizedProps = unit.pendingProps;
340
+ root.finishedWork = unit;
341
+ return undefined;
342
+ }
343
+
344
+ unit.memoizedProps = unit.pendingProps;
345
+ unit = unit.return;
346
+ }
347
+
348
+ return undefined;
349
+ }
350
+
351
+ function bubbleCompletedProperties(unit: Fiber): void {
352
+ let subtreeFlags = unit.subtreeFlags;
353
+ let childLanes = NoLanes;
354
+ let child = unit.child;
355
+
356
+ while (child !== undefined) {
357
+ subtreeFlags |= child.subtreeFlags | child.flags;
358
+ childLanes = mergeLanes(childLanes, mergeLanes(child.lanes, child.childLanes));
359
+ child.return = unit;
360
+ child = child.sibling;
361
+ }
362
+
363
+ unit.subtreeFlags = subtreeFlags;
364
+ unit.childLanes = childLanes;
365
+ }
366
+
367
+ export function canReconcileConcurrently(node: ReactCompatNode): boolean {
368
+ if (
369
+ node === null ||
370
+ node === undefined ||
371
+ typeof node === "boolean" ||
372
+ typeof node === "string" ||
373
+ typeof node === "number"
374
+ ) {
375
+ return true;
376
+ }
377
+
378
+ if (Array.isArray(node)) {
379
+ return node.every(canReconcileConcurrently);
380
+ }
381
+
382
+ if (!isReactCompatElement(node)) {
383
+ return false;
384
+ }
385
+
386
+ if (
387
+ typeof node.type === "string" ||
388
+ node.type === Fragment ||
389
+ node.type === STRICT_MODE_TYPE
390
+ ) {
391
+ return canReconcileConcurrently(node.props.children as ReactCompatNode);
392
+ }
393
+
394
+ if (node.type === Suspense || node.type === SuspenseList) {
395
+ return true;
396
+ }
397
+
398
+ if (node.type === ERROR_BOUNDARY_TYPE) {
399
+ return canReconcileConcurrently(node.props.children as ReactCompatNode);
400
+ }
401
+
402
+ if (isReactCompatProvider(node.type)) {
403
+ return canReconcileConcurrently(node.props.children as ReactCompatNode);
404
+ }
405
+
406
+ if (isReactCompatConsumer(node.type)) {
407
+ return typeof node.props.children === "function";
408
+ }
409
+
410
+ if (isMemoType(node.type)) {
411
+ return canReconcileElementTypeConcurrently(node.type.type);
412
+ }
413
+
414
+ return (
415
+ isFunctionComponentType(node.type) ||
416
+ isForwardRefType(node.type) ||
417
+ isLazyType(node.type) ||
418
+ isClassComponentType(node.type)
419
+ );
420
+ }
421
+
422
+ function popPushedContextProvider(unit: Fiber): void {
423
+ const state = unit.memoizedState as ContextProviderFiberState | undefined;
424
+
425
+ if (state?.pushed === true) {
426
+ popContextProvider(state.provider);
427
+ state.pushed = false;
428
+ }
429
+ }
430
+
431
+ function isFunctionComponentType(
432
+ type: unknown,
433
+ ): type is (props: Record<string, unknown>) => ReactCompatNode {
434
+ return typeof type === "function";
435
+ }
436
+
437
+ function isForwardRefType(
438
+ type: unknown,
439
+ ): type is {
440
+ $$typeof: typeof FORWARD_REF_TYPE;
441
+ render: (props: Record<string, unknown>, ref: unknown) => ReactCompatNode;
442
+ } {
443
+ return (
444
+ typeof type === "object" &&
445
+ type !== null &&
446
+ (type as { $$typeof?: unknown }).$$typeof === FORWARD_REF_TYPE
447
+ );
448
+ }
449
+
450
+ function isMemoType(
451
+ type: unknown,
452
+ ): type is MemoType<Record<string, unknown>> {
453
+ return (
454
+ typeof type === "object" &&
455
+ type !== null &&
456
+ (type as { $$typeof?: unknown }).$$typeof === MEMO_TYPE
457
+ );
458
+ }
459
+
460
+ function isLazyType(
461
+ type: unknown,
462
+ ): type is LazyType<Record<string, unknown>> {
463
+ return (
464
+ typeof type === "object" &&
465
+ type !== null &&
466
+ (type as { $$typeof?: unknown }).$$typeof === LAZY_TYPE
467
+ );
468
+ }
469
+
470
+ function canReconcileElementTypeConcurrently(type: unknown): boolean {
471
+ return (
472
+ typeof type === "string" ||
473
+ type === Fragment ||
474
+ isFunctionComponentType(type) ||
475
+ isForwardRefType(type) ||
476
+ isLazyType(type) ||
477
+ (isMemoType(type) && canReconcileElementTypeConcurrently(type.type))
478
+ );
479
+ }
480
+
481
+ function captureThrownValue(
482
+ root: FiberRoot,
483
+ source: Fiber,
484
+ thrownValue: unknown,
485
+ ): Fiber | undefined {
486
+ let boundary = source.return;
487
+
488
+ while (boundary !== undefined) {
489
+ if (isThenable(thrownValue) && boundary.tag === "suspense") {
490
+ return captureSuspenseBoundary(root, boundary);
491
+ }
492
+
493
+ if (!isThenable(thrownValue) && boundary.tag === "error-boundary") {
494
+ return captureErrorBoundary(root, boundary, thrownValue);
495
+ }
496
+
497
+ if (!isThenable(thrownValue) && boundary.tag === "class-component") {
498
+ const captured = captureClassErrorBoundary(root, boundary, thrownValue);
499
+
500
+ if (captured !== undefined) {
501
+ return captured;
502
+ }
503
+ }
504
+
505
+ boundary = boundary.return;
506
+ }
507
+
508
+ throw thrownValue;
509
+ }
510
+
511
+ function beginClassComponent(
512
+ unit: Fiber,
513
+ type: ClassComponentType,
514
+ ): Fiber | undefined {
515
+ const nextProps = unit.pendingProps as Record<string, unknown>;
516
+ const currentInstance =
517
+ unit.alternate?.stateNode instanceof type
518
+ ? unit.alternate.stateNode
519
+ : undefined;
520
+ const instance = currentInstance ?? new type(nextProps);
521
+ const nextState = instance.state ?? {};
522
+
523
+ unit.stateNode = instance;
524
+
525
+ if (
526
+ unit.alternate !== undefined &&
527
+ instance.shouldComponentUpdate?.(nextProps, nextState) === false
528
+ ) {
529
+ instance.props = nextProps;
530
+ unit.child = unit.alternate.child;
531
+ return undefined;
532
+ }
533
+
534
+ instance.props = nextProps;
535
+ return reconcileChildFibers(unit, unit.alternate?.child, instance.render());
536
+ }
537
+
538
+ function captureClassErrorBoundary(
539
+ root: FiberRoot,
540
+ boundary: Fiber,
541
+ thrownValue: unknown,
542
+ ): Fiber | undefined {
543
+ if (!isClassComponentType(boundary.type)) {
544
+ return undefined;
545
+ }
546
+
547
+ const instance = boundary.stateNode as ClassComponentInstance | undefined;
548
+
549
+ if (instance === undefined || !isClassErrorBoundary(boundary.type, instance)) {
550
+ return undefined;
551
+ }
552
+
553
+ cleanupUnfinishedWork(boundary.child);
554
+ boundary.flags |= DidCapture;
555
+ const error = thrownValue instanceof Error ? thrownValue : new Error(String(thrownValue));
556
+ const derivedState = boundary.type.getDerivedStateFromError?.(error);
557
+
558
+ if (derivedState !== undefined && derivedState !== null) {
559
+ instance.state = {
560
+ ...instance.state,
561
+ ...derivedState,
562
+ };
563
+ }
564
+
565
+ instance.componentDidCatch?.(error, { componentStack: "" });
566
+ boundary.child = reconcileChildFibers(
567
+ boundary,
568
+ boundary.alternate?.child,
569
+ instance.render(),
570
+ );
571
+ return boundary.child ?? completeUnitOfWork(root, boundary);
572
+ }
573
+
574
+ function isClassErrorBoundary(
575
+ type: ClassComponentType,
576
+ instance: ClassComponentInstance,
577
+ ): boolean {
578
+ return (
579
+ typeof type.getDerivedStateFromError === "function" ||
580
+ typeof instance.componentDidCatch === "function"
581
+ );
582
+ }
583
+
584
+ function captureSuspenseBoundary(
585
+ root: FiberRoot,
586
+ boundary: Fiber,
587
+ ): Fiber | undefined {
588
+ cleanupUnfinishedWork(boundary.child);
589
+ boundary.flags |= DidCapture;
590
+ boundary.memoizedState = { didSuspend: true } satisfies SuspenseFiberState;
591
+ boundary.child = reconcileChildFibers(
592
+ boundary,
593
+ boundary.alternate?.child,
594
+ (boundary.pendingProps as { fallback?: ReactCompatNode }).fallback,
595
+ );
596
+ applySuspenseListRevealOrder(boundary);
597
+ return boundary.child ?? completeUnitOfWork(root, boundary);
598
+ }
599
+
600
+ function captureErrorBoundary(
601
+ root: FiberRoot,
602
+ boundary: Fiber,
603
+ thrownValue: unknown,
604
+ ): Fiber | undefined {
605
+ cleanupUnfinishedWork(boundary.child);
606
+ boundary.flags |= DidCapture;
607
+ const error = thrownValue instanceof Error ? thrownValue : new Error(String(thrownValue));
608
+ const props = boundary.pendingProps as {
609
+ fallback?: unknown;
610
+ onError?: unknown;
611
+ };
612
+
613
+ if (typeof props.onError === "function") {
614
+ (props.onError as (error: Error) => void)(error);
615
+ }
616
+
617
+ const fallback =
618
+ typeof props.fallback === "function"
619
+ ? (props.fallback as (error: Error) => ReactCompatNode)(error)
620
+ : null;
621
+ boundary.child = reconcileChildFibers(boundary, boundary.alternate?.child, fallback);
622
+ return boundary.child ?? completeUnitOfWork(root, boundary);
623
+ }
624
+
625
+ function applySuspenseListRevealOrder(boundary: Fiber): void {
626
+ const parent = boundary.return;
627
+
628
+ if (parent?.tag !== "suspense-list") {
629
+ return;
630
+ }
631
+
632
+ const revealOrder = (parent.pendingProps as { revealOrder?: unknown }).revealOrder;
633
+
634
+ if (revealOrder === "forwards") {
635
+ boundary.sibling = undefined;
636
+ } else if (revealOrder === "backwards") {
637
+ parent.child = boundary;
638
+ }
639
+ }