mirror of
https://github.com/facebook/react.git
synced 2026-02-23 20:23:02 +00:00
[Flight] Don't drop debug info if there's only a readable debug channel (#34304)
When the Flight Client is waiting for pending debug chunks, it drops the debug info if there is no writable side of the debug channel defined. However, it should instead check if there's no readable side defined. Fixing this is not only important for browser clients that don't want or need a return channel, but it's also crucial for server-side rendering, because the Node and Edge clients only accept a readable side of the debug channel. So they can't even define a noop writable side as a workaround.
This commit is contained in:
48
packages/react-client/src/ReactFlightClient.js
vendored
48
packages/react-client/src/ReactFlightClient.js
vendored
@@ -341,6 +341,11 @@ export type FindSourceMapURLCallback = (
|
||||
|
||||
export type DebugChannelCallback = (message: string) => void;
|
||||
|
||||
export type DebugChannel = {
|
||||
hasReadable: boolean,
|
||||
callback: DebugChannelCallback | null,
|
||||
};
|
||||
|
||||
type Response = {
|
||||
_bundlerConfig: ServerConsumerModuleMap,
|
||||
_serverReferenceConfig: null | ServerManifest,
|
||||
@@ -362,7 +367,7 @@ type Response = {
|
||||
_debugRootStack?: null | Error, // DEV-only
|
||||
_debugRootTask?: null | ConsoleTask, // DEV-only
|
||||
_debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only
|
||||
_debugChannel?: void | DebugChannelCallback, // DEV-only
|
||||
_debugChannel?: void | DebugChannel, // DEV-only
|
||||
_blockedConsole?: null | SomeChunk<ConsoleEntry>, // DEV-only
|
||||
_replayConsole: boolean, // DEV-only
|
||||
_rootEnvironmentName: string, // DEV-only, the requested environment name.
|
||||
@@ -404,16 +409,16 @@ function getWeakResponse(response: Response): WeakResponse {
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupDebugChannel(debugChannel: DebugChannelCallback): void {
|
||||
// When a Response gets GC:ed because nobody is referring to any of the objects that lazily
|
||||
// loads from the Response anymore, then we can close the debug channel.
|
||||
debugChannel('');
|
||||
function closeDebugChannel(debugChannel: DebugChannel): void {
|
||||
if (debugChannel.callback) {
|
||||
debugChannel.callback('');
|
||||
}
|
||||
}
|
||||
|
||||
// If FinalizationRegistry doesn't exist, we cannot use the debugChannel.
|
||||
const debugChannelRegistry =
|
||||
__DEV__ && typeof FinalizationRegistry === 'function'
|
||||
? new FinalizationRegistry(cleanupDebugChannel)
|
||||
? new FinalizationRegistry(closeDebugChannel)
|
||||
: null;
|
||||
|
||||
function readChunk<T>(chunk: SomeChunk<T>): T {
|
||||
@@ -1007,7 +1012,7 @@ export function reportGlobalError(
|
||||
if (debugChannel !== undefined) {
|
||||
// If we don't have any more ways of reading data, we don't have to send any
|
||||
// more neither. So we close the writable side.
|
||||
debugChannel('');
|
||||
closeDebugChannel(debugChannel);
|
||||
response._debugChannel = undefined;
|
||||
}
|
||||
}
|
||||
@@ -1494,8 +1499,8 @@ function waitForReference<T>(
|
||||
): T {
|
||||
if (
|
||||
__DEV__ &&
|
||||
// TODO: This should check for the existence of the "readable" side, not the "writable".
|
||||
response._debugChannel === undefined
|
||||
(response._debugChannel === undefined ||
|
||||
!response._debugChannel.hasReadable)
|
||||
) {
|
||||
if (
|
||||
referencedChunk.status === PENDING &&
|
||||
@@ -2262,15 +2267,16 @@ function parseModelString(
|
||||
case 'Y': {
|
||||
if (__DEV__) {
|
||||
if (value.length > 2) {
|
||||
const debugChannel = response._debugChannel;
|
||||
if (debugChannel) {
|
||||
const debugChannelCallback =
|
||||
response._debugChannel && response._debugChannel.callback;
|
||||
if (debugChannelCallback) {
|
||||
if (value[2] === '@') {
|
||||
// This is a deferred Promise.
|
||||
const ref = value.slice(3); // We assume this doesn't have a path just id.
|
||||
const id = parseInt(ref, 16);
|
||||
if (!response._chunks.has(id)) {
|
||||
// We haven't seen this id before. Query the server to start sending it.
|
||||
debugChannel('P:' + ref);
|
||||
debugChannelCallback('P:' + ref);
|
||||
}
|
||||
// Start waiting. This now creates a pending chunk if it doesn't already exist.
|
||||
// This is the actual Promise we're waiting for.
|
||||
@@ -2280,7 +2286,7 @@ function parseModelString(
|
||||
const id = parseInt(ref, 16);
|
||||
if (!response._chunks.has(id)) {
|
||||
// We haven't seen this id before. Query the server to start sending it.
|
||||
debugChannel('Q:' + ref);
|
||||
debugChannelCallback('Q:' + ref);
|
||||
}
|
||||
// Start waiting. This now creates a pending chunk if it doesn't already exist.
|
||||
const chunk = getChunk(response, id);
|
||||
@@ -2358,7 +2364,7 @@ function ResponseInstance(
|
||||
findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
|
||||
replayConsole: boolean, // DEV-only
|
||||
environmentName: void | string, // DEV-only
|
||||
debugChannel: void | DebugChannelCallback, // DEV-only
|
||||
debugChannel: void | DebugChannel, // DEV-only
|
||||
) {
|
||||
const chunks: Map<number, SomeChunk<any>> = new Map();
|
||||
this._bundlerConfig = bundlerConfig;
|
||||
@@ -2420,10 +2426,14 @@ function ResponseInstance(
|
||||
this._rootEnvironmentName = rootEnv;
|
||||
if (debugChannel) {
|
||||
if (debugChannelRegistry === null) {
|
||||
// We can't safely clean things up later, so we immediately close the debug channel.
|
||||
debugChannel('');
|
||||
// We can't safely clean things up later, so we immediately close the
|
||||
// debug channel.
|
||||
closeDebugChannel(debugChannel);
|
||||
this._debugChannel = undefined;
|
||||
} else {
|
||||
// When a Response gets GC:ed because nobody is referring to any of the
|
||||
// objects that lazily load from the Response anymore, then we can close
|
||||
// the debug channel.
|
||||
debugChannelRegistry.register(this, debugChannel);
|
||||
}
|
||||
}
|
||||
@@ -2451,7 +2461,7 @@ export function createResponse(
|
||||
findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
|
||||
replayConsole: boolean, // DEV-only
|
||||
environmentName: void | string, // DEV-only
|
||||
debugChannel: void | DebugChannelCallback, // DEV-only
|
||||
debugChannel: void | DebugChannel, // DEV-only
|
||||
): WeakResponse {
|
||||
return getWeakResponse(
|
||||
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
|
||||
@@ -3545,8 +3555,8 @@ function resolveDebugModel(
|
||||
if (
|
||||
__DEV__ &&
|
||||
((debugChunk: any): SomeChunk<any>).status === BLOCKED &&
|
||||
// TODO: This should check for the existence of the "readable" side, not the "writable".
|
||||
response._debugChannel === undefined
|
||||
(response._debugChannel === undefined ||
|
||||
!response._debugChannel.hasReadable)
|
||||
) {
|
||||
if (json[0] === '"' && json[1] === '$') {
|
||||
const path = json.slice(2, json.length - 1).split(':');
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
import type {Thenable} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response as FlightResponse,
|
||||
FindSourceMapURLCallback,
|
||||
DebugChannel,
|
||||
DebugChannelCallback,
|
||||
FindSourceMapURLCallback,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
@@ -72,6 +73,19 @@ function createDebugCallbackFromWritableStream(
|
||||
}
|
||||
|
||||
function createResponseFromOptions(options: void | Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback:
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(
|
||||
options.debugChannel.writable,
|
||||
)
|
||||
: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
options && options.moduleBaseURL ? options.moduleBaseURL : '',
|
||||
null,
|
||||
@@ -89,12 +103,7 @@ function createResponseFromOptions(options: void | Options) {
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel !== undefined &&
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(options.debugChannel.writable)
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response,
|
||||
DebugChannel,
|
||||
FindSourceMapURLCallback,
|
||||
Response,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {Readable} from 'stream';
|
||||
@@ -88,6 +89,14 @@ function createFromNodeStream<T>(
|
||||
moduleBaseURL: string,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const response: Response = createResponse(
|
||||
moduleRootPath,
|
||||
null,
|
||||
@@ -103,6 +112,7 @@ function createFromNodeStream<T>(
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
|
||||
if (__DEV__ && options && options.debugChannel) {
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
import type {Thenable} from 'shared/ReactTypes.js';
|
||||
import type {
|
||||
Response as FlightResponse,
|
||||
DebugChannel,
|
||||
DebugChannelCallback,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
import type {ServerReferenceId} from '../client/ReactFlightClientConfigBundlerParcel';
|
||||
@@ -99,6 +100,39 @@ function createDebugCallbackFromWritableStream(
|
||||
};
|
||||
}
|
||||
|
||||
function createResponseFromOptions(options: void | Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback:
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(
|
||||
options.debugChannel.writable,
|
||||
)
|
||||
: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
null, // bundlerConfig
|
||||
null, // serverReferenceConfig
|
||||
null, // moduleLoading
|
||||
callCurrentServerCallback,
|
||||
undefined, // encodeFormAction
|
||||
undefined, // nonce
|
||||
options && options.temporaryReferences
|
||||
? options.temporaryReferences
|
||||
: undefined,
|
||||
__DEV__ ? findSourceMapURL : undefined,
|
||||
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
function startReadingFromUniversalStream(
|
||||
response: FlightResponse,
|
||||
stream: ReadableStream,
|
||||
@@ -176,28 +210,7 @@ export function createFromReadableStream<T>(
|
||||
stream: ReadableStream,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const response: FlightResponse = createResponse(
|
||||
null, // bundlerConfig
|
||||
null, // serverReferenceConfig
|
||||
null, // moduleLoading
|
||||
callCurrentServerCallback,
|
||||
undefined, // encodeFormAction
|
||||
undefined, // nonce
|
||||
options && options.temporaryReferences
|
||||
? options.temporaryReferences
|
||||
: undefined,
|
||||
__DEV__ ? findSourceMapURL : undefined,
|
||||
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel !== undefined &&
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(options.debugChannel.writable)
|
||||
: undefined,
|
||||
);
|
||||
const response: FlightResponse = createResponseFromOptions(options);
|
||||
if (
|
||||
__DEV__ &&
|
||||
options &&
|
||||
@@ -226,28 +239,7 @@ export function createFromFetch<T>(
|
||||
promiseForResponse: Promise<Response>,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const response: FlightResponse = createResponse(
|
||||
null, // bundlerConfig
|
||||
null, // serverReferenceConfig
|
||||
null, // moduleLoading
|
||||
callCurrentServerCallback,
|
||||
undefined, // encodeFormAction
|
||||
undefined, // nonce
|
||||
options && options.temporaryReferences
|
||||
? options.temporaryReferences
|
||||
: undefined,
|
||||
__DEV__ ? findSourceMapURL : undefined,
|
||||
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel !== undefined &&
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(options.debugChannel.writable)
|
||||
: undefined,
|
||||
);
|
||||
const response: FlightResponse = createResponseFromOptions(options);
|
||||
promiseForResponse.then(
|
||||
function (r) {
|
||||
if (
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
|
||||
import type {
|
||||
DebugChannel,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
|
||||
import {
|
||||
@@ -81,6 +84,14 @@ export type Options = {
|
||||
};
|
||||
|
||||
function createResponseFromOptions(options?: Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
null, // bundlerConfig
|
||||
null, // serverReferenceConfig
|
||||
@@ -96,6 +107,7 @@ function createResponseFromOptions(options?: Options) {
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
import type {Response} from 'react-client/src/ReactFlightClient';
|
||||
import type {DebugChannel, Response} from 'react-client/src/ReactFlightClient';
|
||||
import type {Readable} from 'stream';
|
||||
|
||||
import {
|
||||
@@ -82,6 +82,14 @@ export function createFromNodeStream<T>(
|
||||
stream: Readable,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const response: Response = createResponse(
|
||||
null, // bundlerConfig
|
||||
null, // serverReferenceConfig
|
||||
@@ -95,6 +103,7 @@ export function createFromNodeStream<T>(
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
|
||||
if (__DEV__ && options && options.debugChannel) {
|
||||
|
||||
@@ -19,10 +19,12 @@ global.WritableStream =
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
global.TextDecoder = require('util').TextDecoder;
|
||||
|
||||
let clientExports;
|
||||
let React;
|
||||
let ReactDOMClient;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMClient;
|
||||
let ReactServer;
|
||||
let ReactServerScheduler;
|
||||
let act;
|
||||
let serverAct;
|
||||
@@ -39,10 +41,13 @@ describe('ReactFlightTurbopackDOMBrowser', () => {
|
||||
|
||||
// Simulate the condition resolution
|
||||
jest.mock('react', () => require('react/react.react-server'));
|
||||
ReactServer = require('react');
|
||||
|
||||
jest.mock('react-server-dom-turbopack/server', () =>
|
||||
require('react-server-dom-turbopack/server.browser'),
|
||||
);
|
||||
const TurbopackMock = require('./utils/TurbopackMock');
|
||||
clientExports = TurbopackMock.clientExports;
|
||||
turbopackMap = TurbopackMock.turbopackMap;
|
||||
|
||||
ReactServerDOMServer = require('react-server-dom-turbopack/server.browser');
|
||||
@@ -77,6 +82,15 @@ describe('ReactFlightTurbopackDOMBrowser', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
return (
|
||||
str &&
|
||||
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
|
||||
return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
it('should resolve HTML using W3C streams', async () => {
|
||||
function Text({children}) {
|
||||
return <span>{children}</span>;
|
||||
@@ -163,4 +177,74 @@ describe('ReactFlightTurbopackDOMBrowser', () => {
|
||||
|
||||
expect(container.innerHTML).toBe('<div>Hi</div>');
|
||||
});
|
||||
|
||||
it('can transport debug info through a dedicated debug channel', async () => {
|
||||
let ownerStack;
|
||||
|
||||
const ClientComponent = clientExports(() => {
|
||||
ownerStack = React.captureOwnerStack ? React.captureOwnerStack() : null;
|
||||
return <p>Hi</p>;
|
||||
});
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
null,
|
||||
ReactServer.createElement(ClientComponent, null),
|
||||
);
|
||||
}
|
||||
|
||||
let debugReadableStreamController;
|
||||
|
||||
const debugReadableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
debugReadableStreamController = controller;
|
||||
},
|
||||
});
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
ReactServer.createElement(App, null),
|
||||
turbopackMap,
|
||||
{
|
||||
debugChannel: {
|
||||
writable: new WritableStream({
|
||||
write(chunk) {
|
||||
debugReadableStreamController.enqueue(chunk);
|
||||
},
|
||||
close() {
|
||||
debugReadableStreamController.close();
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
|
||||
replayConsoleLogs: true,
|
||||
debugChannel: {
|
||||
readable: debugReadableStream,
|
||||
// Explicitly not defining a writable side here. Its presence was
|
||||
// previously used as a condition to wait for referenced debug chunks.
|
||||
},
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<ClientRoot response={response} />);
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
|
||||
}
|
||||
|
||||
expect(container.innerHTML).toBe('<p>Hi</p>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -241,4 +241,101 @@ describe('ReactFlightTurbopackDOMEdge', () => {
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('can transport debug info through a slow debug channel', async () => {
|
||||
function Thrower() {
|
||||
throw new Error('ssr-throw');
|
||||
}
|
||||
|
||||
const ClientComponentOnTheClient = clientExports(
|
||||
Thrower,
|
||||
123,
|
||||
'path/to/chunk.js',
|
||||
);
|
||||
|
||||
const ClientComponentOnTheServer = clientExports(Thrower);
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
null,
|
||||
ReactServer.createElement(ClientComponentOnTheClient, null),
|
||||
);
|
||||
}
|
||||
|
||||
let debugReadableStreamController;
|
||||
|
||||
const debugReadableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
debugReadableStreamController = controller;
|
||||
},
|
||||
});
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
ReactServer.createElement(App, null),
|
||||
turbopackMap,
|
||||
{
|
||||
debugChannel: {
|
||||
writable: new WritableStream({
|
||||
write(chunk) {
|
||||
debugReadableStreamController.enqueue(chunk);
|
||||
},
|
||||
close() {
|
||||
debugReadableStreamController.close();
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const serverConsumerManifest = {
|
||||
moduleMap: {
|
||||
[turbopackMap[ClientComponentOnTheClient.$$id].id]: {
|
||||
'*': turbopackMap[ClientComponentOnTheServer.$$id],
|
||||
},
|
||||
},
|
||||
moduleLoading: null,
|
||||
};
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
|
||||
serverConsumerManifest,
|
||||
debugChannel: {
|
||||
readable:
|
||||
// Create a delayed stream to simulate that the debug stream might
|
||||
// be transported slower than the RSC stream, which must not lead to
|
||||
// missing debug info.
|
||||
createDelayedStream(debugReadableStream),
|
||||
},
|
||||
});
|
||||
|
||||
let ownerStack;
|
||||
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(
|
||||
<ClientRoot response={response} />,
|
||||
{
|
||||
onError(err, errorInfo) {
|
||||
ownerStack = React.captureOwnerStack
|
||||
? React.captureOwnerStack()
|
||||
: null;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await readResult(ssrStream);
|
||||
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
|
||||
|
||||
expect(result).toContain(
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,15 +91,19 @@ describe('ReactFlightTurbopackDOMNode', () => {
|
||||
}
|
||||
|
||||
function createDelayedStream() {
|
||||
return new Stream.Transform({
|
||||
let resolveDelayedStream;
|
||||
const promise = new Promise(resolve => (resolveDelayedStream = resolve));
|
||||
const delayedStream = new Stream.Transform({
|
||||
...streamOptions,
|
||||
transform(chunk, encoding, callback) {
|
||||
setTimeout(() => {
|
||||
// Artificially delay pushing the chunk.
|
||||
promise.then(() => {
|
||||
this.push(chunk);
|
||||
callback();
|
||||
});
|
||||
},
|
||||
});
|
||||
return {delayedStream, resolveDelayedStream};
|
||||
}
|
||||
|
||||
it('should allow an alternative module mapping to be used for SSR', async () => {
|
||||
@@ -202,8 +206,102 @@ describe('ReactFlightTurbopackDOMNode', () => {
|
||||
|
||||
// Create a delayed stream to simulate that the RSC stream might be
|
||||
// transported slower than the debug channel, which must not lead to a
|
||||
// `controller.enqueueModel is not a function` error in the Flight client.
|
||||
const readable = createDelayedStream();
|
||||
// `Connection closed` error in the Flight client.
|
||||
const {delayedStream, resolveDelayedStream} = createDelayedStream();
|
||||
|
||||
rscStream.pipe(delayedStream);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const serverConsumerManifest = {
|
||||
moduleMap: {
|
||||
[turbopackMap[ClientComponentOnTheClient.$$id].id]: {
|
||||
'*': turbopackMap[ClientComponentOnTheServer.$$id],
|
||||
},
|
||||
},
|
||||
moduleLoading: null,
|
||||
};
|
||||
|
||||
const response = ReactServerDOMClient.createFromNodeStream(
|
||||
delayedStream,
|
||||
serverConsumerManifest,
|
||||
{debugChannel: debugReadable},
|
||||
);
|
||||
|
||||
setTimeout(resolveDelayedStream);
|
||||
|
||||
let ownerStack;
|
||||
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot response={response} />,
|
||||
{
|
||||
onError(err, errorInfo) {
|
||||
ownerStack = React.captureOwnerStack
|
||||
? React.captureOwnerStack()
|
||||
: null;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await readResult(ssrStream);
|
||||
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
|
||||
|
||||
expect(result).toContain(
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('can transport debug info through a slow debug channel', async () => {
|
||||
function Thrower() {
|
||||
throw new Error('ssr-throw');
|
||||
}
|
||||
|
||||
const ClientComponentOnTheClient = clientExports(
|
||||
Thrower,
|
||||
123,
|
||||
'path/to/chunk.js',
|
||||
);
|
||||
|
||||
const ClientComponentOnTheServer = clientExports(Thrower);
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
null,
|
||||
ReactServer.createElement(ClientComponentOnTheClient, null),
|
||||
);
|
||||
}
|
||||
|
||||
// Create a delayed stream to simulate that the debug stream might be
|
||||
// transported slower than the RSC stream, which must not lead to missing
|
||||
// debug info.
|
||||
const {delayedStream, resolveDelayedStream} = createDelayedStream();
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
ReactServer.createElement(App, null),
|
||||
turbopackMap,
|
||||
{
|
||||
debugChannel: new Stream.Writable({
|
||||
write(chunk, encoding, callback) {
|
||||
delayedStream.write(chunk, encoding);
|
||||
callback();
|
||||
},
|
||||
final() {
|
||||
delayedStream.end();
|
||||
},
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const readable = new Stream.PassThrough(streamOptions);
|
||||
|
||||
rscStream.pipe(readable);
|
||||
|
||||
@@ -223,9 +321,11 @@ describe('ReactFlightTurbopackDOMNode', () => {
|
||||
const response = ReactServerDOMClient.createFromNodeStream(
|
||||
readable,
|
||||
serverConsumerManifest,
|
||||
{debugChannel: debugReadable},
|
||||
{debugChannel: delayedStream},
|
||||
);
|
||||
|
||||
setTimeout(resolveDelayedStream);
|
||||
|
||||
let ownerStack;
|
||||
|
||||
const ssrStream = await serverAct(() =>
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
import type {Thenable} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response as FlightResponse,
|
||||
FindSourceMapURLCallback,
|
||||
DebugChannel,
|
||||
DebugChannelCallback,
|
||||
FindSourceMapURLCallback,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
@@ -71,6 +72,19 @@ function createDebugCallbackFromWritableStream(
|
||||
}
|
||||
|
||||
function createResponseFromOptions(options: void | Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback:
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(
|
||||
options.debugChannel.writable,
|
||||
)
|
||||
: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
null,
|
||||
null,
|
||||
@@ -88,12 +102,7 @@ function createResponseFromOptions(options: void | Options) {
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel !== undefined &&
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(options.debugChannel.writable)
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
DebugChannel,
|
||||
Response as FlightResponse,
|
||||
FindSourceMapURLCallback,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
@@ -83,6 +84,14 @@ export type Options = {
|
||||
};
|
||||
|
||||
function createResponseFromOptions(options: Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
options.serverConsumerManifest.moduleMap,
|
||||
options.serverConsumerManifest.serverModuleMap,
|
||||
@@ -100,6 +109,7 @@ function createResponseFromOptions(options: Options) {
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response,
|
||||
DebugChannel,
|
||||
FindSourceMapURLCallback,
|
||||
Response,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {
|
||||
@@ -90,6 +91,14 @@ function createFromNodeStream<T>(
|
||||
serverConsumerManifest: ServerConsumerManifest,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const response: Response = createResponse(
|
||||
serverConsumerManifest.moduleMap,
|
||||
serverConsumerManifest.serverModuleMap,
|
||||
@@ -105,6 +114,7 @@ function createFromNodeStream<T>(
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
|
||||
if (__DEV__ && options && options.debugChannel) {
|
||||
|
||||
@@ -174,6 +174,15 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeCodeLocInfo(str) {
|
||||
return (
|
||||
str &&
|
||||
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
|
||||
return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
it('should resolve HTML using W3C streams', async () => {
|
||||
function Text({children}) {
|
||||
return <span>{children}</span>;
|
||||
@@ -2767,4 +2776,74 @@ describe('ReactFlightDOMBrowser', () => {
|
||||
|
||||
expect(container.innerHTML).toBe('<div>Hi</div>');
|
||||
});
|
||||
|
||||
it('can transport debug info through a dedicated debug channel', async () => {
|
||||
let ownerStack;
|
||||
|
||||
const ClientComponent = clientExports(() => {
|
||||
ownerStack = React.captureOwnerStack ? React.captureOwnerStack() : null;
|
||||
return <p>Hi</p>;
|
||||
});
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
null,
|
||||
ReactServer.createElement(ClientComponent, null),
|
||||
);
|
||||
}
|
||||
|
||||
let debugReadableStreamController;
|
||||
|
||||
const debugReadableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
debugReadableStreamController = controller;
|
||||
},
|
||||
});
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
ReactServer.createElement(App, null),
|
||||
webpackMap,
|
||||
{
|
||||
debugChannel: {
|
||||
writable: new WritableStream({
|
||||
write(chunk) {
|
||||
debugReadableStreamController.enqueue(chunk);
|
||||
},
|
||||
close() {
|
||||
debugReadableStreamController.close();
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
|
||||
replayConsoleLogs: true,
|
||||
debugChannel: {
|
||||
readable: debugReadableStream,
|
||||
// Explicitly not defining a writable side here. Its presence was
|
||||
// previously used as a condition to wait for referenced debug chunks.
|
||||
},
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<ClientRoot response={response} />);
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
|
||||
}
|
||||
|
||||
expect(container.innerHTML).toBe('<p>Hi</p>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2089,4 +2089,103 @@ describe('ReactFlightDOMEdge', () => {
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('can transport debug info through a slow debug channel', async () => {
|
||||
function Thrower() {
|
||||
throw new Error('ssr-throw');
|
||||
}
|
||||
|
||||
const ClientComponentOnTheClient = clientExports(
|
||||
Thrower,
|
||||
123,
|
||||
'path/to/chunk.js',
|
||||
);
|
||||
|
||||
const ClientComponentOnTheServer = clientExports(Thrower);
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
null,
|
||||
ReactServer.createElement(ClientComponentOnTheClient, null),
|
||||
);
|
||||
}
|
||||
|
||||
let debugReadableStreamController;
|
||||
|
||||
const debugReadableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
debugReadableStreamController = controller;
|
||||
},
|
||||
});
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
passThrough(
|
||||
ReactServerDOMServer.renderToReadableStream(
|
||||
ReactServer.createElement(App, null),
|
||||
webpackMap,
|
||||
{
|
||||
debugChannel: {
|
||||
writable: new WritableStream({
|
||||
write(chunk) {
|
||||
debugReadableStreamController.enqueue(chunk);
|
||||
},
|
||||
close() {
|
||||
debugReadableStreamController.close();
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const serverConsumerManifest = {
|
||||
moduleMap: {
|
||||
[webpackMap[ClientComponentOnTheClient.$$id].id]: {
|
||||
'*': webpackMap[ClientComponentOnTheServer.$$id],
|
||||
},
|
||||
},
|
||||
moduleLoading: webpackModuleLoading,
|
||||
};
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(rscStream, {
|
||||
serverConsumerManifest,
|
||||
debugChannel: {
|
||||
readable:
|
||||
// Create a delayed stream to simulate that the debug stream might be
|
||||
// transported slower than the RSC stream, which must not lead to
|
||||
// missing debug info.
|
||||
createDelayedStream(debugReadableStream),
|
||||
},
|
||||
});
|
||||
|
||||
let ownerStack;
|
||||
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToReadableStream(
|
||||
<ClientRoot response={response} />,
|
||||
{
|
||||
onError(err, errorInfo) {
|
||||
ownerStack = React.captureOwnerStack
|
||||
? React.captureOwnerStack()
|
||||
: null;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await readResult(ssrStream);
|
||||
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
|
||||
|
||||
expect(result).toContain(
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,16 +152,19 @@ describe('ReactFlightDOMNode', () => {
|
||||
}
|
||||
|
||||
function createDelayedStream() {
|
||||
return new Stream.Transform({
|
||||
let resolveDelayedStream;
|
||||
const promise = new Promise(resolve => (resolveDelayedStream = resolve));
|
||||
const delayedStream = new Stream.Transform({
|
||||
...streamOptions,
|
||||
transform(chunk, encoding, callback) {
|
||||
// Artificially delay between pushing chunks.
|
||||
setTimeout(() => {
|
||||
// Artificially delay pushing the chunk.
|
||||
promise.then(() => {
|
||||
this.push(chunk);
|
||||
callback();
|
||||
});
|
||||
},
|
||||
});
|
||||
return {delayedStream, resolveDelayedStream};
|
||||
}
|
||||
|
||||
it('should support web streams in node', async () => {
|
||||
@@ -963,8 +966,102 @@ describe('ReactFlightDOMNode', () => {
|
||||
|
||||
// Create a delayed stream to simulate that the RSC stream might be
|
||||
// transported slower than the debug channel, which must not lead to a
|
||||
// `controller.enqueueModel is not a function` error in the Flight client.
|
||||
const readable = createDelayedStream();
|
||||
// `Connection closed` error in the Flight client.
|
||||
const {delayedStream, resolveDelayedStream} = createDelayedStream();
|
||||
|
||||
rscStream.pipe(delayedStream);
|
||||
|
||||
function ClientRoot({response}) {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const serverConsumerManifest = {
|
||||
moduleMap: {
|
||||
[webpackMap[ClientComponentOnTheClient.$$id].id]: {
|
||||
'*': webpackMap[ClientComponentOnTheServer.$$id],
|
||||
},
|
||||
},
|
||||
moduleLoading: webpackModuleLoading,
|
||||
};
|
||||
|
||||
const response = ReactServerDOMClient.createFromNodeStream(
|
||||
delayedStream,
|
||||
serverConsumerManifest,
|
||||
{debugChannel: debugReadable},
|
||||
);
|
||||
|
||||
setTimeout(resolveDelayedStream);
|
||||
|
||||
let ownerStack;
|
||||
|
||||
const ssrStream = await serverAct(() =>
|
||||
ReactDOMServer.renderToPipeableStream(
|
||||
<ClientRoot response={response} />,
|
||||
{
|
||||
onError(err, errorInfo) {
|
||||
ownerStack = React.captureOwnerStack
|
||||
? React.captureOwnerStack()
|
||||
: null;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await readResult(ssrStream);
|
||||
|
||||
expect(normalizeCodeLocInfo(ownerStack)).toBe('\n in App (at **)');
|
||||
|
||||
expect(result).toContain(
|
||||
'Switched to client rendering because the server rendering errored:\n\nssr-throw',
|
||||
);
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
it('can transport debug info through a slow debug channel', async () => {
|
||||
function Thrower() {
|
||||
throw new Error('ssr-throw');
|
||||
}
|
||||
|
||||
const ClientComponentOnTheClient = clientExports(
|
||||
Thrower,
|
||||
123,
|
||||
'path/to/chunk.js',
|
||||
);
|
||||
|
||||
const ClientComponentOnTheServer = clientExports(Thrower);
|
||||
|
||||
function App() {
|
||||
return ReactServer.createElement(
|
||||
ReactServer.Suspense,
|
||||
null,
|
||||
ReactServer.createElement(ClientComponentOnTheClient, null),
|
||||
);
|
||||
}
|
||||
|
||||
// Create a delayed stream to simulate that the debug stream might be
|
||||
// transported slower than the RSC stream, which must not lead to missing
|
||||
// debug info.
|
||||
const {delayedStream, resolveDelayedStream} = createDelayedStream();
|
||||
|
||||
const rscStream = await serverAct(() =>
|
||||
ReactServerDOMServer.renderToPipeableStream(
|
||||
ReactServer.createElement(App, null),
|
||||
webpackMap,
|
||||
{
|
||||
debugChannel: new Stream.Writable({
|
||||
write(chunk, encoding, callback) {
|
||||
delayedStream.write(chunk, encoding);
|
||||
callback();
|
||||
},
|
||||
final() {
|
||||
delayedStream.end();
|
||||
},
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const readable = new Stream.PassThrough(streamOptions);
|
||||
|
||||
rscStream.pipe(readable);
|
||||
|
||||
@@ -984,9 +1081,11 @@ describe('ReactFlightDOMNode', () => {
|
||||
const response = ReactServerDOMClient.createFromNodeStream(
|
||||
readable,
|
||||
serverConsumerManifest,
|
||||
{debugChannel: debugReadable},
|
||||
{debugChannel: delayedStream},
|
||||
);
|
||||
|
||||
setTimeout(resolveDelayedStream);
|
||||
|
||||
let ownerStack;
|
||||
|
||||
const ssrStream = await serverAct(() =>
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
import type {Thenable} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response as FlightResponse,
|
||||
FindSourceMapURLCallback,
|
||||
DebugChannel,
|
||||
DebugChannelCallback,
|
||||
FindSourceMapURLCallback,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
@@ -71,6 +72,19 @@ function createDebugCallbackFromWritableStream(
|
||||
}
|
||||
|
||||
function createResponseFromOptions(options: void | Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback:
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(
|
||||
options.debugChannel.writable,
|
||||
)
|
||||
: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
null,
|
||||
null,
|
||||
@@ -88,12 +102,7 @@ function createResponseFromOptions(options: void | Options) {
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
__DEV__ &&
|
||||
options &&
|
||||
options.debugChannel !== undefined &&
|
||||
options.debugChannel.writable !== undefined
|
||||
? createDebugCallbackFromWritableStream(options.debugChannel.writable)
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response as FlightResponse,
|
||||
DebugChannel,
|
||||
FindSourceMapURLCallback,
|
||||
Response as FlightResponse,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
|
||||
@@ -83,6 +84,14 @@ export type Options = {
|
||||
};
|
||||
|
||||
function createResponseFromOptions(options: Options) {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return createResponse(
|
||||
options.serverConsumerManifest.moduleMap,
|
||||
options.serverConsumerManifest.serverModuleMap,
|
||||
@@ -100,6 +109,7 @@ function createResponseFromOptions(options: Options) {
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
||||
|
||||
import type {
|
||||
Response,
|
||||
DebugChannel,
|
||||
FindSourceMapURLCallback,
|
||||
Response,
|
||||
} from 'react-client/src/ReactFlightClient';
|
||||
|
||||
import type {
|
||||
@@ -90,6 +91,14 @@ function createFromNodeStream<T>(
|
||||
serverConsumerManifest: ServerConsumerManifest,
|
||||
options?: Options,
|
||||
): Thenable<T> {
|
||||
const debugChannel: void | DebugChannel =
|
||||
__DEV__ && options && options.debugChannel !== undefined
|
||||
? {
|
||||
hasReadable: options.debugChannel.readable !== undefined,
|
||||
callback: null,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const response: Response = createResponse(
|
||||
serverConsumerManifest.moduleMap,
|
||||
serverConsumerManifest.serverModuleMap,
|
||||
@@ -105,6 +114,7 @@ function createFromNodeStream<T>(
|
||||
__DEV__ && options && options.environmentName
|
||||
? options.environmentName
|
||||
: undefined,
|
||||
debugChannel,
|
||||
);
|
||||
|
||||
if (__DEV__ && options && options.debugChannel) {
|
||||
|
||||
Reference in New Issue
Block a user