@@ -86,12 +86,25 @@ describe('ReactFlightDOMNode', () => {
8686 ) ;
8787 }
8888
89- function normalizeCodeLocInfo ( str ) {
89+ const relativeFilename = path . relative ( __dirname , __filename ) ;
90+
91+ function normalizeCodeLocInfo ( str , { preserveLocation = false } = { } ) {
9092 return (
9193 str &&
92- str . replace ( / ^ + (?: a t | i n ) ( [ \S ] + ) [ ^ \n ] * / gm, function ( m , name ) {
93- return ' in ' + name + ( / \d / . test ( m ) ? ' (at **)' : '' ) ;
94- } )
94+ str . replace (
95+ / ^ + (?: a t | i n ) ( [ \S ] + ) ( [ ^ \n ] * ) / gm,
96+ function ( m , name , location ) {
97+ return (
98+ ' in ' +
99+ name +
100+ ( / \d / . test ( m )
101+ ? preserveLocation
102+ ? ' ' + location . replace ( __filename , relativeFilename )
103+ : ' (at **)'
104+ : '' )
105+ ) ;
106+ } ,
107+ )
95108 ) ;
96109 }
97110
@@ -1169,4 +1182,198 @@ describe('ReactFlightDOMNode', () => {
11691182 // Must not throw an error.
11701183 await readable . pipeTo ( writable ) ;
11711184 } ) ;
1185+
1186+ describe ( 'with real timers' , ( ) => {
1187+ // These tests schedule their rendering in a way that requires real timers
1188+ // to be used to accurately represent how this interacts with React's
1189+ // internal scheduling.
1190+
1191+ beforeEach ( ( ) => {
1192+ jest . useRealTimers ( ) ;
1193+ } ) ;
1194+
1195+ afterEach ( ( ) => {
1196+ jest . useFakeTimers ( ) ;
1197+ } ) ;
1198+
1199+ it ( 'should use late-arriving I/O debug info to enhance component and owner stacks when aborting a prerender' , async ( ) => {
1200+ let resolveDynamicData ;
1201+
1202+ async function getCachedData ( ) {
1203+ // Cached data resolves in microtasks.
1204+ return Promise . resolve ( 'Hi' ) ;
1205+ }
1206+
1207+ async function getDynamicData ( ) {
1208+ return new Promise ( resolve => {
1209+ resolveDynamicData = resolve ;
1210+ } ) ;
1211+ }
1212+
1213+ async function Dynamic ( ) {
1214+ const cachedData = await getCachedData ( ) ;
1215+ const dynamicData = await getDynamicData ( ) ;
1216+
1217+ return (
1218+ < p >
1219+ { cachedData } { dynamicData }
1220+ </ p >
1221+ ) ;
1222+ }
1223+
1224+ function App ( ) {
1225+ return ReactServer . createElement (
1226+ 'html' ,
1227+ null ,
1228+ ReactServer . createElement (
1229+ 'body' ,
1230+ null ,
1231+ ReactServer . createElement ( Dynamic ) ,
1232+ ) ,
1233+ ) ;
1234+ }
1235+
1236+ const stream = await ReactServerDOMServer . renderToPipeableStream (
1237+ ReactServer . createElement ( App ) ,
1238+ webpackMap ,
1239+ { filterStackFrame} ,
1240+ ) ;
1241+
1242+ const staticChunks = [ ] ;
1243+ const dynamicChunks = [ ] ;
1244+ let isStatic = true ;
1245+
1246+ const passThrough = new Stream . PassThrough ( streamOptions ) ;
1247+ stream . pipe ( passThrough ) ;
1248+
1249+ // Split chunks into static and dynamic chunks.
1250+ passThrough . on ( 'data' , chunk => {
1251+ if ( isStatic ) {
1252+ staticChunks . push ( chunk ) ;
1253+ } else {
1254+ dynamicChunks . push ( chunk ) ;
1255+ }
1256+ } ) ;
1257+
1258+ await new Promise ( resolve => {
1259+ setTimeout ( ( ) => {
1260+ isStatic = false ;
1261+ resolveDynamicData ( 'Josh' ) ;
1262+ resolve ( ) ;
1263+ } ) ;
1264+ } ) ;
1265+
1266+ await new Promise ( resolve => {
1267+ passThrough . on ( 'end' , resolve ) ;
1268+ } ) ;
1269+
1270+ // Create a new Readable and push all static chunks immediately.
1271+ const readable = new Stream . Readable ( { ...streamOptions , read ( ) { } } ) ;
1272+ for ( let i = 0 ; i < staticChunks . length ; i ++ ) {
1273+ readable . push ( staticChunks [ i ] ) ;
1274+ }
1275+
1276+ const abortController = new AbortController ( ) ;
1277+
1278+ // When prerendering is aborted, push all dynamic chunks.
1279+ abortController . signal . addEventListener (
1280+ 'abort' ,
1281+ ( ) => {
1282+ for ( let i = 0 ; i < dynamicChunks . length ; i ++ ) {
1283+ readable . push ( dynamicChunks [ i ] ) ;
1284+ }
1285+ } ,
1286+ { once : true } ,
1287+ ) ;
1288+
1289+ const response = ReactServerDOMClient . createFromNodeStream ( readable , {
1290+ serverConsumerManifest : {
1291+ moduleMap : null ,
1292+ moduleLoading : null ,
1293+ } ,
1294+ } ) ;
1295+
1296+ function ClientRoot ( ) {
1297+ return use ( response ) ;
1298+ }
1299+
1300+ let componentStack ;
1301+ let ownerStack ;
1302+
1303+ const { prelude} = await new Promise ( resolve => {
1304+ let result ;
1305+
1306+ setTimeout ( ( ) => {
1307+ result = ReactDOMFizzStatic . prerenderToNodeStream (
1308+ React . createElement ( ClientRoot ) ,
1309+ {
1310+ signal : abortController . signal ,
1311+ onError ( error , errorInfo ) {
1312+ componentStack = errorInfo . componentStack ;
1313+ ownerStack = React . captureOwnerStack
1314+ ? React . captureOwnerStack ( )
1315+ : null ;
1316+ } ,
1317+ } ,
1318+ ) ;
1319+ } ) ;
1320+
1321+ setTimeout ( ( ) => {
1322+ abortController . abort ( ) ;
1323+ resolve ( result ) ;
1324+ } ) ;
1325+ } ) ;
1326+
1327+ const prerenderHTML = await readResult ( prelude ) ;
1328+
1329+ expect ( prerenderHTML ) . toContain ( '' ) ;
1330+
1331+ if ( __DEV__ ) {
1332+ expect (
1333+ normalizeCodeLocInfo ( componentStack , { preserveLocation : true } ) ,
1334+ ) . toBe (
1335+ '\n' +
1336+ ' in Dynamic' +
1337+ ( gate ( flags => flags . enableAsyncDebugInfo )
1338+ ? ' (file://ReactFlightDOMNode-test.js:1215:33)\n'
1339+ : '\n' ) +
1340+ ' in body\n' +
1341+ ' in html\n' +
1342+ ' in App (file://ReactFlightDOMNode-test.js:1231:25)\n' +
1343+ ' in ClientRoot (ReactFlightDOMNode-test.js:1297:16)' ,
1344+ ) ;
1345+ } else {
1346+ expect (
1347+ normalizeCodeLocInfo ( componentStack , { preserveLocation : true } ) ,
1348+ ) . toBe (
1349+ '\n' +
1350+ ' in body\n' +
1351+ ' in html\n' +
1352+ ' in ClientRoot (ReactFlightDOMNode-test.js:1297:16)' ,
1353+ ) ;
1354+ }
1355+
1356+ if ( __DEV__ ) {
1357+ if ( gate ( flags => flags . enableAsyncDebugInfo ) ) {
1358+ expect (
1359+ normalizeCodeLocInfo ( ownerStack , { preserveLocation : true } ) ,
1360+ ) . toBe (
1361+ '\n' +
1362+ ' in Dynamic (file://ReactFlightDOMNode-test.js:1215:33)\n' +
1363+ ' in App (file://ReactFlightDOMNode-test.js:1231:25)' ,
1364+ ) ;
1365+ } else {
1366+ expect (
1367+ normalizeCodeLocInfo ( ownerStack , { preserveLocation : true } ) ,
1368+ ) . toBe (
1369+ '' +
1370+ '\n' +
1371+ ' in App (file://ReactFlightDOMNode-test.js:1231:25)' ,
1372+ ) ;
1373+ }
1374+ } else {
1375+ expect ( ownerStack ) . toBeNull ( ) ;
1376+ }
1377+ } ) ;
1378+ } ) ;
11721379} ) ;
0 commit comments