/** * 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} from 'shared/ReactTypes'; import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; import ReactVersion from 'shared/ReactVersion'; import { createRequest, startWork, startFlowing, abort, } from 'react-server/src/ReactFizzServer'; import { createResponseState, createRootFormatContext, } from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; type Options = { identifierPrefix?: string, namespaceURI?: string, nonce?: string, bootstrapScriptContent?: string, bootstrapScripts?: Array, bootstrapModules?: Array, progressiveChunkSize?: number, signal?: AbortSignal, onError?: (error: mixed) => ?string, unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor, }; // TODO: Move to sub-classing ReadableStream. type ReactDOMServerReadableStream = ReadableStream & { allReady: Promise, }; function renderToReadableStream( children: ReactNodeList, options?: Options, ): Promise { return new Promise((resolve, reject) => { let onFatalError; let onAllReady; const allReady = new Promise((res, rej) => { onAllReady = res; onFatalError = rej; }); function onShellReady() { const stream: ReactDOMServerReadableStream = (new ReadableStream( { type: 'bytes', pull: (controller): ?Promise => { startFlowing(request, controller); }, cancel: (reason): ?Promise => { abort(request); }, }, // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams. {highWaterMark: 0}, ): 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 request = createRequest( children, createResponseState( options ? options.identifierPrefix : undefined, options ? options.nonce : undefined, options ? options.bootstrapScriptContent : undefined, options ? options.bootstrapScripts : undefined, options ? options.bootstrapModules : undefined, options ? options.unstable_externalRuntimeSrc : undefined, ), createRootFormatContext(options ? options.namespaceURI : undefined), options ? options.progressiveChunkSize : undefined, options ? options.onError : undefined, onAllReady, onShellReady, onShellError, onFatalError, ); 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};