mirror of
https://github.com/facebook/react.git
synced 2026-02-24 04:33:04 +00:00
When a debug channel is used between the Flight server and a browser Flight client, we want to allow the same RSC stream to be used for server-side rendering. To support this, the Edge and Node Flight clients also need to accept a `debugChannel` option. Without it, debug information would be missing (e.g. for SSR error stacks), and in some cases this could result in `Connection closed` errors. This PR adds support for the `debugChannel` option in the Edge and Node clients for ESM, Parcel, Turbopack, and Webpack. Unlike the browser clients, these clients only support a one-way channel, since the Flight server’s return protocol is not designed for multiple clients. The implementation follows the approach used in the browser clients, but excludes the writable parts.
115 lines
3.1 KiB
JavaScript
115 lines
3.1 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
|
|
import type {Response} from 'react-client/src/ReactFlightClient';
|
|
import type {Readable} from 'stream';
|
|
|
|
import {
|
|
createResponse,
|
|
createStreamState,
|
|
getRoot,
|
|
reportGlobalError,
|
|
processStringChunk,
|
|
processBinaryChunk,
|
|
close,
|
|
} from 'react-client/src/ReactFlightClient';
|
|
|
|
export * from './ReactFlightDOMClientEdge';
|
|
|
|
function findSourceMapURL(filename: string, environmentName: string) {
|
|
const devServer = parcelRequire.meta.devServer;
|
|
if (devServer != null) {
|
|
const qs = new URLSearchParams();
|
|
qs.set('filename', filename);
|
|
qs.set('env', environmentName);
|
|
return devServer + '/__parcel_source_map?' + qs.toString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function noServerCall() {
|
|
throw new Error(
|
|
'Server Functions cannot be called during initial render. ' +
|
|
'This would create a fetch waterfall. Try to use a Server Component ' +
|
|
'to pass data to Client Components instead.',
|
|
);
|
|
}
|
|
|
|
type EncodeFormActionCallback = <A>(
|
|
id: any,
|
|
args: Promise<A>,
|
|
) => ReactCustomFormAction;
|
|
|
|
export type Options = {
|
|
nonce?: string,
|
|
encodeFormAction?: EncodeFormActionCallback,
|
|
replayConsoleLogs?: boolean,
|
|
environmentName?: string,
|
|
// For the Node.js client we only support a single-direction debug channel.
|
|
debugChannel?: Readable,
|
|
};
|
|
|
|
function startReadingFromStream(
|
|
response: Response,
|
|
stream: Readable,
|
|
isSecondaryStream: boolean,
|
|
): void {
|
|
const streamState = createStreamState();
|
|
|
|
stream.on('data', chunk => {
|
|
if (typeof chunk === 'string') {
|
|
processStringChunk(response, streamState, chunk);
|
|
} else {
|
|
processBinaryChunk(response, streamState, chunk);
|
|
}
|
|
});
|
|
|
|
stream.on('error', error => {
|
|
reportGlobalError(response, error);
|
|
});
|
|
|
|
stream.on('end', () => {
|
|
// If we're the secondary stream, then we don't close the response until the
|
|
// debug channel closes.
|
|
if (!isSecondaryStream) {
|
|
close(response);
|
|
}
|
|
});
|
|
}
|
|
|
|
export function createFromNodeStream<T>(
|
|
stream: Readable,
|
|
options?: Options,
|
|
): Thenable<T> {
|
|
const response: Response = createResponse(
|
|
null, // bundlerConfig
|
|
null, // serverReferenceConfig
|
|
null, // moduleLoading
|
|
noServerCall,
|
|
options ? options.encodeFormAction : undefined,
|
|
options && typeof options.nonce === 'string' ? options.nonce : undefined,
|
|
undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
|
|
__DEV__ ? findSourceMapURL : undefined,
|
|
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
|
|
__DEV__ && options && options.environmentName
|
|
? options.environmentName
|
|
: undefined,
|
|
);
|
|
|
|
if (__DEV__ && options && options.debugChannel) {
|
|
startReadingFromStream(response, options.debugChannel, false);
|
|
startReadingFromStream(response, stream, true);
|
|
} else {
|
|
startReadingFromStream(response, stream, false);
|
|
}
|
|
|
|
return getRoot(response);
|
|
}
|