1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import type {Request, PostponedState} from 'react-server/src/ReactFizzServer';
    
  11. import type {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
    
  12. import type {Writable} from 'stream';
    
  13. import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
    
  14. import type {Destination} from 'react-server/src/ReactServerStreamConfigNode';
    
  15. import type {ImportMap} from '../shared/ReactDOMTypes';
    
  16. 
    
  17. import ReactVersion from 'shared/ReactVersion';
    
  18. 
    
  19. import {
    
  20.   createRequest,
    
  21.   resumeRequest,
    
  22.   startWork,
    
  23.   startFlowing,
    
  24.   stopFlowing,
    
  25.   abort,
    
  26. } from 'react-server/src/ReactFizzServer';
    
  27. 
    
  28. import {
    
  29.   createResumableState,
    
  30.   createRenderState,
    
  31.   resumeRenderState,
    
  32.   createRootFormatContext,
    
  33. } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
    
  34. 
    
  35. function createDrainHandler(destination: Destination, request: Request) {
    
  36.   return () => startFlowing(request, destination);
    
  37. }
    
  38. 
    
  39. function createCancelHandler(request: Request, reason: string) {
    
  40.   return () => {
    
  41.     stopFlowing(request);
    
  42.     // eslint-disable-next-line react-internal/prod-error-codes
    
  43.     abort(request, new Error(reason));
    
  44.   };
    
  45. }
    
  46. 
    
  47. type Options = {
    
  48.   identifierPrefix?: string,
    
  49.   namespaceURI?: string,
    
  50.   nonce?: string,
    
  51.   bootstrapScriptContent?: string,
    
  52.   bootstrapScripts?: Array<string | BootstrapScriptDescriptor>,
    
  53.   bootstrapModules?: Array<string | BootstrapScriptDescriptor>,
    
  54.   progressiveChunkSize?: number,
    
  55.   onShellReady?: () => void,
    
  56.   onShellError?: (error: mixed) => void,
    
  57.   onAllReady?: () => void,
    
  58.   onError?: (error: mixed) => ?string,
    
  59.   onPostpone?: (reason: string) => void,
    
  60.   unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
    
  61.   importMap?: ImportMap,
    
  62.   formState?: ReactFormState<any, any> | null,
    
  63. };
    
  64. 
    
  65. type ResumeOptions = {
    
  66.   nonce?: string,
    
  67.   onShellReady?: () => void,
    
  68.   onShellError?: (error: mixed) => void,
    
  69.   onAllReady?: () => void,
    
  70.   onError?: (error: mixed) => ?string,
    
  71.   onPostpone?: (reason: string) => void,
    
  72. };
    
  73. 
    
  74. type PipeableStream = {
    
  75.   // Cancel any pending I/O and put anything remaining into
    
  76.   // client rendered mode.
    
  77.   abort(reason: mixed): void,
    
  78.   pipe<T: Writable>(destination: T): T,
    
  79. };
    
  80. 
    
  81. function createRequestImpl(children: ReactNodeList, options: void | Options) {
    
  82.   const resumableState = createResumableState(
    
  83.     options ? options.identifierPrefix : undefined,
    
  84.     options ? options.unstable_externalRuntimeSrc : undefined,
    
  85.   );
    
  86.   return createRequest(
    
  87.     children,
    
  88.     resumableState,
    
  89.     createRenderState(
    
  90.       resumableState,
    
  91.       options ? options.nonce : undefined,
    
  92.       options ? options.bootstrapScriptContent : undefined,
    
  93.       options ? options.bootstrapScripts : undefined,
    
  94.       options ? options.bootstrapModules : undefined,
    
  95.       options ? options.unstable_externalRuntimeSrc : undefined,
    
  96.       options ? options.importMap : undefined,
    
  97.     ),
    
  98.     createRootFormatContext(options ? options.namespaceURI : undefined),
    
  99.     options ? options.progressiveChunkSize : undefined,
    
  100.     options ? options.onError : undefined,
    
  101.     options ? options.onAllReady : undefined,
    
  102.     options ? options.onShellReady : undefined,
    
  103.     options ? options.onShellError : undefined,
    
  104.     undefined,
    
  105.     options ? options.onPostpone : undefined,
    
  106.     options ? options.formState : undefined,
    
  107.   );
    
  108. }
    
  109. 
    
  110. function renderToPipeableStream(
    
  111.   children: ReactNodeList,
    
  112.   options?: Options,
    
  113. ): PipeableStream {
    
  114.   const request = createRequestImpl(children, options);
    
  115.   let hasStartedFlowing = false;
    
  116.   startWork(request);
    
  117.   return {
    
  118.     pipe<T: Writable>(destination: T): T {
    
  119.       if (hasStartedFlowing) {
    
  120.         throw new Error(
    
  121.           'React currently only supports piping to one writable stream.',
    
  122.         );
    
  123.       }
    
  124.       hasStartedFlowing = true;
    
  125.       startFlowing(request, destination);
    
  126.       destination.on('drain', createDrainHandler(destination, request));
    
  127.       destination.on(
    
  128.         'error',
    
  129.         createCancelHandler(
    
  130.           request,
    
  131.           'The destination stream errored while writing data.',
    
  132.         ),
    
  133.       );
    
  134.       destination.on(
    
  135.         'close',
    
  136.         createCancelHandler(request, 'The destination stream closed early.'),
    
  137.       );
    
  138.       return destination;
    
  139.     },
    
  140.     abort(reason: mixed) {
    
  141.       abort(request, reason);
    
  142.     },
    
  143.   };
    
  144. }
    
  145. 
    
  146. function resumeRequestImpl(
    
  147.   children: ReactNodeList,
    
  148.   postponedState: PostponedState,
    
  149.   options: void | ResumeOptions,
    
  150. ) {
    
  151.   return resumeRequest(
    
  152.     children,
    
  153.     postponedState,
    
  154.     resumeRenderState(
    
  155.       postponedState.resumableState,
    
  156.       options ? options.nonce : undefined,
    
  157.     ),
    
  158.     options ? options.onError : undefined,
    
  159.     options ? options.onAllReady : undefined,
    
  160.     options ? options.onShellReady : undefined,
    
  161.     options ? options.onShellError : undefined,
    
  162.     undefined,
    
  163.     options ? options.onPostpone : undefined,
    
  164.   );
    
  165. }
    
  166. 
    
  167. function resumeToPipeableStream(
    
  168.   children: ReactNodeList,
    
  169.   postponedState: PostponedState,
    
  170.   options?: ResumeOptions,
    
  171. ): PipeableStream {
    
  172.   const request = resumeRequestImpl(children, postponedState, options);
    
  173.   let hasStartedFlowing = false;
    
  174.   startWork(request);
    
  175.   return {
    
  176.     pipe<T: Writable>(destination: T): T {
    
  177.       if (hasStartedFlowing) {
    
  178.         throw new Error(
    
  179.           'React currently only supports piping to one writable stream.',
    
  180.         );
    
  181.       }
    
  182.       hasStartedFlowing = true;
    
  183.       startFlowing(request, destination);
    
  184.       destination.on('drain', createDrainHandler(destination, request));
    
  185.       destination.on(
    
  186.         'error',
    
  187.         createCancelHandler(
    
  188.           request,
    
  189.           'The destination stream errored while writing data.',
    
  190.         ),
    
  191.       );
    
  192.       destination.on(
    
  193.         'close',
    
  194.         createCancelHandler(request, 'The destination stream closed early.'),
    
  195.       );
    
  196.       return destination;
    
  197.     },
    
  198.     abort(reason: mixed) {
    
  199.       abort(request, reason);
    
  200.     },
    
  201.   };
    
  202. }
    
  203. 
    
  204. export {
    
  205.   renderToPipeableStream,
    
  206.   resumeToPipeableStream,
    
  207.   ReactVersion as version,
    
  208. };