mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Stacked on #28872 renderToStaticNodeStream was not originally deprecated when renderToNodeStream was deprecated because it did not yet have a clear analog in the modern streaming implementation for SSR. In React 19 we have already removed renderToNodeStream. This change removes renderToStaticNodeStream as well because you can replicate it's semantics using renderToPipeableStream with onAllReady or renderToReadableStream with await stream.allready.
150 lines
4.7 KiB
JavaScript
150 lines
4.7 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 {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
|
|
import type {
|
|
BootstrapScriptDescriptor,
|
|
HeadersDescriptor,
|
|
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
import type {ImportMap} from '../shared/ReactDOMTypes';
|
|
import type {ErrorInfo, PostponeInfo} from 'react-server/src/ReactFizzServer';
|
|
|
|
import ReactVersion from 'shared/ReactVersion';
|
|
|
|
import {
|
|
createRequest,
|
|
startWork,
|
|
startFlowing,
|
|
stopFlowing,
|
|
abort,
|
|
} from 'react-server/src/ReactFizzServer';
|
|
|
|
import {
|
|
createResumableState,
|
|
createRenderState,
|
|
createRootFormatContext,
|
|
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
|
|
type Options = {
|
|
identifierPrefix?: string,
|
|
namespaceURI?: string,
|
|
nonce?: string,
|
|
bootstrapScriptContent?: string,
|
|
bootstrapScripts?: Array<string | BootstrapScriptDescriptor>,
|
|
bootstrapModules?: Array<string | BootstrapScriptDescriptor>,
|
|
progressiveChunkSize?: number,
|
|
signal?: AbortSignal,
|
|
onError?: (error: mixed, errorInfo: ErrorInfo) => ?string,
|
|
onPostpone?: (reason: string, postponeInfo: PostponeInfo) => void,
|
|
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
|
|
importMap?: ImportMap,
|
|
formState?: ReactFormState<any, any> | null,
|
|
onHeaders?: (headers: Headers) => void,
|
|
maxHeadersLength?: number,
|
|
};
|
|
|
|
// TODO: Move to sub-classing ReadableStream.
|
|
type ReactDOMServerReadableStream = ReadableStream & {
|
|
allReady: Promise<void>,
|
|
};
|
|
|
|
function renderToReadableStream(
|
|
children: ReactNodeList,
|
|
options?: Options,
|
|
): Promise<ReactDOMServerReadableStream> {
|
|
return new Promise((resolve, reject) => {
|
|
let onFatalError;
|
|
let onAllReady;
|
|
const allReady = new Promise<void>((res, rej) => {
|
|
onAllReady = res;
|
|
onFatalError = rej;
|
|
});
|
|
|
|
function onShellReady() {
|
|
const stream: ReactDOMServerReadableStream = (new ReadableStream(
|
|
{
|
|
type: 'direct',
|
|
pull: (controller): ?Promise<void> => {
|
|
// $FlowIgnore
|
|
startFlowing(request, controller);
|
|
},
|
|
cancel: (reason): ?Promise<void> => {
|
|
stopFlowing(request);
|
|
abort(request, reason);
|
|
},
|
|
},
|
|
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
|
{highWaterMark: 2048},
|
|
): any);
|
|
// TODO: Move to sub-classing ReadableStream.
|
|
stream.allReady = allReady;
|
|
resolve(stream);
|
|
}
|
|
function onShellError(error: mixed) {
|
|
// If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`.
|
|
// However, `allReady` will be rejected by `onFatalError` as well.
|
|
// So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`.
|
|
allReady.catch(() => {});
|
|
reject(error);
|
|
}
|
|
|
|
const onHeaders = options ? options.onHeaders : undefined;
|
|
let onHeadersImpl;
|
|
if (onHeaders) {
|
|
onHeadersImpl = (headersDescriptor: HeadersDescriptor) => {
|
|
onHeaders(new Headers(headersDescriptor));
|
|
};
|
|
}
|
|
|
|
const resumableState = createResumableState(
|
|
options ? options.identifierPrefix : undefined,
|
|
options ? options.unstable_externalRuntimeSrc : undefined,
|
|
options ? options.bootstrapScriptContent : undefined,
|
|
options ? options.bootstrapScripts : undefined,
|
|
options ? options.bootstrapModules : undefined,
|
|
);
|
|
const request = createRequest(
|
|
children,
|
|
resumableState,
|
|
createRenderState(
|
|
resumableState,
|
|
options ? options.nonce : undefined,
|
|
options ? options.unstable_externalRuntimeSrc : undefined,
|
|
options ? options.importMap : undefined,
|
|
onHeadersImpl,
|
|
options ? options.maxHeadersLength : undefined,
|
|
),
|
|
createRootFormatContext(options ? options.namespaceURI : undefined),
|
|
options ? options.progressiveChunkSize : undefined,
|
|
options ? options.onError : undefined,
|
|
onAllReady,
|
|
onShellReady,
|
|
onShellError,
|
|
onFatalError,
|
|
options ? options.onPostpone : undefined,
|
|
options ? options.formState : undefined,
|
|
);
|
|
if (options && options.signal) {
|
|
const signal = options.signal;
|
|
if (signal.aborted) {
|
|
abort(request, (signal: any).reason);
|
|
} else {
|
|
const listener = () => {
|
|
abort(request, (signal: any).reason);
|
|
signal.removeEventListener('abort', listener);
|
|
};
|
|
signal.addEventListener('abort', listener);
|
|
}
|
|
}
|
|
startWork(request);
|
|
});
|
|
}
|
|
|
|
export {renderToReadableStream, ReactVersion as version};
|