Repository: facebook/react
Pull request: [ServerRenderer] Setup for adding data attributes streaming format

<!--
  Thanks for submitting a pull request!
  We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory.

  Before submitting a pull request, please make sure the following is done:

  1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`.
  2. Run `yarn` in the repository root.
  3. If you've fixed a bug or added code that should be tested, add tests!
  4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
  5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`.
  6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect".
  7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`).
  8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files.
  9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`).
  10. If you haven't already, complete the CLA.

  Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html
-->

## Summary
Setup for #25437. This PR does not change any existing behavior, it just adds:
- renaming local variables (to be more symmetric with changes in #25437)
- adding helper functions and script setup to test cases

<!--
 Explain the **motivation** for making this change. What existing problem does the pull request solve?
-->

## How did you test this change?

<!--
  Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface.
  How exactly did you verify that your PR solves the issue you wanted to solve?
  If you leave this empty, your PR will very likely be closed.
-->


Changed files:
filename: packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
patch: @@ -8,6 +8,7 @@
  */
 
 'use strict';
+import {replaceScriptsAndMove, mergeOptions} from '../test-utils/FizzTestUtils';
 
 let JSDOM;
 let Stream;
@@ -30,6 +31,7 @@ let container;
 let buffer = '';
 let hasErrored = false;
 let fatalError = undefined;
+const renderOptions = {};
 
 describe('ReactDOMFizzServer', () => {
   beforeEach(() => {
@@ -134,17 +136,7 @@ describe('ReactDOMFizzServer', () => {
       container.nodeName === '#document' ? container.body : container;
     while (fakeBody.firstChild) {
       const node = fakeBody.firstChild;
-      if (
-        node.nodeName === 'SCRIPT' &&
-        (CSPnonce === null || node.getAttribute('nonce') === CSPnonce)
-      ) {
-        const script = document.createElement('script');
-        script.textContent = node.textContent;
-        fakeBody.removeChild(node);
-        parent.appendChild(script);
-      } else {
-        parent.appendChild(node);
-      }
+      await replaceScriptsAndMove(window, CSPnonce, node, parent);
     }
   }
 
@@ -170,6 +162,7 @@ describe('ReactDOMFizzServer', () => {
     document = jsdom.window.document;
     container = document;
     buffer = '';
+    await replaceScriptsAndMove(window, CSPnonce, document.documentElement);
   }
 
   function getVisibleChildren(element) {
@@ -179,6 +172,7 @@ describe('ReactDOMFizzServer', () => {
       if (node.nodeType === 1) {
         if (
           node.tagName !== 'SCRIPT' &&
+          node.tagName !== 'script' &&
           node.tagName !== 'TEMPLATE' &&
           node.tagName !== 'template' &&
           !node.hasAttribute('hidden') &&
@@ -288,6 +282,13 @@ describe('ReactDOMFizzServer', () => {
     const As = as;
     return <As>{readText(text)}</As>;
   }
+  function renderToPipeableStream(jsx, options) {
+    // Merge options with renderOptions, which may contain featureFlag specific behavior
+    return ReactDOMFizzServer.renderToPipeableStream(
+      jsx,
+      mergeOptions(options, renderOptions),
+    );
+  }
 
   it('should asynchronously load a lazy component', async () => {
     let resolveA;
@@ -313,7 +314,7 @@ describe('ReactDOMFizzServer', () => {
     };
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <div>
           <div>
             <Suspense fallback={<Text text="Loading..." />}>
@@ -379,7 +380,7 @@ describe('ReactDOMFizzServer', () => {
     // Server-side
     const [App, resolve] = makeApp();
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
     expect(getVisibleChildren(container)).toEqual(
@@ -432,7 +433,7 @@ describe('ReactDOMFizzServer', () => {
       });
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <div>
             <Suspense fallback={<Text text="Loading..." />}>
               <Lazy text="Hello" />
@@ -492,13 +493,10 @@ describe('ReactDOMFizzServer', () => {
     loggedErrors.length = 0;
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-        <App isClient={false} />,
-        {
-          bootstrapScriptContent: '__INIT__();',
-          onError,
-        },
-      );
+      const {pipe} = renderToPipeableStream(<App isClient={false} />, {
+        bootstrapScriptContent: '__INIT__();',
+        onError,
+      });
       pipe(writable);
     });
     expect(loggedErrors).toEqual([]);
@@ -554,7 +552,7 @@ describe('ReactDOMFizzServer', () => {
     });
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <div>
           <Suspense fallback={<Text text="Loading..." />}>
             {lazyElement}
@@ -567,7 +565,7 @@ describe('ReactDOMFizzServer', () => {
     // Because there is no content inside the Suspense boundary that could've
     // been written, we expect to not see any additional partial data flushed
     // yet.
-    expect(container.firstChild.nextSibling).toBe(null);
+    expect(container.childNodes.length).toBe(1);
     await act(async () => {
       resolveElement({default: <Text text="Hello" />});
     });
@@ -603,7 +601,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <App isClient={false} />,
 
         {
@@ -699,7 +697,7 @@ describe('ReactDOMFizzServer', () => {
     loggedErrors.length = 0;
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <App />,
 
         {
@@ -767,7 +765,7 @@ describe('ReactDOMFizzServer', () => {
     loggedErrors.length = 0;
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <App />,
 
         {
@@ -823,7 +821,7 @@ describe('ReactDOMFizzServer', () => {
 
   it('should asynchronously load the suspense boundary', async () => {
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <div>
           <Suspense fallback={<Text text="Loading..." />}>
             <AsyncText text="Hello World" />
@@ -862,7 +860,7 @@ describe('ReactDOMFizzServer', () => {
     };
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+      const {pipe} = renderToPipeableStream(<App />, {
         bootstrapScriptContent: '__INIT__();',
       });
       pipe(writable);
@@ -936,7 +934,7 @@ describe('ReactDOMFizzServer', () => {
 
     // We originally suspend the boundary and start streaming the loading state.
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <App />,
 
         {
@@ -1018,9 +1016,7 @@ describe('ReactDOMFizzServer', () => {
 
     // We originally suspend the boundary and start streaming the loading state.
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-        <App showMore={false} />,
-      );
+      const {pipe} = renderToPipeableStream(<App showMore={false} />);
       pipe(writable);
     });
 
@@ -1093,7 +1089,7 @@ describe('ReactDOMFizzServer', () => {
 
     let controls;
     await act(async () => {
-      controls = ReactDOMFizzServer.renderToPipeableStream(<App />, {onError});
+      controls = renderToPipeableStream(<App />, {onError});
       controls.pipe(writable);
     });
 
@@ -1163,7 +1159,7 @@ describe('ReactDOMFizzServer', () => {
     };
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         // We use two nested boundaries to flush out coverage of an old reentrancy bug.
         <Suspense fallback="Loading...">
           <Suspense fallback={<Text text="Loading A..." />}>
@@ -1187,7 +1183,7 @@ describe('ReactDOMFizzServer', () => {
     });
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <Suspense fallback={<Text text="Loading B..." />}>
           <Text text="This will show B: " />
           <div>
@@ -1283,7 +1279,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
 
@@ -1370,7 +1366,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
 
@@ -1423,7 +1419,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <App />,
 
         {
@@ -1509,7 +1505,7 @@ describe('ReactDOMFizzServer', () => {
 
     try {
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<A />);
+        const {pipe} = renderToPipeableStream(<A />);
         pipe(writable);
       });
 
@@ -1607,7 +1603,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <TestProvider ctx="A">
           <div>
             <Suspense fallback={[<Text text="Loading: " />, <TestConsumer />]}>
@@ -1664,7 +1660,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <div>
           <PrintA />
           <div>
@@ -1725,7 +1721,7 @@ describe('ReactDOMFizzServer', () => {
 
     const loggedErrors = [];
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <div>
           <PrintA />
           <div>
@@ -1793,7 +1789,7 @@ describe('ReactDOMFizzServer', () => {
 
     let controls;
     await act(async () => {
-      controls = ReactDOMFizzServer.renderToPipeableStream(
+      controls = renderToPipeableStream(
         <App isClient={false} />,
 
         {
@@ -1876,7 +1872,7 @@ describe('ReactDOMFizzServer', () => {
 
   it('should be able to abort the fallback if the main content finishes first', async () => {
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <Suspense fallback={<Text text="Loading Outer" />}>
           <div>
             <Suspense
@@ -1973,9 +1969,7 @@ describe('ReactDOMFizzServer', () => {
     await jest.runAllTimers();
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-        <App isClient={false} />,
-      );
+      const {pipe} = renderToPipeableStream(<App isClient={false} />);
       pipe(writable);
     });
 
@@ -2085,7 +2079,7 @@ describe('ReactDOMFizzServer', () => {
 
     const loggedErrors = [];
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <Suspense fallback="Loading...">
           <App />
         </Suspense>,
@@ -2170,7 +2164,7 @@ describe('ReactDOMFizzServer', () => {
     }
     const loggedErrors = [];
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <Suspense fallback="Loading...">
           <App />
         </Suspense>,
@@ -2259,7 +2253,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2349,7 +2343,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2439,7 +2433,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2492,7 +2486,7 @@ describe('ReactDOMFizzServer', () => {
       );
     }
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+      const {pipe} = renderToPipeableStream(<App />, {
         onError(error) {
           Scheduler.unstable_yieldValue('[s!] ' + error.message);
         },
@@ -2577,14 +2571,11 @@ describe('ReactDOMFizzServer', () => {
         );
       }
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-          <App color="red" />,
-          {
-            onError(error) {
-              Scheduler.unstable_yieldValue('[s!] ' + error.message);
-            },
+        const {pipe} = renderToPipeableStream(<App color="red" />, {
+          onError(error) {
+            Scheduler.unstable_yieldValue('[s!] ' + error.message);
           },
-        );
+        });
         pipe(writable);
       });
       expect(Scheduler).toHaveYielded(['[s!] Oops.']);
@@ -2678,7 +2669,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <App fallbackText="Loading..." />,
           {
             onError(error) {
@@ -2807,7 +2798,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2959,7 +2950,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
     expect(Scheduler).toHaveYielded(['A', 'B']);
@@ -3025,7 +3016,7 @@ describe('ReactDOMFizzServer', () => {
     }
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
+      const {pipe} = renderToPipeableStream(<Foo />);
       pipe(writable);
     });
 
@@ -3047,9 +3038,7 @@ describe('ReactDOMFizzServer', () => {
     ]).map(item => <li key={item.get('value')}>{item.get('name')}</li>);
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-        <ul>{mappedJSX}</ul>,
-      );
+      const {pipe} = renderToPipeableStream(<ul>{mappedJSX}</ul>);
       pipe(writable);
     });
     expect(getVisibleChildren(container)).toEqual(
@@ -3081,10 +3070,7 @@ describe('ReactDOMFizzServer', () => {
     let abort;
     const loggedErrors = [];
     await act(async () => {
-      const {
-        pipe,
-        abort: abortImpl,
-      } = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+      const {pipe, abort: abortImpl} = renderToPipeableStream(<App />, {
         onError(error) {
           // In this test we contrive erroring with strings so we push the error whereas in most
           // other tests we contrive erroring with Errors and push the message.
@@ -3167,10 +3153,7 @@ describe('ReactDOMFizzServer', () => {
     let abort;
     const loggedErrors = [];
     await act(async () => {
-      const {
-        pipe,
-        abort: abortImpl,
-      } = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+      const {pipe, abort: abortImpl} = renderToPipeableStream(<App />, {
         onError(error) {
           loggedErrors.push(error.message);
           return 'a digest';
@@ -3232,7 +3215,7 @@ describe('ReactDOMFizzServer', () => {
 
   it('warns in dev if you access digest from errorInfo in onRecoverableError', async () => {
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <div>
           <Suspense fallback={'loading...'}>
             <AsyncText text={'hello'} />
@@ -3309,7 +3292,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+        const {pipe} = renderToPipeableStream(<App />, {
           onError,
         });
         pipe(writable);
@@ -3357,7 +3340,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+        const {pipe} = renderToPipeableStream(<App />, {
           onError,
         });
         pipe(writable);
@@ -3397,7 +3380,7 @@ describe('ReactDOMFizzServer', () => {
       loggedErrors.length = 0;
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+        const {pipe} = renderToPipeableStream(<App />, {
           onError,
         });
         pipe(writable);
@@ -3435,7 +3418,7 @@ describe('ReactDOMFizzServer', () => {
 
   it('accepts an integrity property for bootstrapScripts and bootstrapModules', async () => {
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3494,7 +3477,7 @@ describe('ReactDOMFizzServer', () => {
       const stringWithScriptsInIt =
         'prescription pre<scription pre<Scription pre</scRipTion pre</ScripTion </script><script><!-- <script> -->';
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<div />, {
+        const {pipe} = renderToPipeableStream(<div />, {
           bootstrapScriptContent:
             'window.__test_outlet = "This should have been replaced";var x = "' +
             stringWithScriptsInIt +
@@ -3515,7 +3498,7 @@ describe('ReactDOMFizzServer', () => {
       el.textContent = '{"one":1,\u2028\u2029"two":2}';
       const stringWithLSAndPSCharacters = el.textContent;
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<div />, {
+        const {pipe} = renderToPipeableStream(<div />, {
           bootstrapScriptContent:
             'let x = ' +
             stringWithLSAndPSCharacters +
@@ -3536,7 +3519,7 @@ describe('ReactDOMFizzServer', () => {
       // this boolean expression will be cast to a number due to the bitwise &. we will look for a truthy value (1) below
       const booleanLogicString = '1 < 2 & 3 > 1';
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<div />, {
+        const {pipe} = renderToPipeableStream(<div />, {
           bootstrapScriptContent:
             'let x = ' + booleanLogicString + '; window.__test_outlet = x;',
         });
@@ -3549,7 +3532,7 @@ describe('ReactDOMFizzServer', () => {
   // @gate enableFizzExternalRuntime
   it('supports option to load runtime as an external script', async () => {
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3608,7 +3591,7 @@ describe('ReactDOMFizzServer', () => {
 
     const [ServerApp, serverResolve] = makeApp();
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<ServerApp />);
+      const {pipe} = renderToPipeableStream(<ServerApp />);
       pipe(writable);
     });
     await act(() => {
@@ -3684,9 +3667,7 @@ describe('ReactDOMFizzServer', () => {
 
     const [ServerApp, serverResolve] = makeApp();
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-        <ServerApp text="initial" />,
-      );
+      const {pipe} = renderToPipeableStream(<ServerApp text="initial" />);
       pipe(writable);
     });
     await act(() => {
@@ -3763,9 +3744,7 @@ describe('ReactDOMFizzServer', () => {
 
     try {
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-          <App text="initial" />,
-        );
+        const {pipe} = renderToPipeableStream(<App text="initial" />);
         pipe(writable);
       });
 
@@ -3854,7 +3833,7 @@ describe('ReactDOMFizzServer', () => {
     };
 
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
 
@@ -3946,7 +3925,7 @@ describe('ReactDOMFizzServer', () => {
 
     try {
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
 
@@ -4077,7 +4056,7 @@ describe('ReactDOMFizzServer', () => {
 
     try {
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
 
@@ -4200,7 +4179,7 @@ describe('ReactDOMFizzServer', () => {
 
     try {
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
 
@@ -4271,7 +4250,7 @@ describe('ReactDOMFizzServer', () => {
       );
     }
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
 
@@ -4317,7 +4296,7 @@ describe('ReactDOMFizzServer', () => {
       );
     }
     await act(async () => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
 
@@ -4372,7 +4351,7 @@ describe('ReactDOMFizzServer', () => {
       );
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html data-html="html">
           <AsyncNoOutput />
           <AsyncHead />
@@ -4413,7 +4392,7 @@ describe('ReactDOMFizzServer', () => {
     });
 
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -4470,9 +4449,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-          <App name="Foo" />,
-        );
+        const {pipe} = renderToPipeableStream(<App name="Foo" />);
         pipe(writable);
       });
 
@@ -4510,9 +4487,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
-          <App name="Foo" />,
-        );
+        const {pipe} = renderToPipeableStream(<App name="Foo" />);
         pipe(writable);
       });
 
@@ -4562,7 +4537,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
 
@@ -4607,7 +4582,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         await afterImmediate();
         await act(() => resolveText('ello'));
         pipe(writable);
@@ -4651,7 +4626,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         await afterImmediate();
         await act(() => resolveText('world'));
         pipe(writable);
@@ -4689,7 +4664,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         await afterImmediate();
         await act(() => resolveText('world'));
         pipe(writable);
@@ -4738,7 +4713,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         await afterImmediate();
         await act(() => resolveText('world'));
         pipe(writable);
@@ -4823,12 +4798,13 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         await afterImmediate();
         await act(() => resolveText('world'));
         pipe(writable);
       });
 
+      // strip inserted external runtime
       expect(container.innerHTML).toEqual(
         '<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
       );
@@ -4879,7 +4855,7 @@ describe('ReactDOMFizzServer', () => {
 
       prepareJSDOMForTitle();
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
@@ -4903,7 +4879,7 @@ describe('ReactDOMFizzServer', () => {
 
       prepareJSDOMForTitle();
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
@@ -4941,7 +4917,7 @@ describe('ReactDOMFizzServer', () => {
         prepareJSDOMForTitle();
 
         await act(async () => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+          const {pipe} = renderToPipeableStream(<App />);
           pipe(writable);
         });
         if (__DEV__) {
@@ -5025,7 +5001,7 @@ describe('ReactDOMFizzServer', () => {
         prepareJSDOMForTitle();
 
         await act(async () => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+          const {pipe} = renderToPipeableStream(<App />);
           pipe(writable);
         });
         if (__DEV__) {
@@ -5086,7 +5062,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
 
@@ -5146,7 +5122,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
@@ -5185,7 +5161,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
 
@@ -5254,7 +5230,7 @@ describe('ReactDOMFizzServer', () => {
 
       const reportedServerErrors = [];
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+        const {pipe} = renderToPipeableStream(<App />, {
           onError(error) {
             reportedServerErrors.push(error);
           },
@@ -5315,7 +5291,7 @@ describe('ReactDOMFizzServer', () => {
       }
 
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getVisibleChildren(container)).toEqual('Hi');
@@ -5342,7 +5318,7 @@ describe('ReactDOMFizzServer', () => {
         );
       }
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getVisibleChildren(container)).toEqual(<button>0</button>);
@@ -5373,7 +5349,7 @@ describe('ReactDOMFizzServer', () => {
       let caughtError;
       try {
         await act(async () => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
+          const {pipe} = renderToPipeableStream(<App />, {
             onError(e) {
               reportedServerErrors.push(e);
             },
@@ -5402,7 +5378,7 @@ describe('ReactDOMFizzServer', () => {
         }
       }
       await act(async () => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getVisibleChildren(container)).toEqual(<div />);

filename: packages/react-dom/src/__tests__/ReactDOMFloat-test.js
patch: @@ -8,6 +8,7 @@
  */
 
 'use strict';
+import {replaceScriptsAndMove, mergeOptions} from '../test-utils/FizzTestUtils';
 
 let JSDOM;
 let Stream;
@@ -25,6 +26,7 @@ let container;
 let buffer = '';
 let hasErrored = false;
 let fatalError = undefined;
+const renderOptions = {};
 
 describe('ReactDOMFloat', () => {
   beforeEach(() => {
@@ -100,17 +102,7 @@ describe('ReactDOMFloat', () => {
       container.nodeName === '#document' ? container.body : container;
     while (fakeBody.firstChild) {
       const node = fakeBody.firstChild;
-      if (
-        node.nodeName === 'SCRIPT' &&
-        (CSPnonce === null || node.getAttribute('nonce') === CSPnonce)
-      ) {
-        const script = document.createElement('script');
-        script.textContent = node.textContent;
-        fakeBody.removeChild(node);
-        parent.appendChild(script);
-      } else {
-        parent.appendChild(node);
-      }
+      await replaceScriptsAndMove(document.defaultView, CSPnonce, node, parent);
     }
   }
 
@@ -135,6 +127,7 @@ describe('ReactDOMFloat', () => {
     document = jsdom.window.document;
     container = document;
     buffer = '';
+    await replaceScriptsAndMove(jsdom.window, null, document.documentElement);
   }
 
   function getMeaningfulChildren(element) {
@@ -148,6 +141,8 @@ describe('ReactDOMFloat', () => {
           node.hasAttribute('data-meaningful') ||
           (node.tagName === 'SCRIPT' &&
             node.hasAttribute('src') &&
+            node.getAttribute('src') !==
+              renderOptions.unstable_externalRuntimeSrc &&
             node.hasAttribute('async')) ||
           (node.tagName !== 'SCRIPT' &&
             node.tagName !== 'TEMPLATE' &&
@@ -235,6 +230,14 @@ describe('ReactDOMFloat', () => {
     return readText(text);
   }
 
+  function renderToPipeableStream(jsx, options) {
+    // Merge options with renderOptions, which may contain featureFlag specific behavior
+    return ReactDOMFizzServer.renderToPipeableStream(
+      jsx,
+      mergeOptions(options, renderOptions),
+    );
+  }
+
   // @gate enableFloat
   it('can render resources before singletons', async () => {
     const root = ReactDOMClient.createRoot(document);
@@ -569,7 +572,7 @@ describe('ReactDOMFloat', () => {
     });
 
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <>
           <title>foo</title>
           <html>
@@ -635,7 +638,7 @@ describe('ReactDOMFloat', () => {
         return 'foo';
       }
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -786,7 +789,7 @@ describe('ReactDOMFloat', () => {
       }
 
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<ServerApp />);
+        const {pipe} = renderToPipeableStream(<ServerApp />);
         pipe(writable);
       });
       expect(getMeaningfulChildren(document)).toEqual(
@@ -846,7 +849,7 @@ describe('ReactDOMFloat', () => {
         return 'foo';
       }
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -877,7 +880,7 @@ describe('ReactDOMFloat', () => {
         return 'foo';
       }
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -996,7 +999,7 @@ describe('ReactDOMFloat', () => {
         );
       }
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <App srcs={['server', 'shared']} />,
         );
         pipe(writable);
@@ -1148,7 +1151,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('supports preconnects, prefetc-dns, and arbitrary other link types', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -1259,7 +1262,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can render <base> as a Resource', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -1317,7 +1320,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can render icons and apple-touch-icons as Resources', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <html>
               <head />
@@ -1371,7 +1374,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can hydrate the right instances for deeply nested structured metas', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <html>
               <head />
@@ -1463,7 +1466,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can insert meta tags in the expected location', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <html>
               <head />
@@ -1615,7 +1618,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can render meta tags with og properties with structured data', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <html>
               <head />
@@ -1778,7 +1781,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can render meta tags as resources', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <html>
               <head />
@@ -1843,7 +1846,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('can rendering title tags anywhere in the tree', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <title>before</title>
             <>
@@ -1918,7 +1921,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('prepends new titles on the client so newer ones override older ones, including orphaned server rendered titles', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head>
               <title>server</title>
@@ -2032,7 +2035,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat && enableHostSingletons && (enableClientRenderFallbackOnTextMismatch || !__DEV__)
     it('can render a title before a singleton even if that singleton clears its contents', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <>
             <title>foo</title>
             <html>
@@ -2097,7 +2100,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('treats link rel stylesheet elements as a style resource when it includes a precedence when server rendering', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2150,7 +2153,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('treats link rel stylesheet elements as a style resource when it includes a precedence when hydrating', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2188,7 +2191,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('preloads stylesheets without a precedence prop when server rendering', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2216,7 +2219,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('hoists style resources to the correct precedence', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2311,7 +2314,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat && enableHostSingletons && enableClientRenderFallbackOnTextMismatch
     it('retains styles even when a new html, head, and/body mount', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2363,7 +2366,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat && !enableHostSingletons
     it('retains styles even when a new html, head, and/body mount - without HostSingleton', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2468,7 +2471,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('treats async scripts without onLoad or onError as Resources', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -2553,7 +2556,7 @@ describe('ReactDOMFloat', () => {
       );
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+      const {pipe} = renderToPipeableStream(<App />);
       pipe(writable);
     });
 
@@ -2636,7 +2639,7 @@ describe('ReactDOMFloat', () => {
   // @gate enableFloat
   it('treats stylesheet links with a precedence as a resource', async () => {
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -2689,7 +2692,7 @@ describe('ReactDOMFloat', () => {
     }
 
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -2745,7 +2748,7 @@ describe('ReactDOMFloat', () => {
       ReactDOM.preinit('preset', {as: 'style', precedence: 'preset'});
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3109,7 +3112,7 @@ describe('ReactDOMFloat', () => {
   // @gate enableFloat
   it('normalizes style resource precedence for all boundaries inlined as part of the shell flush', async () => {
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3199,7 +3202,7 @@ describe('ReactDOMFloat', () => {
   // @gate enableFloat
   it('style resources are inserted according to precedence order on the client', async () => {
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3328,7 +3331,7 @@ describe('ReactDOMFloat', () => {
       return null;
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3406,7 +3409,7 @@ describe('ReactDOMFloat', () => {
       return children;
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3529,7 +3532,7 @@ describe('ReactDOMFloat', () => {
       return children;
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3761,7 +3764,7 @@ describe('ReactDOMFloat', () => {
     }
     try {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
+        const {pipe} = renderToPipeableStream(<App />);
         pipe(writable);
       });
       expect(getMeaningfulChildren(document)).toEqual(
@@ -3869,7 +3872,7 @@ describe('ReactDOMFloat', () => {
       return children;
     }
     await actIntoEmptyDocument(() => {
-      const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+      const {pipe} = renderToPipeableStream(
         <html>
           <head />
           <body>
@@ -3998,7 +4001,7 @@ describe('ReactDOMFloat', () => {
     };
     try {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -4082,7 +4085,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <Component scenarios={scenarios} />
@@ -4719,7 +4722,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <link
@@ -4832,7 +4835,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <link
@@ -4945,7 +4948,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <link
@@ -5056,7 +5059,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <script src="foo" async={true} data-foo="a current value" />
@@ -5106,7 +5109,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <link
@@ -5201,7 +5204,7 @@ describe('ReactDOMFloat', () => {
       };
       try {
         await actIntoEmptyDocument(() => {
-          const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+          const {pipe} = renderToPipeableStream(
             <html>
               <head>
                 <link
@@ -5262,7 +5265,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('escapes hrefs when selecting matching elements in the document when rendering Resources', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>
@@ -5324,7 +5327,7 @@ describe('ReactDOMFloat', () => {
     // @gate enableFloat
     it('escapes hrefs when selecting matching elements in the document when using preload and preinit', async () => {
       await actIntoEmptyDocument(() => {
-        const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+        const {pipe} = renderToPipeableStream(
           <html>
             <head />
             <body>

filename: packages/react-dom/src/server/ReactDOMServerExternalRuntime.js
patch: @@ -1,10 +1,17 @@
-// TODO: Add Flow types
+/**
+ * This file is compiled to a standalone browser script by rollup and loaded by Fizz
+ *  clients. Therefore, it should be fast and not have many external dependencies.
+ * @flow
+ */
+
+// Imports are resolved statically by the closure compiler in release bundles
+// and by rollup in jest unit tests
 import {
   clientRenderBoundary,
   completeBoundaryWithStyles,
   completeBoundary,
   completeSegment,
-} from 'react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet';
+} from '../../../react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet';
 
 // Intentionally does nothing. Implementation will be added in future PR.
 // eslint-disable-next-line no-unused-vars

filename: packages/react-dom/src/test-utils/FizzTestUtils.js
patch: @@ -0,0 +1,123 @@
+/**
+ * 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
+ */
+'use strict';
+
+import * as tmp from 'tmp';
+import * as fs from 'fs';
+import replace from 'rollup-plugin-replace';
+import {rollup} from 'rollup';
+
+const rollupCache: Map<string, string | null> = new Map();
+
+// Utility function to read and bundle a standalone browser script
+async function getRollupResult(scriptSrc: string): Promise<string | null> {
+  const cachedResult = rollupCache.get(scriptSrc);
+  if (cachedResult !== undefined) {
+    return cachedResult;
+  }
+  let tmpFile;
+  try {
+    tmpFile = tmp.fileSync();
+    const rollupConfig = {
+      input: require.resolve(scriptSrc),
+      onwarn: console.warn,
+      plugins: [replace({__DEV__: 'true'})],
+      output: {
+        externalLiveBindings: false,
+        freeze: false,
+        interop: false,
+        esModule: false,
+      },
+    };
+    const outputConfig = {
+      file: tmpFile.name,
+      format: 'iife',
+    };
+    const bundle = await rollup(rollupConfig);
+    await bundle.write(outputConfig);
+    const bundleBuffer = Buffer.alloc(4096);
+    let bundleStr = '';
+    while (true) {
+      // $FlowFixMe[incompatible-call]
+      const bytes = fs.readSync(tmpFile.fd, bundleBuffer);
+      if (bytes <= 0) {
+        break;
+      }
+      bundleStr += bundleBuffer.slice(0, bytes).toString();
+    }
+    rollupCache.set(scriptSrc, bundleStr);
+    return bundleStr;
+  } catch (e) {
+    rollupCache.set(scriptSrc, null);
+    return null;
+  } finally {
+    if (tmpFile) {
+      tmpFile.removeCallback();
+    }
+  }
+}
+
+// Utility function to process received HTML nodes and execute
+//  embedded scripts by:
+//  1. Matching nonce attributes and moving node into an existing
+//      parent container (if passed)
+//  2. Resolving scripts with sources
+async function replaceScriptsAndMove(
+  window: any,
+  CSPnonce: string | null,
+  node: Node,
+  parent: Node | null,
+) {
+  if (
+    node.nodeType === 1 &&
+    (node.nodeName === 'SCRIPT' || node.nodeName === 'script')
+  ) {
+    // $FlowFixMe[incompatible-cast]
+    const element = (node: HTMLElement);
+    const script = window.document.createElement('SCRIPT');
+    const scriptSrc = element.getAttribute('src');
+    if (scriptSrc) {
+      const rollupOutput = await getRollupResult(scriptSrc);
+      if (rollupOutput) {
+        // Manually call eval(...) here, since changing the HTML text content
+        //  may interfere with hydration
+        window.eval(rollupOutput);
+      }
+      for (let i = 0; i < element.attributes.length; i++) {
+        const attr = element.attributes.item(i);
+        script.setAttribute(attr.name, attr.value);
+      }
+    } else if (element === null || element.getAttribute('nonce') === CSPnonce) {
+      script.textContent = node.textContent;
+    }
+    if (parent) {
+      element.parentNode?.removeChild(element);
+      parent.appendChild(script);
+    } else {
+      element.parentNode?.replaceChild(script, element);
+    }
+  } else {
+    for (let i = 0; i < node.childNodes.length; i++) {
+      const inner = node.childNodes[i];
+      await replaceScriptsAndMove(window, CSPnonce, inner, null);
+    }
+    if (parent != null) {
+      parent.appendChild(node);
+    }
+  }
+}
+
+function mergeOptions(options: Object, defaultOptions: Object): Object {
+  return {
+    ...defaultOptions,
+    ...options,
+  };
+}
+
+export {replaceScriptsAndMove, mergeOptions};

filename: packages/shared/ReactFeatureFlags.js
patch: @@ -128,6 +128,8 @@ export const enableUseMemoCacheHook = __EXPERIMENTAL__;
 export const enableUseEventHook = __EXPERIMENTAL__;
 
 // Test in www before enabling in open source.
+// Enables DOM-server to stream its instruction set as data-attributes
+// (handled with an MutationObserver) instead of inline-scripts
 export const enableFizzExternalRuntime = false;
 
 // -----------------------------------------------------------------------------