11
ReactComponentInfo,
12
ReactDebugInfo,
13
ReactAsyncInfo,
14
+ ReactIOInfo,
15
} from 'shared/ReactTypes';
16
17
import {
174
logCount: number, // total number of errors/warnings last seen
175
treeBaseDuration: number, // the profiled time of the last render of this subtree
176
suspendedBy: null | Array<ReactAsyncInfo>, // things that suspended in the children position of this component
177
+ suspenseNode: null | SuspenseNode,
178
data: Fiber, // one of a Fiber pair
179
};
180
189
logCount: 0,
190
treeBaseDuration: 0,
191
suspendedBy: null,
192
+ suspenseNode: null,
193
data: fiber,
194
};
195
}
206
logCount: number, // total number of errors/warnings last seen
207
treeBaseDuration: number, // the profiled time of the last render of this subtree
208
suspendedBy: null | Array<ReactAsyncInfo>, // not used
209
+ suspenseNode: null | SuspenseNode,
210
data: Fiber, // one of a Fiber pair
211
};
212
222
logCount: 0,
223
treeBaseDuration: 0,
224
suspendedBy: null,
225
+ suspenseNode: null,
226
data: fiber,
227
}: any);
228
}
242
logCount: number, // total number of errors/warnings last seen
243
treeBaseDuration: number, // the profiled time of the last render of this subtree
244
suspendedBy: null | Array<ReactAsyncInfo>, // things that blocked the server component's child from rendering
245
+ suspenseNode: null,
246
// The latest info for this instance. This can be updated over time and the
247
// same info can appear in more than once ServerComponentInstance.
248
data: ReactComponentInfo,
261
logCount: 0,
262
treeBaseDuration: 0,
263
suspendedBy: null,
264
+ suspenseNode: null,
265
data: debugEntry,
266
};
267
}
268
269
type DevToolsInstance = FiberInstance | VirtualInstance | FilteredFiberInstance;
270
271
+type SuspenseNode = {
272
+ // The Instance can be a Suspense boundary, a SuspenseList Row, or HostRoot.
273
+ // It can also be disconnected from the main tree if it's a Filtered Instance.
274
+ instance: FiberInstance | FilteredFiberInstance,
275
+ parent: null | SuspenseNode,
276
+ firstChild: null | SuspenseNode,
277
+ nextSibling: null | SuspenseNode,
278
+ suspendedBy: Map<ReactIOInfo, Set<DevToolsInstance | null>>, // Tracks which data we're suspended by and the children that suspend it.
279
+ // Track whether any of the items in suspendedBy are unique this this Suspense boundaries or if they're all
280
+ // also in the parent sets. This determine whether this could contribute in the loading sequence.
281
+ hasUniqueSuspenders: boolean,
282
+};
283
+
284
+function createSuspenseNode(
285
+ instance: FiberInstance | FilteredFiberInstance,
286
+): SuspenseNode {
287
+ return (instance.suspenseNode = {
288
+ instance: instance,
289
+ parent: null,
290
+ firstChild: null,
291
+ nextSibling: null,
292
+ suspendedBy: new Map(),
293
+ hasUniqueSuspenders: false,
294
+ });
295
+}
296
+
297
type getDisplayNameForFiberType = (fiber: Fiber) => string | null;
298
type getTypeSymbolType = (type: any) => symbol | string | number;
299
2400
// the current parent here as well.
2401
let reconcilingParent: null | DevToolsInstance = null;
2402
2403
+ let remainingReconcilingChildrenSuspenseNodes: null | SuspenseNode = null;
2404
+ // The previously placed child.
2405
+ let previouslyReconciledSiblingSuspenseNode: null | SuspenseNode = null;
2406
+ // To save on stack allocation and ensure that they are updated as a pair, we also store
2407
+ // the current parent here as well.
2408
+ let reconcilingParentSuspenseNode: null | SuspenseNode = null;
2409
+
2410
+ function isSuspenseInFallback(suspenseNode: SuspenseNode) {
2411
+ const fiber = suspenseNode.instance.data;
2412
+ return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
2413
+ }
2414
+
2415
+ function ioExistsInSuspenseAncestor(
2416
+ suspenseNode: SuspenseNode,
2417
+ ioInfo: ReactIOInfo,
2418
+ ): boolean {
2419
+ let ancestor = suspenseNode.parent;
2420
+ while (ancestor !== null) {
2421
+ if (ancestor.suspendedBy.has(ioInfo)) {
2422
+ return true;
2423
+ }
2424
+ ancestor = ancestor.parent;
2425
+ }
2426
+ return false;
2427
+ }
2428
+
2429
function insertSuspendedBy(asyncInfo: ReactAsyncInfo): void {
2430
+ let parentSuspenseNode = reconcilingParentSuspenseNode;
2431
+ while (
2432
+ parentSuspenseNode !== null &&
2433
+ isSuspenseInFallback(parentSuspenseNode)
2434
+ ) {
2435
+ // If we have something that suspends inside the fallback tree of a Suspense boundary, then
2436
+ // we bubble that up to the nearest parent Suspense boundary that isn't in fallback mode.
2437
+ parentSuspenseNode = parentSuspenseNode.parent;
2438
+ }
2439
const parentInstance = reconcilingParent;
2372
- if (parentInstance === null) {
2373
- // Suspending at the root is not attributed to any particular component
2374
- // TODO: It should be attributed to the shell.
2440
+ if (parentSuspenseNode !== null) {
2441
+ const suspendedBy = parentSuspenseNode.suspendedBy;
2442
+ const ioInfo = asyncInfo.awaited;
2443
+ let suspendedBySet = suspendedBy.get(ioInfo);
2444
+ if (suspendedBySet === undefined) {
2445
+ suspendedBySet = new Set();
2446
+ suspendedBy.set(asyncInfo.awaited, suspendedBySet);
2447
+ }
2448
+ // The child of the Suspense boundary that was suspended on this, or null if suspended at the root.
2449
+ // This is used to keep track of how many dependents are still alive and also to get information
2450
+ // like owner instances to link down into the tree.
2451
+ if (!suspendedBySet.has(parentInstance)) {
2452
+ suspendedBySet.add(parentInstance);
2453
+ if (
2454
+ !parentSuspenseNode.hasUniqueSuspenders &&
2455
+ !ioExistsInSuspenseAncestor(parentSuspenseNode, ioInfo)
2456
+ ) {
2457
+ // This didn't exist in the parent before, so let's mark this boundary as having a unique suspender.
2458
+ parentSuspenseNode.hasUniqueSuspenders = true;
2459
+ }
2460
+ }
2461
+ }
2462
+ if (parentInstance !== null) {
2463
+ // Suspending at the root is not attributed to any particular component other than the SuspenseNode.
2464
+ const suspendedBy = parentInstance.suspendedBy;
2465
+ if (suspendedBy === null) {
2466
+ parentInstance.suspendedBy = [asyncInfo];
2467
+ } else if (suspendedBy.indexOf(asyncInfo) === -1) {
2468
+ suspendedBy.push(asyncInfo);
2469
+ }
2470
+ }
2471
+ }
2472
+
2473
+ function getAwaitInSuspendedByFromIO(
2474
+ suspensedBy: Array<ReactAsyncInfo>,
2475
+ ioInfo: ReactIOInfo,
2476
+ ): null | ReactAsyncInfo {
2477
+ for (let i = 0; i < suspensedBy.length; i++) {
2478
+ const asyncInfo = suspensedBy[i];
2479
+ if (asyncInfo.awaited === ioInfo) {
2480
+ return asyncInfo;
2481
+ }
2482
+ }
2483
+ return null;
2484
+ }
2485
+
2486
+ function unblockSuspendedBy(
2487
+ parentSuspenseNode: SuspenseNode,
2488
+ ioInfo: ReactIOInfo,
2489
+ ): void {
2490
+ const firstChild = parentSuspenseNode.firstChild;
2491
+ if (firstChild === null) {
2492
return;
2493
}
2377
- const suspendedBy = parentInstance.suspendedBy;
2378
- if (suspendedBy === null) {
2379
- parentInstance.suspendedBy = [asyncInfo];
2380
- } else if (suspendedBy.indexOf(asyncInfo) === -1) {
2381
- suspendedBy.push(asyncInfo);
2494
+ let node: SuspenseNode = firstChild;
2495
+ while (node !== null) {
2496
+ if (node.suspendedBy.has(ioInfo)) {
2497
+ // We have found a child boundary that depended on the unblocked I/O.
2498
+ // It can now be marked as having unique suspenders. We can skip its children
2499
+ // since they'll still be blocked by this one.
2500
+ node.hasUniqueSuspenders = true;
2501
+ } else if (node.firstChild !== null) {
2502
+ node = node.firstChild;
2503
+ continue;
2504
+ }
2505
+ while (node.nextSibling === null) {
2506
+ if (node.parent === null || node.parent === parentSuspenseNode) {
2507
+ return;
2508
+ }
2509
+ node = node.parent;
2510
+ }
2511
+ node = node.nextSibling;
2512
+ }
2513
+ }
2514
+
2515
+ function removePreviousSuspendedBy(
2516
+ instance: DevToolsInstance,
2517
+ previousSuspendedBy: null | Array<ReactAsyncInfo>,
2518
+ ): void {
2519
+ // Remove any async info from the parent, if they were in the previous set but
2520
+ // is no longer in the new set.
2521
+ const parentSuspenseNode = reconcilingParentSuspenseNode;
2522
+ if (previousSuspendedBy !== null && parentSuspenseNode !== null) {
2523
+ const nextSuspendedBy = instance.suspendedBy;
2524
+ for (let i = 0; i < previousSuspendedBy.length; i++) {
2525
+ const asyncInfo = previousSuspendedBy[i];
2526
+ if (
2527
+ nextSuspendedBy === null ||
2528
+ (nextSuspendedBy.indexOf(asyncInfo) === -1 &&
2529
+ getAwaitInSuspendedByFromIO(nextSuspendedBy, asyncInfo.awaited) ===
2530
+ null)
2531
+ ) {
2532
+ // This IO entry is no longer blocking the current tree.
2533
+ // Let's remove it from the parent SuspenseNode.
2534
+ const ioInfo = asyncInfo.awaited;
2535
+ const suspendedBySet = parentSuspenseNode.suspendedBy.get(ioInfo);
2536
+ if (
2537
+ suspendedBySet === undefined ||
2538
+ !suspendedBySet.delete(instance)
2539
+ ) {
2540
+ throw new Error(
2541
+ 'We are cleaning up async info that was not on the parent Suspense boundary. ' +
2542
+ 'This is a bug in React.',
2543
+ );
2544
+ }
2545
+ if (suspendedBySet.size === 0) {
2546
+ parentSuspenseNode.suspendedBy.delete(asyncInfo.awaited);
2547
+ }
2548
+ if (
2549
+ parentSuspenseNode.hasUniqueSuspenders &&
2550
+ !ioExistsInSuspenseAncestor(parentSuspenseNode, ioInfo)
2551
+ ) {
2552
+ // This entry wasn't in any ancestor and is no longer in this suspense boundary.
2553
+ // This means that a child might now be the unique suspender for this IO.
2554
+ // Search the child boundaries to see if we can reveal any of them.
2555
+ unblockSuspendedBy(parentSuspenseNode, ioInfo);
2556
+ }
2557
+ }
2558
+ }
2559
}
2560
}
2561
2575
previouslyReconciledSibling = instance;
2576
}
2577
instance.nextSibling = null;
2578
+ // Insert any SuspenseNode into its parent Node.
2579
+ const suspenseNode = instance.suspenseNode;
2580
+ if (suspenseNode !== null) {
2581
+ const parentNode = reconcilingParentSuspenseNode;
2582
+ if (parentNode !== null) {
2583
+ suspenseNode.parent = parentNode;
2584
+ if (previouslyReconciledSiblingSuspenseNode === null) {
2585
+ previouslyReconciledSiblingSuspenseNode = suspenseNode;
2586
+ parentNode.firstChild = suspenseNode;
2587
+ } else {
2588
+ previouslyReconciledSiblingSuspenseNode.nextSibling = suspenseNode;
2589
+ previouslyReconciledSiblingSuspenseNode = suspenseNode;
2590
+ }
2591
+ suspenseNode.nextSibling = null;
2592
+ }
2593
+ }
2594
}
2595
2596
function moveChild(
2640
}
2641
instance.nextSibling = null;
2642
instance.parent = null;
2643
+
2644
+ // Remove any SuspenseNode from its parent.
2645
+ const suspenseNode = instance.suspenseNode;
2646
+ if (suspenseNode !== null && suspenseNode.parent !== null) {
2647
+ const parentNode = reconcilingParentSuspenseNode;
2648
+ if (parentNode === null) {
2649
+ throw new Error('Should not have a parent if we are at the root');
2650
+ }
2651
+ if (suspenseNode.parent !== parentNode) {
2652
+ throw new Error(
2653
+ 'Cannot remove a node from a different parent than is being reconciled.',
2654
+ );
2655
+ }
2656
+ let previousSuspenseSibling = remainingReconcilingChildrenSuspenseNodes;
2657
+ if (previousSuspenseSibling === suspenseNode) {
2658
+ // We're first in the remaining set. Remove us.
2659
+ remainingReconcilingChildrenSuspenseNodes = suspenseNode.nextSibling;
2660
+ } else {
2661
+ // Search for our previous sibling and remove us.
2662
+ while (previousSuspenseSibling !== null) {
2663
+ if (previousSuspenseSibling.nextSibling === suspenseNode) {
2664
+ previousSuspenseSibling.nextSibling = suspenseNode.nextSibling;
2665
+ break;
2666
+ }
2667
+ previousSuspenseSibling = previousSuspenseSibling.nextSibling;
2668
+ }
2669
+ }
2670
+ suspenseNode.nextSibling = null;
2671
+ suspenseNode.parent = null;
2672
+ }
2673
}
2674
2675
function unmountRemainingChildren() {
2877
): void {
2878
const shouldIncludeInTree = !shouldFilterFiber(fiber);
2879
let newInstance = null;
2880
+ let newSuspenseNode = null;
2881
if (shouldIncludeInTree) {
2882
newInstance = recordMount(fiber, reconcilingParent);
2883
+ if (fiber.tag === SuspenseComponent || fiber.tag === HostRoot) {
2884
+ newSuspenseNode = createSuspenseNode(newInstance);
2885
+ }
2886
insertChild(newInstance);
2887
if (__DEBUG__) {
2888
debug('mountFiberRecursively()', newInstance, reconcilingParent);
2889
}
2890
} else if (
2664
- reconcilingParent !== null &&
2665
- reconcilingParent.kind === VIRTUAL_INSTANCE
2891
+ (reconcilingParent !== null &&
2892
+ reconcilingParent.kind === VIRTUAL_INSTANCE) ||
2893
+ fiber.tag === SuspenseComponent
2894
) {
2895
// If the parent is a Virtual Instance and we filtered this Fiber we include a
2668
- // hidden node.
2669
-
2896
+ // hidden node. We also include this if it's a Suspense boundary so we can track those
2897
+ // in the Suspense tree.
2898
if (
2899
+ reconcilingParent !== null &&
2900
+ reconcilingParent.kind === VIRTUAL_INSTANCE &&
2901
reconcilingParent.data === fiber._debugOwner &&
2902
fiber._debugStack != null &&
2903
reconcilingParent.source === null
2908
}
2909
2910
newInstance = createFilteredFiberInstance(fiber);
2911
+ if (fiber.tag === SuspenseComponent) {
2912
+ newSuspenseNode = createSuspenseNode(newInstance);
2913
+ }
2914
insertChild(newInstance);
2915
if (__DEBUG__) {
2916
debug('mountFiberRecursively()', newInstance, reconcilingParent);
2927
const stashedParent = reconcilingParent;
2928
const stashedPrevious = previouslyReconciledSibling;
2929
const stashedRemaining = remainingReconcilingChildren;
2930
+ const stashedSuspenseParent = reconcilingParentSuspenseNode;
2931
+ const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
2932
+ const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
2933
if (newInstance !== null) {
2934
// Push a new DevTools instance parent while reconciling this subtree.
2935
reconcilingParent = newInstance;
2936
previouslyReconciledSibling = null;
2937
remainingReconcilingChildren = null;
2938
}
2939
+ if (newSuspenseNode !== null) {
2940
+ reconcilingParentSuspenseNode = newSuspenseNode;
2941
+ previouslyReconciledSiblingSuspenseNode = null;
2942
+ remainingReconcilingChildrenSuspenseNodes = null;
2943
+ }
2944
try {
2945
if (traceUpdatesEnabled) {
2946
if (traceNearestHostComponentUpdate) {
2994
);
2995
}
2996
}
2997
+ // TODO: Track SuspenseNode in resuspended trees.
2998
} else {
2999
let primaryChild: Fiber | null = null;
3000
const areSuspenseChildrenConditionallyWrapped =
3026
previouslyReconciledSibling = stashedPrevious;
3027
remainingReconcilingChildren = stashedRemaining;
3028
}
3029
+ if (newSuspenseNode !== null) {
3030
+ reconcilingParentSuspenseNode = stashedSuspenseParent;
3031
+ previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
3032
+ remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
3033
+ }
3034
}
3035
3036
// We're exiting this Fiber now, and entering its siblings.
3048
const stashedParent = reconcilingParent;
3049
const stashedPrevious = previouslyReconciledSibling;
3050
const stashedRemaining = remainingReconcilingChildren;
3051
+ const stashedSuspenseParent = reconcilingParentSuspenseNode;
3052
+ const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
3053
+ const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
3054
+ const previousSuspendedBy = instance.suspendedBy;
3055
// Push a new DevTools instance parent while reconciling this subtree.
3056
reconcilingParent = instance;
3057
previouslyReconciledSibling = null;
3059
remainingReconcilingChildren = instance.firstChild;
3060
instance.firstChild = null;
3061
instance.suspendedBy = null;
3062
+
3063
+ if (instance.suspenseNode !== null) {
3064
+ reconcilingParentSuspenseNode = instance.suspenseNode;
3065
+ previouslyReconciledSiblingSuspenseNode = null;
3066
+ remainingReconcilingChildrenSuspenseNodes = null;
3067
+ }
3068
+
3069
try {
3070
// Unmount the remaining set.
3071
unmountRemainingChildren();
3072
+ removePreviousSuspendedBy(instance, previousSuspendedBy);
3073
} finally {
3074
reconcilingParent = stashedParent;
3075
previouslyReconciledSibling = stashedPrevious;
3076
remainingReconcilingChildren = stashedRemaining;
3077
+ reconcilingParentSuspenseNode = stashedSuspenseParent;
3078
+ previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
3079
+ remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
3080
}
3081
if (instance.kind === FIBER_INSTANCE) {
3082
recordUnmount(instance);
3263
const stashedParent = reconcilingParent;
3264
const stashedPrevious = previouslyReconciledSibling;
3265
const stashedRemaining = remainingReconcilingChildren;
3266
+ const previousSuspendedBy = virtualInstance.suspendedBy;
3267
// Push a new DevTools instance parent while reconciling this subtree.
3268
reconcilingParent = virtualInstance;
3269
previouslyReconciledSibling = null;
3284
) {
3285
recordResetChildren(virtualInstance);
3286
}
3287
+ removePreviousSuspendedBy(virtualInstance, previousSuspendedBy);
3288
// Update the errors/warnings count. If this Instance has switched to a different
3289
// ReactComponentInfo instance, such as when refreshing Server Components, then
3290
// we replace all the previous logs with the ones associated with the new ones rather
3640
const stashedParent = reconcilingParent;
3641
const stashedPrevious = previouslyReconciledSibling;
3642
const stashedRemaining = remainingReconcilingChildren;
3643
+ const stashedSuspenseParent = reconcilingParentSuspenseNode;
3644
+ const stashedSuspensePrevious = previouslyReconciledSiblingSuspenseNode;
3645
+ const stashedSuspenseRemaining = remainingReconcilingChildrenSuspenseNodes;
3646
+ let previousSuspendedBy = null;
3647
if (fiberInstance !== null) {
3648
+ previousSuspendedBy = fiberInstance.suspendedBy;
3649
// Update the Fiber so we that we always keep the current Fiber on the data.
3650
fiberInstance.data = nextFiber;
3651
if (
3665
remainingReconcilingChildren = fiberInstance.firstChild;
3666
fiberInstance.firstChild = null;
3667
fiberInstance.suspendedBy = null;
3668
+
3669
+ if (fiberInstance.suspenseNode !== null) {
3670
+ reconcilingParentSuspenseNode = fiberInstance.suspenseNode;
3671
+ previouslyReconciledSiblingSuspenseNode = null;
3672
+ remainingReconcilingChildrenSuspenseNodes = null;
3673
+ }
3674
}
3675
try {
3676
if (
3825
}
3826
3827
if (fiberInstance !== null) {
3828
+ removePreviousSuspendedBy(fiberInstance, previousSuspendedBy);
3829
+
3830
let componentLogsEntry = fiberToComponentLogsMap.get(
3831
fiberInstance.data,
3832
);
3864
reconcilingParent = stashedParent;
3865
previouslyReconciledSibling = stashedPrevious;
3866
remainingReconcilingChildren = stashedRemaining;
3867
+ reconcilingParentSuspenseNode = stashedSuspenseParent;
3868
+ previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious;
3869
+ remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining;
3870
}
3871
}
3872
}
4384
return null;
4385
}
4386
4387
+ function getSuspendedByOfSuspenseNode(
4388
+ suspenseNode: SuspenseNode,
4389
+ ): Array<ReactAsyncInfo> {
4390
+ // Collect all ReactAsyncInfo that was suspending this SuspenseNode but
4391
+ // isn't also in any parent set.
4392
+ const result: Array<ReactAsyncInfo> = [];
4393
+ if (!suspenseNode.hasUniqueSuspenders) {
4394
+ return result;
4395
+ }
4396
+ suspenseNode.suspendedBy.forEach((set, ioInfo) => {
4397
+ let parentNode = suspenseNode.parent;
4398
+ while (parentNode !== null) {
4399
+ if (parentNode.suspendedBy.has(ioInfo)) {
4400
+ return;
4401
+ }
4402
+ parentNode = parentNode.parent;
4403
+ }
4404
+ // We have the ioInfo but we need to find at least one corresponding await
4405
+ // to go along with it. We don't really need to show every child that awaits the same
4406
+ // thing so we just pick the first one that is still alive.
4407
+ if (set.size === 0) {
4408
+ return;
4409
+ }
4410
+ const firstInstance: DevToolsInstance = (set.values().next().value: any);
4411
+ if (firstInstance.suspendedBy !== null) {
4412
+ const asyncInfo = getAwaitInSuspendedByFromIO(
4413
+ firstInstance.suspendedBy,
4414
+ ioInfo,
4415
+ );
4416
+ if (asyncInfo !== null) {
4417
+ result.push(asyncInfo);
4418
+ }
4419
+ }
4420
+ });
4421
+ return result;
4422
+ }
4423
+
4424
function serializeAsyncInfo(
4425
asyncInfo: ReactAsyncInfo,
4426
index: number,
4765
nativeTag = getNativeTag(fiber.stateNode);
4766
}
4767
4451
- // This set is an edge case where if you pass a promise to a Client Component into a children
4452
- // position without a Server Component as the direct parent. E.g. <div>{promise}</div>
4453
- // In this case, this becomes associated with the Client/Host Component where as normally
4454
- // you'd expect these to be associated with the Server Component that awaited the data.
4455
- // TODO: Prepend other suspense sources like css, images and use().
4456
- const suspendedBy = fiberInstance.suspendedBy;
4768
+ const suspendedBy =
4769
+ fiberInstance.suspenseNode !== null
4770
+ ? // If this is a Suspense boundary, then we include everything in the subtree that might suspend
4771
+ // this boundary down to the next Suspense boundary.
4772
+ getSuspendedByOfSuspenseNode(fiberInstance.suspenseNode)
4773
+ : // This set is an edge case where if you pass a promise to a Client Component into a children
4774
+ // position without a Server Component as the direct parent. E.g. <div>{promise}</div>
4775
+ // In this case, this becomes associated with the Client/Host Component where as normally
4776
+ // you'd expect these to be associated with the Server Component that awaited the data.
4777
+ // TODO: Prepend other suspense sources like css, images and use().
4778
+ fiberInstance.suspendedBy;
4779
4780
return {
4781
id: fiberInstance.id,