Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,9 @@ function filterDebugInfo(
return;
}

// Remove any debug info entries that arrived after the defined end time.
// Remove any debug info entries after the defined end time. For async info
// that means we're including anything that was awaited before the end time,
// but it doesn't need to be resolved before the end time.
const relativeEndTime =
response._debugEndTime -
// $FlowFixMe[prop-missing]
Expand All @@ -521,9 +523,6 @@ function filterDebugInfo(
if (typeof info.time === 'number' && info.time > relativeEndTime) {
break;
}
if (info.awaited != null && info.awaited.end > relativeEndTime) {
break;
}
debugInfo.push(info);
}
value._debugInfo = debugInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1197,35 +1197,28 @@ describe('ReactFlightDOMNode', () => {
});

it('should use late-arriving I/O debug info to enhance component and owner stacks when aborting a prerender', async () => {
// This test is constructing a scenario where a framework might separate
// I/O into different phases, e.g. runtime I/O and dynamic I/O. The
// framework might choose to define an end time for the Flight client,
// indicating that all I/O info (or any debug info for that matter) that
// arrives after that time should be ignored. When rendering in Fizz is
// then aborted, the late-arriving debug info that's used to enhance the
// owner stack only includes I/O info up to that end time.
let resolveRuntimeData;
let resolveDynamicData;

async function getRuntimeData() {
let resolveDynamicData1;
let resolveDynamicData2;

async function getDynamicData1() {
return new Promise(resolve => {
resolveRuntimeData = resolve;
resolveDynamicData1 = resolve;
});
}

async function getDynamicData() {
async function getDynamicData2() {
return new Promise(resolve => {
resolveDynamicData = resolve;
resolveDynamicData2 = resolve;
});
}

async function Dynamic() {
const runtimeData = await getRuntimeData();
const dynamicData = await getDynamicData();
const data1 = await getDynamicData1();
const data2 = await getDynamicData2();

return (
<p>
{runtimeData} {dynamicData}
{data1} {data2}
</p>
);
}
Expand All @@ -1242,45 +1235,40 @@ describe('ReactFlightDOMNode', () => {
);
}

const stream = await ReactServerDOMServer.renderToPipeableStream(
ReactServer.createElement(App),
webpackMap,
{filterStackFrame},
);

let staticEndTime = -1;
const initialChunks = [];
const dynamicChunks = [];
let isDynamic = false;

const passThrough = new Stream.PassThrough(streamOptions);
stream.pipe(passThrough);
await new Promise(resolve => {
setTimeout(async () => {
const stream = ReactServerDOMServer.renderToPipeableStream(
ReactServer.createElement(App),
webpackMap,
{filterStackFrame},
);

passThrough.on('data', chunk => {
if (isDynamic) {
dynamicChunks.push(chunk);
} else {
initialChunks.push(chunk);
}
});
const passThrough = new Stream.PassThrough(streamOptions);
stream.pipe(passThrough);

let endTime;
passThrough.on('data', chunk => {
if (staticEndTime < 0) {
initialChunks.push(chunk);
} else {
dynamicChunks.push(chunk);
}
});

await new Promise(resolve => {
setTimeout(() => {
resolveRuntimeData('Hi');
passThrough.on('end', resolve);
});
setTimeout(() => {
isDynamic = true;
endTime = performance.now() + performance.timeOrigin;
resolveDynamicData('Josh');
resolve();
staticEndTime = performance.now() + performance.timeOrigin;
resolveDynamicData1('Hi');
setTimeout(() => {
resolveDynamicData2('Josh');
});
});
});

await new Promise(resolve => {
passThrough.on('end', resolve);
});

// Create a new Readable and push all initial chunks immediately.
const readable = new Stream.Readable({...streamOptions, read() {}});
for (let i = 0; i < initialChunks.length; i++) {
Expand Down Expand Up @@ -1311,8 +1299,8 @@ describe('ReactFlightDOMNode', () => {
},
{
// Debug info arriving after this end time will be ignored, e.g. the
// I/O info for the dynamic data.
endTime,
// I/O info for the second dynamic data.
endTime: staticEndTime,
},
);

Expand Down Expand Up @@ -1358,12 +1346,12 @@ describe('ReactFlightDOMNode', () => {
'\n' +
' in Dynamic' +
(gate(flags => flags.enableAsyncDebugInfo)
? ' (file://ReactFlightDOMNode-test.js:1223:33)\n'
? ' (file://ReactFlightDOMNode-test.js:1216:27)\n'
: '\n') +
' in body\n' +
' in html\n' +
' in App (file://ReactFlightDOMNode-test.js:1240:25)\n' +
' in ClientRoot (ReactFlightDOMNode-test.js:1320:16)',
' in App (file://ReactFlightDOMNode-test.js:1233:25)\n' +
' in ClientRoot (ReactFlightDOMNode-test.js:1308:16)',
);
} else {
expect(
Expand All @@ -1372,7 +1360,7 @@ describe('ReactFlightDOMNode', () => {
'\n' +
' in body\n' +
' in html\n' +
' in ClientRoot (ReactFlightDOMNode-test.js:1320:16)',
' in ClientRoot (ReactFlightDOMNode-test.js:1308:16)',
);
}

Expand All @@ -1382,16 +1370,16 @@ describe('ReactFlightDOMNode', () => {
normalizeCodeLocInfo(ownerStack, {preserveLocation: true}),
).toBe(
'\n' +
' in Dynamic (file://ReactFlightDOMNode-test.js:1223:33)\n' +
' in App (file://ReactFlightDOMNode-test.js:1240:25)',
' in Dynamic (file://ReactFlightDOMNode-test.js:1216:27)\n' +
' in App (file://ReactFlightDOMNode-test.js:1233:25)',
);
} else {
expect(
normalizeCodeLocInfo(ownerStack, {preserveLocation: true}),
).toBe(
'' +
'\n' +
' in App (file://ReactFlightDOMNode-test.js:1240:25)',
' in App (file://ReactFlightDOMNode-test.js:1233:25)',
);
}
} else {
Expand Down
Loading