@@ -24,6 +24,7 @@ import type {
24
24
FormatContext ,
25
25
} from './ReactServerFormatConfig' ;
26
26
import type { ContextSnapshot } from './ReactFizzNewContext' ;
27
+ import type { ComponentStackNode } from './ReactFizzComponentStack' ;
27
28
28
29
import {
29
30
scheduleWork ,
@@ -77,6 +78,7 @@ import {
77
78
currentResponseState ,
78
79
setCurrentResponseState ,
79
80
} from './ReactFizzHooks' ;
81
+ import { getStackByComponentStackNode } from './ReactFizzComponentStack' ;
80
82
81
83
import {
82
84
getIteratorFn ,
@@ -110,6 +112,7 @@ import invariant from 'shared/invariant';
110
112
import isArray from 'shared/isArray' ;
111
113
112
114
const ReactCurrentDispatcher = ReactSharedInternals . ReactCurrentDispatcher ;
115
+ const ReactDebugCurrentFrame = ReactSharedInternals . ReactDebugCurrentFrame ;
113
116
114
117
type LegacyContext = {
115
118
[ key : string ] : any ,
@@ -135,6 +138,7 @@ type Task = {
135
138
legacyContext : LegacyContext , // the current legacy context that this task is executing in
136
139
context : ContextSnapshot , // the current new context that this task is executing in
137
140
assignID : null | SuspenseBoundaryID , // id to assign to the content
141
+ componentStack : null | ComponentStackNode , // DEV-only component stack
138
142
} ;
139
143
140
144
const PENDING = 0 ;
@@ -299,7 +303,7 @@ function createTask(
299
303
} else {
300
304
blockedBoundary . pendingTasks ++ ;
301
305
}
302
- const task = {
306
+ const task : Task = ( {
303
307
node,
304
308
ping : ( ) => pingTask ( request , task ) ,
305
309
blockedBoundary,
@@ -308,7 +312,10 @@ function createTask(
308
312
legacyContext,
309
313
context,
310
314
assignID,
311
- } ;
315
+ } : any ) ;
316
+ if ( __DEV__ ) {
317
+ task . componentStack = currentTaskInDEV ? task . componentStack : null ;
318
+ }
312
319
abortSet . add ( task ) ;
313
320
return task ;
314
321
}
@@ -331,6 +338,55 @@ function createPendingSegment(
331
338
} ;
332
339
}
333
340
341
+ // DEV-only global reference to the currently executing task
342
+ let currentTaskInDEV : null | Task = null ;
343
+ function getCurrentStackInDEV ( ) : string {
344
+ if ( __DEV__ ) {
345
+ if ( currentTaskInDEV === null || currentTaskInDEV . componentStack === null ) {
346
+ return '' ;
347
+ }
348
+ return getStackByComponentStackNode ( currentTaskInDEV . componentStack ) ;
349
+ }
350
+ return '' ;
351
+ }
352
+
353
+ function pushBuiltInComponentStackInDEV ( task : Task , type : string ) : void {
354
+ if ( __DEV__ ) {
355
+ task . componentStack = {
356
+ tag : 0 ,
357
+ parent : task . componentStack ,
358
+ type,
359
+ } ;
360
+ }
361
+ }
362
+ function pushFunctionComponentStackInDEV ( task : Task , type : Function ) : void {
363
+ if ( __DEV__ ) {
364
+ task . componentStack = {
365
+ tag : 1 ,
366
+ parent : task . componentStack ,
367
+ type,
368
+ } ;
369
+ }
370
+ }
371
+ function pushClassComponentStackInDEV ( task : Task , type : Function ) : void {
372
+ if ( __DEV__ ) {
373
+ task . componentStack = {
374
+ tag : 2 ,
375
+ parent : task . componentStack ,
376
+ type,
377
+ } ;
378
+ }
379
+ }
380
+ function popComponentStackInDEV ( task : Task ) : void {
381
+ if ( __DEV__ ) {
382
+ invariant (
383
+ task . componentStack !== null ,
384
+ 'Unexpectedly popped too many stack frames. This is a bug in React.' ,
385
+ ) ;
386
+ task . componentStack = task . componentStack . parent ;
387
+ }
388
+ }
389
+
334
390
function reportError ( request : Request , error : mixed ) : void {
335
391
// If this callback errors, we intentionally let that error bubble up to become a fatal error
336
392
// so that someone fixes the error reporting instead of hiding it.
@@ -351,6 +407,7 @@ function renderSuspenseBoundary(
351
407
task : Task ,
352
408
props : Object ,
353
409
) : void {
410
+ pushBuiltInComponentStackInDEV ( task , 'Suspense' ) ;
354
411
const parentBoundary = task . blockedBoundary ;
355
412
const parentSegment = task . blockedSegment ;
356
413
@@ -418,6 +475,7 @@ function renderSuspenseBoundary(
418
475
} finally {
419
476
task . blockedBoundary = parentBoundary ;
420
477
task . blockedSegment = parentSegment ;
478
+ popComponentStackInDEV ( task ) ;
421
479
}
422
480
423
481
// This injects an extra segment just to contain an empty tag with an ID.
@@ -456,6 +514,7 @@ function renderHostElement(
456
514
type : string ,
457
515
props : Object ,
458
516
) : void {
517
+ pushBuiltInComponentStackInDEV ( task , type ) ;
459
518
const segment = task . blockedSegment ;
460
519
const children = pushStartInstance (
461
520
segment . chunks ,
@@ -476,6 +535,7 @@ function renderHostElement(
476
535
// the correct context. Therefore this is not in a finally.
477
536
segment . formatContext = prevContext ;
478
537
pushEndInstance ( segment . chunks , type , props ) ;
538
+ popComponentStackInDEV ( task ) ;
479
539
}
480
540
481
541
function shouldConstruct ( Component ) {
@@ -564,12 +624,14 @@ function renderClassComponent(
564
624
Component : any ,
565
625
props : any ,
566
626
) : void {
627
+ pushClassComponentStackInDEV ( task , Component ) ;
567
628
const maskedContext = ! disableLegacyContext
568
629
? getMaskedContext ( Component , task . legacyContext )
569
630
: undefined ;
570
631
const instance = constructClassInstance ( Component , props , maskedContext ) ;
571
632
mountClassInstance ( instance , Component , props , maskedContext ) ;
572
633
finishClassComponent ( request , task , instance , Component , props ) ;
634
+ popComponentStackInDEV ( task ) ;
573
635
}
574
636
575
637
const didWarnAboutBadClass = { } ;
@@ -594,6 +656,7 @@ function renderIndeterminateComponent(
594
656
if ( ! disableLegacyContext ) {
595
657
legacyContext = getMaskedContext ( Component , task . legacyContext ) ;
596
658
}
659
+ pushFunctionComponentStackInDEV ( task , Component ) ;
597
660
598
661
if ( __DEV__ ) {
599
662
if (
@@ -688,6 +751,7 @@ function renderIndeterminateComponent(
688
751
// the previous task every again, so we can use the destructive recursive form.
689
752
renderNodeDestructive ( request , task , value ) ;
690
753
}
754
+ popComponentStackInDEV ( task ) ;
691
755
}
692
756
693
757
function validateFunctionComponentInDev ( Component : any ) : void {
@@ -768,8 +832,10 @@ function renderForwardRef(
768
832
props : Object ,
769
833
ref : any ,
770
834
) : void {
835
+ pushFunctionComponentStackInDEV ( task , type . render ) ;
771
836
const children = renderWithHooks ( request , task , type . render , props , ref ) ;
772
837
renderNodeDestructive ( request , task , children ) ;
838
+ popComponentStackInDEV ( task ) ;
773
839
}
774
840
775
841
function renderMemo (
@@ -866,11 +932,13 @@ function renderLazyComponent(
866
932
props : Object ,
867
933
ref : any ,
868
934
) : void {
935
+ pushBuiltInComponentStackInDEV ( task , 'Lazy' ) ;
869
936
const payload = lazyComponent . _payload ;
870
937
const init = lazyComponent . _init ;
871
938
const Component = init ( payload ) ;
872
939
const resolvedProps = resolveDefaultProps ( Component , props ) ;
873
- return renderElement ( request , task , Component , resolvedProps , ref ) ;
940
+ renderElement ( request , task , Component , resolvedProps , ref ) ;
941
+ popComponentStackInDEV ( task ) ;
874
942
}
875
943
876
944
function renderElement (
@@ -907,11 +975,17 @@ function renderElement(
907
975
case REACT_DEBUG_TRACING_MODE_TYPE :
908
976
case REACT_STRICT_MODE_TYPE :
909
977
case REACT_PROFILER_TYPE :
910
- case REACT_SUSPENSE_LIST_TYPE : // TODO: SuspenseList should control the boundaries.
911
978
case REACT_FRAGMENT_TYPE : {
912
979
renderNodeDestructive ( request , task , props . children ) ;
913
980
return ;
914
981
}
982
+ case REACT_SUSPENSE_LIST_TYPE : {
983
+ pushBuiltInComponentStackInDEV ( task , 'SuspenseList' ) ;
984
+ // TODO: SuspenseList should control the boundaries.
985
+ renderNodeDestructive ( request , task , props . children ) ;
986
+ popComponentStackInDEV ( task ) ;
987
+ return ;
988
+ }
915
989
case REACT_SCOPE_TYPE : {
916
990
if ( enableScopeAPI ) {
917
991
renderNodeDestructive ( request , task , props . children ) ;
@@ -1174,6 +1248,10 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
1174
1248
const previousFormatContext = task . blockedSegment . formatContext ;
1175
1249
const previousLegacyContext = task . legacyContext ;
1176
1250
const previousContext = task . context ;
1251
+ let previousComponentStack = null ;
1252
+ if ( __DEV__ ) {
1253
+ previousComponentStack = task . componentStack ;
1254
+ }
1177
1255
try {
1178
1256
return renderNodeDestructive ( request , task , node ) ;
1179
1257
} catch ( x ) {
@@ -1187,6 +1265,9 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
1187
1265
task . context = previousContext ;
1188
1266
// Restore all active ReactContexts to what they were before.
1189
1267
switchContext ( previousContext ) ;
1268
+ if ( __DEV__ ) {
1269
+ task . componentStack = previousComponentStack ;
1270
+ }
1190
1271
} else {
1191
1272
// Restore the context. We assume that this will be restored by the inner
1192
1273
// functions in case nothing throws so we don't use "finally" here.
@@ -1195,6 +1276,9 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
1195
1276
task . context = previousContext ;
1196
1277
// Restore all active ReactContexts to what they were before.
1197
1278
switchContext ( previousContext ) ;
1279
+ if ( __DEV__ ) {
1280
+ task . componentStack = previousComponentStack ;
1281
+ }
1198
1282
// We assume that we don't need the correct context.
1199
1283
// Let's terminate the rest of the tree and don't render any siblings.
1200
1284
throw x ;
@@ -1360,6 +1444,11 @@ function retryTask(request: Request, task: Task): void {
1360
1444
// We don't restore it after we leave because it's likely that we'll end up
1361
1445
// needing a very similar context soon again.
1362
1446
switchContext ( task . context ) ;
1447
+ let prevTaskInDEV = null ;
1448
+ if ( __DEV__ ) {
1449
+ prevTaskInDEV = currentTaskInDEV ;
1450
+ currentTaskInDEV = task ;
1451
+ }
1363
1452
try {
1364
1453
// We call the destructive form that mutates this task. That way if something
1365
1454
// suspends again, we can reuse the same task instead of spawning a new one.
@@ -1379,6 +1468,10 @@ function retryTask(request: Request, task: Task): void {
1379
1468
segment . status = ERRORED ;
1380
1469
erroredTask ( request , task . blockedBoundary , segment , x ) ;
1381
1470
}
1471
+ } finally {
1472
+ if ( __DEV__ ) {
1473
+ currentTaskInDEV = prevTaskInDEV ;
1474
+ }
1382
1475
}
1383
1476
}
1384
1477
@@ -1389,6 +1482,11 @@ export function performWork(request: Request): void {
1389
1482
const prevContext = getActiveContext ( ) ;
1390
1483
const prevDispatcher = ReactCurrentDispatcher . current ;
1391
1484
ReactCurrentDispatcher . current = Dispatcher ;
1485
+ let prevGetCurrentStackImpl ;
1486
+ if ( __DEV__ ) {
1487
+ prevGetCurrentStackImpl = ReactDebugCurrentFrame . getCurrentStack ;
1488
+ ReactDebugCurrentFrame . getCurrentStack = getCurrentStackInDEV ;
1489
+ }
1392
1490
const prevResponseState = currentResponseState ;
1393
1491
setCurrentResponseState ( request . responseState ) ;
1394
1492
try {
@@ -1408,6 +1506,9 @@ export function performWork(request: Request): void {
1408
1506
} finally {
1409
1507
setCurrentResponseState ( prevResponseState ) ;
1410
1508
ReactCurrentDispatcher . current = prevDispatcher ;
1509
+ if ( __DEV__ ) {
1510
+ ReactDebugCurrentFrame . getCurrentStack = prevGetCurrentStackImpl ;
1511
+ }
1411
1512
if ( prevDispatcher === Dispatcher ) {
1412
1513
// This means that we were in a reentrant work loop. This could happen
1413
1514
// in a renderer that supports synchronous work like renderToString,
0 commit comments