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 {ReactNodeList} from 'shared/ReactTypes';
    
  11. import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
    
  12. import type {PostponedState} from 'react-server/src/ReactFizzServer';
    
  13. import type {ImportMap} from '../shared/ReactDOMTypes';
    
  14. 
    
  15. import {Writable, Readable} from 'stream';
    
  16. 
    
  17. import ReactVersion from 'shared/ReactVersion';
    
  18. 
    
  19. import {
    
  20.   createPrerenderRequest,
    
  21.   startWork,
    
  22.   startFlowing,
    
  23.   abort,
    
  24.   getPostponedState,
    
  25. } from 'react-server/src/ReactFizzServer';
    
  26. 
    
  27. import {
    
  28.   createResumableState,
    
  29.   createRenderState,
    
  30.   createRootFormatContext,
    
  31. } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
    
  32. 
    
  33. type Options = {
    
  34.   identifierPrefix?: string,
    
  35.   namespaceURI?: string,
    
  36.   bootstrapScriptContent?: string,
    
  37.   bootstrapScripts?: Array<string | BootstrapScriptDescriptor>,
    
  38.   bootstrapModules?: Array<string | BootstrapScriptDescriptor>,
    
  39.   progressiveChunkSize?: number,
    
  40.   signal?: AbortSignal,
    
  41.   onError?: (error: mixed) => ?string,
    
  42.   onPostpone?: (reason: string) => void,
    
  43.   unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
    
  44.   importMap?: ImportMap,
    
  45. };
    
  46. 
    
  47. type StaticResult = {
    
  48.   postponed: null | PostponedState,
    
  49.   prelude: Readable,
    
  50. };
    
  51. 
    
  52. function createFakeWritable(readable: any): Writable {
    
  53.   // The current host config expects a Writable so we create
    
  54.   // a fake writable for now to push into the Readable.
    
  55.   return ({
    
  56.     write(chunk) {
    
  57.       return readable.push(chunk);
    
  58.     },
    
  59.     end() {
    
  60.       readable.push(null);
    
  61.     },
    
  62.     destroy(error) {
    
  63.       readable.destroy(error);
    
  64.     },
    
  65.   }: any);
    
  66. }
    
  67. 
    
  68. function prerenderToNodeStream(
    
  69.   children: ReactNodeList,
    
  70.   options?: Options,
    
  71. ): Promise<StaticResult> {
    
  72.   return new Promise((resolve, reject) => {
    
  73.     const onFatalError = reject;
    
  74. 
    
  75.     function onAllReady() {
    
  76.       const readable: Readable = new Readable({
    
  77.         read() {
    
  78.           startFlowing(request, writable);
    
  79.         },
    
  80.       });
    
  81.       const writable = createFakeWritable(readable);
    
  82. 
    
  83.       const result = {
    
  84.         postponed: getPostponedState(request),
    
  85.         prelude: readable,
    
  86.       };
    
  87.       resolve(result);
    
  88.     }
    
  89.     const resumableState = createResumableState(
    
  90.       options ? options.identifierPrefix : undefined,
    
  91.       options ? options.unstable_externalRuntimeSrc : undefined,
    
  92.     );
    
  93.     const request = createPrerenderRequest(
    
  94.       children,
    
  95.       resumableState,
    
  96.       createRenderState(
    
  97.         resumableState,
    
  98.         undefined, // nonce is not compatible with prerendered bootstrap scripts
    
  99.         options ? options.bootstrapScriptContent : undefined,
    
  100.         options ? options.bootstrapScripts : undefined,
    
  101.         options ? options.bootstrapModules : undefined,
    
  102.         options ? options.unstable_externalRuntimeSrc : undefined,
    
  103.         options ? options.importMap : undefined,
    
  104.       ),
    
  105.       createRootFormatContext(options ? options.namespaceURI : undefined),
    
  106.       options ? options.progressiveChunkSize : undefined,
    
  107.       options ? options.onError : undefined,
    
  108.       onAllReady,
    
  109.       undefined,
    
  110.       undefined,
    
  111.       onFatalError,
    
  112.       options ? options.onPostpone : undefined,
    
  113.     );
    
  114.     if (options && options.signal) {
    
  115.       const signal = options.signal;
    
  116.       if (signal.aborted) {
    
  117.         abort(request, (signal: any).reason);
    
  118.       } else {
    
  119.         const listener = () => {
    
  120.           abort(request, (signal: any).reason);
    
  121.           signal.removeEventListener('abort', listener);
    
  122.         };
    
  123.         signal.addEventListener('abort', listener);
    
  124.       }
    
  125.     }
    
  126.     startWork(request);
    
  127.   });
    
  128. }
    
  129. 
    
  130. export {prerenderToNodeStream, ReactVersion as version};