mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[Flight] Allow overriding request.timeOrigin via options.startTime (#35598)
Currently, IO that finished before the request started is not considered IO: https://github.com/facebook/react/blob/da33fa5028edd00cf9697b153efe2b3f5e3f643d/packages/react-server/src/ReactFlightServer.js#L5338-L5343 This leads to loss of debug info when a flight stream is deserialized and serialized again. We can solve this by allowing "when the the request started" to be set to a point in the past, when the original stream started by doing ```js const startTime = performance.now() + performance.timeOrigin // ... stuff happens and time passes... ReactServer.renderToReadableStream(..., { startTime }) ```
This commit is contained in:
@@ -184,6 +184,7 @@ export function experimental_renderToHTML(
|
||||
handleFlightError,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'Markup',
|
||||
undefined,
|
||||
false,
|
||||
|
||||
@@ -73,6 +73,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
debugChannel?: {onMessage?: (message: string) => void},
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function render(model: ReactClientValue, options?: Options): Destination {
|
||||
@@ -84,6 +85,7 @@ function render(model: ReactClientValue, options?: Options): Destination {
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
__DEV__ && options && options.debugChannel !== undefined,
|
||||
|
||||
@@ -146,6 +146,7 @@ type Options = {
|
||||
onError?: (error: mixed) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
@@ -183,6 +184,7 @@ function renderToPipeableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannel !== undefined,
|
||||
@@ -272,6 +274,7 @@ type PrerenderOptions = {
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
@@ -303,6 +306,7 @@ function prerenderToNodeStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
ReactClientValue,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
import type {ReactFormState, Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
preloadModule,
|
||||
requireModule,
|
||||
@@ -67,6 +68,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
@@ -128,6 +130,7 @@ export function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -215,6 +218,7 @@ export function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
ReactClientValue,
|
||||
} from 'react-server/src/ReactFlightServer';
|
||||
import type {ReactFormState, Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
preloadModule,
|
||||
requireModule,
|
||||
@@ -72,6 +73,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
@@ -133,6 +135,7 @@ export function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -220,6 +223,7 @@ export function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -159,6 +159,7 @@ type Options = {
|
||||
onError?: (error: mixed) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
@@ -195,6 +196,7 @@ export function renderToPipeableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannel !== undefined,
|
||||
@@ -352,6 +354,7 @@ export function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -434,6 +437,7 @@ type PrerenderOptions = {
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
@@ -464,6 +468,7 @@ export function prerenderToNodeStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
@@ -526,6 +531,7 @@ export function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -64,6 +64,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
@@ -126,6 +127,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -214,6 +216,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -69,6 +69,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
@@ -131,6 +132,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -219,6 +221,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -152,6 +152,7 @@ type Options = {
|
||||
onError?: (error: mixed) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
@@ -189,6 +190,7 @@ function renderToPipeableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannel !== undefined,
|
||||
@@ -347,6 +349,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -429,6 +432,7 @@ type PrerenderOptions = {
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
@@ -460,6 +464,7 @@ function prerenderToNodeStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
@@ -523,6 +528,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -152,6 +152,7 @@ type Options = {
|
||||
onError?: (error: mixed) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
@@ -189,6 +190,7 @@ function renderToPipeableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -347,6 +349,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -429,6 +432,7 @@ type PrerenderOptions = {
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
@@ -460,6 +464,7 @@ function prerenderToNodeStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
@@ -523,6 +528,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
+291
-1
@@ -101,7 +101,7 @@ describe('ReactFlightDOMNode', () => {
|
||||
return (
|
||||
' in ' +
|
||||
name +
|
||||
(/\d/.test(m)
|
||||
(/:\d+:\d+/.test(m)
|
||||
? preserveLocation
|
||||
? ' ' + location.replace(__filename, relativeFilename)
|
||||
: ' (at **)'
|
||||
@@ -1589,6 +1589,296 @@ describe('ReactFlightDOMNode', () => {
|
||||
expect(ownerStack).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
function createReadableWithLateRelease(initialChunks, lateChunks, signal) {
|
||||
// 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++) {
|
||||
readable.push(initialChunks[i]);
|
||||
}
|
||||
|
||||
// When prerendering is aborted, push all dynamic chunks. They won't be
|
||||
// considered for rendering, but they include debug info we want to use.
|
||||
signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
for (let i = 0; i < lateChunks.length; i++) {
|
||||
readable.push(lateChunks[i]);
|
||||
}
|
||||
setImmediate(() => {
|
||||
readable.push(null);
|
||||
});
|
||||
},
|
||||
{once: true},
|
||||
);
|
||||
|
||||
return readable;
|
||||
}
|
||||
|
||||
async function reencodeFlightStream(
|
||||
staticChunks,
|
||||
dynamicChunks,
|
||||
startTime,
|
||||
serverConsumerManifest,
|
||||
) {
|
||||
let staticEndTime = -1;
|
||||
const chunks = {
|
||||
static: [],
|
||||
dynamic: [],
|
||||
};
|
||||
await new Promise(async resolve => {
|
||||
const renderStageController = new AbortController();
|
||||
|
||||
const serverStream = createReadableWithLateRelease(
|
||||
staticChunks,
|
||||
dynamicChunks,
|
||||
renderStageController.signal,
|
||||
);
|
||||
const decoded = await ReactServerDOMClient.createFromNodeStream(
|
||||
serverStream,
|
||||
serverConsumerManifest,
|
||||
{
|
||||
// We're re-encoding the whole stream, so we don't want to filter out any debug info.
|
||||
endTime: undefined,
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
decoded,
|
||||
webpackMap,
|
||||
{
|
||||
filterStackFrame,
|
||||
// Pass in the original render's startTime to avoid omitting its IO info.
|
||||
startTime,
|
||||
},
|
||||
);
|
||||
|
||||
const passThrough = new Stream.PassThrough(streamOptions);
|
||||
|
||||
passThrough.on('data', chunk => {
|
||||
if (!renderStageController.signal.aborted) {
|
||||
chunks.static.push(chunk);
|
||||
} else {
|
||||
chunks.dynamic.push(chunk);
|
||||
}
|
||||
});
|
||||
passThrough.on('end', resolve);
|
||||
|
||||
stream.pipe(passThrough);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
staticEndTime = performance.now() + performance.timeOrigin;
|
||||
renderStageController.abort();
|
||||
});
|
||||
});
|
||||
|
||||
return {chunks, staticEndTime};
|
||||
}
|
||||
|
||||
// @gate __DEV__
|
||||
it('can preserve old IO info when decoding and re-encoding a stream with options.startTime', async () => {
|
||||
let resolveDynamicData;
|
||||
|
||||
function getDynamicData() {
|
||||
return new Promise(resolve => {
|
||||
resolveDynamicData = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
async function Dynamic() {
|
||||
const data = await getDynamicData();
|
||||
return ReactServer.createElement('p', null, data);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
'html',
|
||||
null,
|
||||
ReactServer.createElement(
|
||||
'body',
|
||||
null,
|
||||
ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
{fallback: 'Loading...'},
|
||||
// TODO: having a wrapper <section> here seems load-bearing.
|
||||
// ReactServer.createElement(ReactServer.createElement(Dynamic)),
|
||||
ReactServer.createElement(
|
||||
'section',
|
||||
null,
|
||||
ReactServer.createElement(Dynamic),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const resolveDynamic = () => {
|
||||
resolveDynamicData('Hi Janka');
|
||||
};
|
||||
|
||||
// 1. Render <App />, dividing the output into static and dynamic content.
|
||||
|
||||
let startTime = -1;
|
||||
|
||||
let isStatic = true;
|
||||
const chunks1 = {
|
||||
static: [],
|
||||
dynamic: [],
|
||||
};
|
||||
|
||||
await new Promise(resolve => {
|
||||
setTimeout(async () => {
|
||||
startTime = performance.now() + performance.timeOrigin;
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
ReactServer.createElement(App),
|
||||
webpackMap,
|
||||
{
|
||||
filterStackFrame,
|
||||
startTime,
|
||||
environmentName() {
|
||||
return isStatic ? 'Prerender' : 'Server';
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const passThrough = new Stream.PassThrough(streamOptions);
|
||||
|
||||
passThrough.on('data', chunk => {
|
||||
if (isStatic) {
|
||||
chunks1.static.push(chunk);
|
||||
} else {
|
||||
chunks1.dynamic.push(chunk);
|
||||
}
|
||||
});
|
||||
passThrough.on('end', resolve);
|
||||
|
||||
stream.pipe(passThrough);
|
||||
});
|
||||
setTimeout(() => {
|
||||
isStatic = false;
|
||||
resolveDynamic();
|
||||
});
|
||||
});
|
||||
|
||||
//===============================================
|
||||
// 2. Decode the stream from the previous step and render it again.
|
||||
// This should preserve existing debug info.
|
||||
|
||||
const serverConsumerManifest = {
|
||||
moduleMap: null,
|
||||
moduleLoading: null,
|
||||
};
|
||||
|
||||
const {chunks: chunks2, staticEndTime: reencodeStaticEndTime} =
|
||||
await reencodeFlightStream(
|
||||
chunks1.static,
|
||||
chunks1.dynamic,
|
||||
// This is load-bearing. If we don't pass a startTime, IO info
|
||||
// from the initial render will be skipped (because it finished in the past)
|
||||
// and we won't get the precise location of the blocking await in the owner stack.
|
||||
startTime,
|
||||
serverConsumerManifest,
|
||||
);
|
||||
|
||||
//===============================================
|
||||
// 3. SSR the stream from the previous step and abort it after the static stage
|
||||
// (which should trigger `onError` for each "hole" that hasn't resolved yet)
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
let ssrStream;
|
||||
let ownerStack;
|
||||
let componentStack;
|
||||
|
||||
await new Promise(async (resolve, reject) => {
|
||||
const renderController = new AbortController();
|
||||
|
||||
const serverStream = createReadableWithLateRelease(
|
||||
chunks2.static,
|
||||
chunks2.dynamic,
|
||||
renderController.signal,
|
||||
);
|
||||
|
||||
const decodedPromise = ReactServerDOMClient.createFromNodeStream(
|
||||
serverStream,
|
||||
serverConsumerManifest,
|
||||
{
|
||||
endTime: reencodeStaticEndTime,
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
ssrStream = ReactDOMServer.renderToPipeableStream(
|
||||
React.createElement(ClientRoot, {
|
||||
response: decodedPromise,
|
||||
}),
|
||||
{
|
||||
onError(err, errorInfo) {
|
||||
componentStack = errorInfo.componentStack;
|
||||
ownerStack = React.captureOwnerStack
|
||||
? React.captureOwnerStack()
|
||||
: null;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
renderController.signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
const {reason} = renderController.signal;
|
||||
ssrStream.abort(reason);
|
||||
},
|
||||
{
|
||||
once: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
renderController.abort(new Error('ssr-abort'));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const result = await readResult(ssrStream);
|
||||
|
||||
expect(normalizeCodeLocInfo(componentStack)).toBe(
|
||||
'\n' +
|
||||
// TODO:
|
||||
// when we reencode a stream, the component stack doesn't have server frames for the dynamic content
|
||||
// (which is what causes the dynamic hole here)
|
||||
// because Flight delays forwarding debug info for lazies until they resolve.
|
||||
// (the owner stack is filled in `pushHaltedAwaitOnComponentStack`, so it works fine)
|
||||
//
|
||||
// ' in Dynamic (at **)\n'
|
||||
' in section\n' +
|
||||
' in Suspense\n' +
|
||||
' in body\n' +
|
||||
' in html\n' +
|
||||
' in App (at **)\n' +
|
||||
' in ClientRoot (at **)',
|
||||
);
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe(
|
||||
'\n' +
|
||||
gate(flags =>
|
||||
flags.enableAsyncDebugInfo
|
||||
? ' in Dynamic (at **)\n'
|
||||
: ' in section\n',
|
||||
) +
|
||||
' in App (at **)',
|
||||
);
|
||||
|
||||
expect(result).toContain(
|
||||
'Switched to client rendering because the server rendering aborted due to:\n\n' +
|
||||
'ssr-abort',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('warns with a tailored message if eval is not available in dev', async () => {
|
||||
|
||||
@@ -64,6 +64,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
@@ -126,6 +127,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -214,6 +216,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -69,6 +69,7 @@ type Options = {
|
||||
signal?: AbortSignal,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
onError?: (error: mixed) => void,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
function startReadingFromDebugChannelReadableStream(
|
||||
@@ -131,6 +132,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -219,6 +221,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
@@ -152,6 +152,7 @@ type Options = {
|
||||
onError?: (error: mixed) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type PipeableStream = {
|
||||
@@ -189,6 +190,7 @@ function renderToPipeableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -347,6 +349,7 @@ function renderToReadableStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
debugChannelReadable !== undefined,
|
||||
@@ -429,6 +432,7 @@ type PrerenderOptions = {
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
startTime?: number,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
@@ -460,6 +464,7 @@ function prerenderToNodeStream(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
@@ -523,6 +528,7 @@ function prerender(
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
options ? options.startTime : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
false,
|
||||
|
||||
+14
-1
@@ -660,6 +660,7 @@ function RequestInstance(
|
||||
onFatalError: (error: mixed) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences: void | TemporaryReferenceSet,
|
||||
debugStartTime: void | number, // Profiling-only
|
||||
environmentName: void | string | (() => string), // DEV-only
|
||||
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
|
||||
keepDebugAlive: boolean, // DEV-only
|
||||
@@ -751,7 +752,15 @@ function RequestInstance(
|
||||
// This avoids leaking unnecessary information like how long the server has
|
||||
// been running and allows for more compact representation of each timestamp.
|
||||
// The time origin is stored as an offset in the time space of this environment.
|
||||
timeOrigin = this.timeOrigin = performance.now();
|
||||
if (typeof debugStartTime === 'number') {
|
||||
// We expect `startTime` to be an absolute timestamp, so relativize it to match the other case.
|
||||
timeOrigin = this.timeOrigin =
|
||||
debugStartTime -
|
||||
// $FlowFixMe[prop-missing]
|
||||
performance.timeOrigin;
|
||||
} else {
|
||||
timeOrigin = this.timeOrigin = performance.now();
|
||||
}
|
||||
emitTimeOriginChunk(
|
||||
this,
|
||||
timeOrigin +
|
||||
@@ -784,6 +793,7 @@ export function createRequest(
|
||||
onError: void | ((error: mixed) => ?string),
|
||||
identifierPrefix: void | string,
|
||||
temporaryReferences: void | TemporaryReferenceSet,
|
||||
debugStartTime: void | number, // Profiling-only
|
||||
environmentName: void | string | (() => string), // DEV-only
|
||||
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
|
||||
keepDebugAlive: boolean, // DEV-only
|
||||
@@ -802,6 +812,7 @@ export function createRequest(
|
||||
noop,
|
||||
identifierPrefix,
|
||||
temporaryReferences,
|
||||
debugStartTime,
|
||||
environmentName,
|
||||
filterStackFrame,
|
||||
keepDebugAlive,
|
||||
@@ -816,6 +827,7 @@ export function createPrerenderRequest(
|
||||
onError: void | ((error: mixed) => ?string),
|
||||
identifierPrefix: void | string,
|
||||
temporaryReferences: void | TemporaryReferenceSet,
|
||||
debugStartTime: void | number, // Profiling-only
|
||||
environmentName: void | string | (() => string), // DEV-only
|
||||
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
|
||||
keepDebugAlive: boolean, // DEV-only
|
||||
@@ -834,6 +846,7 @@ export function createPrerenderRequest(
|
||||
onFatalError,
|
||||
identifierPrefix,
|
||||
temporaryReferences,
|
||||
debugStartTime,
|
||||
environmentName,
|
||||
filterStackFrame,
|
||||
keepDebugAlive,
|
||||
|
||||
Reference in New Issue
Block a user