@@ -757,7 +757,7 @@ export function listenForDynamicRequest(
757757 responsePromise : Promise < FetchServerResponseResult >
758758) {
759759 responsePromise . then (
760- ( { flightData } : FetchServerResponseResult ) => {
760+ ( { flightData, debugInfo } : FetchServerResponseResult ) => {
761761 if ( typeof flightData === 'string' ) {
762762 // Happens when navigating to page in `pages` from `app`. We shouldn't
763763 // get here because should have already handled this during
@@ -784,18 +784,19 @@ export function listenForDynamicRequest(
784784 segmentPath ,
785785 serverRouterState ,
786786 dynamicData ,
787- dynamicHead
787+ dynamicHead ,
788+ debugInfo
788789 )
789790 }
790791
791792 // Now that we've exhausted all the data we received from the server, if
792793 // there are any remaining pending tasks in the tree, abort them now.
793794 // If there's any missing data, it will trigger a lazy fetch.
794- abortTask ( task , null )
795+ abortTask ( task , null , debugInfo )
795796 } ,
796797 ( error : any ) => {
797798 // This will trigger an error during render
798- abortTask ( task , error )
799+ abortTask ( task , error , null )
799800 }
800801 )
801802}
@@ -805,7 +806,8 @@ function writeDynamicDataIntoPendingTask(
805806 segmentPath : FlightSegmentPath ,
806807 serverRouterState : FlightRouterState ,
807808 dynamicData : CacheNodeSeedData ,
808- dynamicHead : HeadData
809+ dynamicHead : HeadData ,
810+ debugInfo : Array < any > | null
809811) {
810812 // The data sent by the server represents only a subtree of the app. We need
811813 // to find the part of the task tree that matches the server response, and
@@ -844,15 +846,17 @@ function writeDynamicDataIntoPendingTask(
844846 task ,
845847 serverRouterState ,
846848 dynamicData ,
847- dynamicHead
849+ dynamicHead ,
850+ debugInfo
848851 )
849852}
850853
851854function finishTaskUsingDynamicDataPayload (
852855 task : SPANavigationTask ,
853856 serverRouterState : FlightRouterState ,
854857 dynamicData : CacheNodeSeedData ,
855- dynamicHead : HeadData
858+ dynamicHead : HeadData ,
859+ debugInfo : Array < any > | null
856860) {
857861 if ( task . dynamicRequestTree === null ) {
858862 // Everything in this subtree is already complete. Bail out.
@@ -873,7 +877,8 @@ function finishTaskUsingDynamicDataPayload(
873877 task . route ,
874878 serverRouterState ,
875879 dynamicData ,
876- dynamicHead
880+ dynamicHead ,
881+ debugInfo
877882 )
878883 // Set this to null to indicate that this task is now complete.
879884 task . dynamicRequestTree = null
@@ -904,7 +909,8 @@ function finishTaskUsingDynamicDataPayload(
904909 taskChild ,
905910 serverRouterStateChild ,
906911 dynamicDataChild ,
907- dynamicHead
912+ dynamicHead ,
913+ debugInfo
908914 )
909915 }
910916 }
@@ -1004,7 +1010,8 @@ function finishPendingCacheNode(
10041010 taskState : FlightRouterState ,
10051011 serverState : FlightRouterState ,
10061012 dynamicData : CacheNodeSeedData ,
1007- dynamicHead : HeadData
1013+ dynamicHead : HeadData ,
1014+ debugInfo : Array < any > | null
10081015) : void {
10091016 // Writes a dynamic response into an existing Cache Node tree. This does _not_
10101017 // create a new tree, it updates the existing tree in-place. So it must follow
@@ -1053,19 +1060,20 @@ function finishPendingCacheNode(
10531060 taskStateChild ,
10541061 serverStateChild ,
10551062 dataChild ,
1056- dynamicHead
1063+ dynamicHead ,
1064+ debugInfo
10571065 )
10581066 } else {
10591067 // The server never returned data for this segment. Trigger a lazy
10601068 // fetch during render. This shouldn't happen because the Route Tree
10611069 // and the Seed Data tree sent by the server should always be the same
10621070 // shape when part of the same server response.
1063- abortPendingCacheNode ( taskStateChild , cacheNodeChild , null )
1071+ abortPendingCacheNode ( taskStateChild , cacheNodeChild , null , debugInfo )
10641072 }
10651073 } else {
10661074 // The server never returned data for this segment. Trigger a lazy
10671075 // fetch during render.
1068- abortPendingCacheNode ( taskStateChild , cacheNodeChild , null )
1076+ abortPendingCacheNode ( taskStateChild , cacheNodeChild , null , debugInfo )
10691077 }
10701078 } else {
10711079 // The server response matches what was expected to receive, but there's
@@ -1087,7 +1095,7 @@ function finishPendingCacheNode(
10871095 // This is a deferred RSC promise. We can fulfill it with the data we just
10881096 // received from the server. If it was already resolved by a different
10891097 // navigation, then this does nothing because we can't overwrite data.
1090- rsc . resolve ( dynamicSegmentData )
1098+ rsc . resolve ( dynamicSegmentData , debugInfo )
10911099 } else {
10921100 // This is not a deferred RSC promise, nor is it empty, so it must have
10931101 // been populated by a different navigation. We must not overwrite it.
@@ -1098,19 +1106,23 @@ function finishPendingCacheNode(
10981106 const loading = cacheNode . loading
10991107 if ( isDeferredRsc ( loading ) ) {
11001108 const dynamicLoading = dynamicData [ 3 ]
1101- loading . resolve ( dynamicLoading )
1109+ loading . resolve ( dynamicLoading , debugInfo )
11021110 }
11031111
11041112 // Check if this is a leaf segment. If so, it will have a `head` property with
11051113 // a pending promise that needs to be resolved with the dynamic head from
11061114 // the server.
11071115 const head = cacheNode . head
11081116 if ( isDeferredRsc ( head ) ) {
1109- head . resolve ( dynamicHead )
1117+ head . resolve ( dynamicHead , debugInfo )
11101118 }
11111119}
11121120
1113- export function abortTask ( task : SPANavigationTask , error : any ) : void {
1121+ export function abortTask (
1122+ task : SPANavigationTask ,
1123+ error : any ,
1124+ debugInfo : Array < any > | null
1125+ ) : void {
11141126 const cacheNode = task . node
11151127 if ( cacheNode === null ) {
11161128 // This indicates the task is already complete.
@@ -1121,13 +1133,13 @@ export function abortTask(task: SPANavigationTask, error: any): void {
11211133 if ( taskChildren === null ) {
11221134 // Reached the leaf task node. This is the root of a pending cache
11231135 // node tree.
1124- abortPendingCacheNode ( task . route , cacheNode , error )
1136+ abortPendingCacheNode ( task . route , cacheNode , error , debugInfo )
11251137 } else {
11261138 // This is an intermediate task node. Keep traversing until we reach a
11271139 // task node with no children. That will be the root of the cache node tree
11281140 // that needs to be resolved.
11291141 for ( const taskChild of taskChildren . values ( ) ) {
1130- abortTask ( taskChild , error )
1142+ abortTask ( taskChild , error , debugInfo )
11311143 }
11321144 }
11331145
@@ -1138,7 +1150,8 @@ export function abortTask(task: SPANavigationTask, error: any): void {
11381150function abortPendingCacheNode (
11391151 routerState : FlightRouterState ,
11401152 cacheNode : CacheNode ,
1141- error : any
1153+ error : any ,
1154+ debugInfo : Array < any > | null
11421155) : void {
11431156 // For every pending segment in the tree, resolve its `rsc` promise to `null`
11441157 // to trigger a lazy fetch during render.
@@ -1159,7 +1172,7 @@ function abortPendingCacheNode(
11591172 const segmentKeyChild = createRouterCacheKey ( segmentChild )
11601173 const cacheNodeChild = segmentMapChild . get ( segmentKeyChild )
11611174 if ( cacheNodeChild !== undefined ) {
1162- abortPendingCacheNode ( routerStateChild , cacheNodeChild , error )
1175+ abortPendingCacheNode ( routerStateChild , cacheNodeChild , error , debugInfo )
11631176 } else {
11641177 // This shouldn't happen because we're traversing the same tree that was
11651178 // used to construct the cache nodes in the first place.
@@ -1170,16 +1183,16 @@ function abortPendingCacheNode(
11701183 if ( isDeferredRsc ( rsc ) ) {
11711184 if ( error === null ) {
11721185 // This will trigger a lazy fetch during render.
1173- rsc . resolve ( null )
1186+ rsc . resolve ( null , debugInfo )
11741187 } else {
11751188 // This will trigger an error during rendering.
1176- rsc . reject ( error )
1189+ rsc . reject ( error , debugInfo )
11771190 }
11781191 }
11791192
11801193 const loading = cacheNode . loading
11811194 if ( isDeferredRsc ( loading ) ) {
1182- loading . resolve ( null )
1195+ loading . resolve ( null , debugInfo )
11831196 }
11841197
11851198 // Check if this is a leaf segment. If so, it will have a `head` property with
@@ -1188,7 +1201,7 @@ function abortPendingCacheNode(
11881201 // the app. We want the segment to error, not the entire app.
11891202 const head = cacheNode . head
11901203 if ( isDeferredRsc ( head ) ) {
1191- head . resolve ( null )
1204+ head . resolve ( null , debugInfo )
11921205 }
11931206}
11941207
@@ -1260,25 +1273,28 @@ const DEFERRED = Symbol()
12601273
12611274type PendingDeferredRsc < T > = Promise < T > & {
12621275 status : 'pending'
1263- resolve : ( value : T ) => void
1264- reject : ( error : any ) => void
1276+ resolve : ( value : T , debugInfo : Array < any > | null ) => void
1277+ reject : ( error : any , debugInfo : Array < any > | null ) => void
12651278 tag : Symbol
1279+ _debugInfo : Array < any >
12661280}
12671281
12681282type FulfilledDeferredRsc < T > = Promise < T > & {
12691283 status : 'fulfilled'
12701284 value : T
1271- resolve : ( value : T ) => void
1272- reject : ( error : any ) => void
1285+ resolve : ( value : T , debugInfo : Array < any > | null ) => void
1286+ reject : ( error : any , debugInfo : Array < any > | null ) => void
12731287 tag : Symbol
1288+ _debugInfo : Array < any >
12741289}
12751290
12761291type RejectedDeferredRsc < T > = Promise < T > & {
12771292 status : 'rejected'
12781293 reason : any
1279- resolve : ( value : T ) => void
1280- reject : ( error : any ) => void
1294+ resolve : ( value : T , debugInfo : Array < any > | null ) => void
1295+ reject : ( error : any , debugInfo : Array < any > | null ) => void
12811296 tag : Symbol
1297+ _debugInfo : Array < any >
12821298}
12831299
12841300type DeferredRsc < T extends React . ReactNode = React . ReactNode > =
@@ -1297,29 +1313,54 @@ function isDeferredRsc(value: any): value is DeferredRsc {
12971313function createDeferredRsc <
12981314 T extends React . ReactNode = React . ReactNode ,
12991315> ( ) : PendingDeferredRsc < T > {
1316+ // Create an unresolved promise that represents data derived from a Flight
1317+ // response. The promise will be resolved later as soon as we start receiving
1318+ // data from the server, i.e. as soon as the Flight client decodes and returns
1319+ // the top-level response object.
1320+
1321+ // The `_debugInfo` field contains profiling information. Promises that are
1322+ // created by Flight already have this info added by React; for any derived
1323+ // promise created by the router, we need to transfer the Flight debug info
1324+ // onto the derived promise.
1325+ //
1326+ // The debug info represents the latency between the start of the navigation
1327+ // and the start of rendering. (It does not represent the time it takes for
1328+ // whole stream to finish.)
1329+ const debugInfo : Array < any > = [ ]
1330+
13001331 let resolve : any
13011332 let reject : any
13021333 const pendingRsc = new Promise < T > ( ( res , rej ) => {
13031334 resolve = res
13041335 reject = rej
13051336 } ) as PendingDeferredRsc < T >
13061337 pendingRsc . status = 'pending'
1307- pendingRsc . resolve = ( value : T ) => {
1338+ pendingRsc . resolve = ( value : T , responseDebugInfo : Array < any > | null ) => {
13081339 if ( pendingRsc . status === 'pending' ) {
13091340 const fulfilledRsc : FulfilledDeferredRsc < T > = pendingRsc as any
13101341 fulfilledRsc . status = 'fulfilled'
13111342 fulfilledRsc . value = value
1343+ if ( responseDebugInfo !== null ) {
1344+ // Transfer the debug info to the derived promise.
1345+ debugInfo . push . apply ( debugInfo , responseDebugInfo )
1346+ }
13121347 resolve ( value )
13131348 }
13141349 }
1315- pendingRsc . reject = ( error : any ) => {
1350+ pendingRsc . reject = ( error : any , responseDebugInfo : Array < any > | null ) => {
13161351 if ( pendingRsc . status === 'pending' ) {
13171352 const rejectedRsc : RejectedDeferredRsc < T > = pendingRsc as any
13181353 rejectedRsc . status = 'rejected'
13191354 rejectedRsc . reason = error
1355+ if ( responseDebugInfo !== null ) {
1356+ // Transfer the debug info to the derived promise.
1357+ debugInfo . push . apply ( debugInfo , responseDebugInfo )
1358+ }
13201359 reject ( error )
13211360 }
13221361 }
13231362 pendingRsc . tag = DEFERRED
1363+ pendingRsc . _debugInfo = debugInfo
1364+
13241365 return pendingRsc
13251366}
0 commit comments