Repository: facebook/react
Pull request: [Fizz] Always load the external runtime if one is provided

Because we now decided whether to outline in the flushing phase, when we're writing the preamble we don't yet know if we will make that decision so we don't know if it's safe to omit the external runtime.

However, if you are providing an external runtime it's probably a pretty safe bet you're streaming something dynamically that's likely to need it so we can always include it.

The main thing is that this makes it hard to test it because it affects our tests in ways it wouldn't otherwise so we have to add a bunch of conditions.

Changed files:
filename: packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
patch: @@ -5089,15 +5089,10 @@ export function writePreambleStart(
   destination: Destination,
   resumableState: ResumableState,
   renderState: RenderState,
-  willFlushAllSegments: boolean,
   skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
 ): void {
   // This function must be called exactly once on every request
-  if (
-    enableFizzExternalRuntime &&
-    !willFlushAllSegments &&
-    renderState.externalRuntimeScript
-  ) {
+  if (enableFizzExternalRuntime && renderState.externalRuntimeScript) {
     // If the root segment is incomplete due to suspended tasks
     // (e.g. willFlushAllSegments = false) and we are using data
     // streaming format, ensure the external runtime is sent.

filename: packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
patch: @@ -3587,6 +3587,9 @@ describe('ReactDOMFizzServer', () => {
       '<script type="importmap">' +
         JSON.stringify(importMap) +
         '</script><script async="" src="foo"></script>' +
+        (gate(flags => flags.shouldUseFizzExternalRuntime)
+          ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
+          : '') +
         '<link rel="expect" href="#«R»" blocking="render">',
     );
   });
@@ -4501,7 +4504,8 @@ describe('ReactDOMFizzServer', () => {
     expect(document.getElementsByTagName('script').length).toEqual(1);
   });
 
-  it('does not send the external runtime for static pages', async () => {
+  // @gate shouldUseFizzExternalRuntime
+  it('does (unfortunately) send the external runtime for static pages', async () => {
     await act(() => {
       const {pipe} = renderToPipeableStream(
         <html>
@@ -4515,11 +4519,11 @@ describe('ReactDOMFizzServer', () => {
     });
 
     // no scripts should be sent
-    expect(document.getElementsByTagName('script').length).toEqual(0);
+    expect(document.getElementsByTagName('script').length).toEqual(1);
 
     // the html should be as-is
     expect(document.documentElement.innerHTML).toEqual(
-      '<head><link rel="expect" href="#«R»" blocking="render"></head><body><p>hello world!</p><template id="«R»"></template></body>',
+      '<head><script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script><link rel="expect" href="#«R»" blocking="render"></head><body><p>hello world!</p><template id="«R»"></template></body>',
     );
   });
 
@@ -5317,7 +5321,9 @@ describe('ReactDOMFizzServer', () => {
       });
 
       expect(container.innerHTML).toEqual(
-        '<div>hello<b>world, <!-- -->Foo</b>!</div>',
+        (gate(flags => flags.shouldUseFizzExternalRuntime)
+          ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
+          : '') + '<div>hello<b>world, <!-- -->Foo</b>!</div>',
       );
       const errors = [];
       ReactDOMClient.hydrateRoot(container, <App name="Foo" />, {
@@ -5518,7 +5524,7 @@ describe('ReactDOMFizzServer', () => {
         pipe(writable);
       });
 
-      expect(container.firstElementChild.outerHTML).toEqual(
+      expect(container.lastElementChild.outerHTML).toEqual(
         '<div>hello<b>world<!-- --></b></div>',
       );
 
@@ -5556,7 +5562,7 @@ describe('ReactDOMFizzServer', () => {
         pipe(writable);
       });
 
-      expect(container.firstElementChild.outerHTML).toEqual(
+      expect(container.lastElementChild.outerHTML).toEqual(
         '<div>hello<b>world</b></div>',
       );
 
@@ -5696,7 +5702,10 @@ describe('ReactDOMFizzServer', () => {
       });
 
       expect(container.innerHTML).toEqual(
-        '<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
+        (gate(flags => flags.shouldUseFizzExternalRuntime)
+          ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
+          : '') +
+          '<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
       );
 
       const errors = [];
@@ -6499,7 +6508,11 @@ describe('ReactDOMFizzServer', () => {
     });
 
     expect(document.documentElement.outerHTML).toEqual(
-      '<html><head><link rel="expect" href="#«R»" blocking="render"></head><body><script>try { foo() } catch (e) {} ;</script><template id="«R»"></template></body></html>',
+      '<html><head>' +
+        (gate(flags => flags.shouldUseFizzExternalRuntime)
+          ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
+          : '') +
+        '<link rel="expect" href="#«R»" blocking="render"></head><body><script>try { foo() } catch (e) {} ;</script><template id="«R»"></template></body></html>',
     );
   });
 

filename: packages/react-dom/src/__tests__/ReactDOMFloat-test.js
patch: @@ -701,6 +701,9 @@ describe('ReactDOMFloat', () => {
     });
     expect(chunks).toEqual([
       '<!DOCTYPE html><html><head><script async="" src="foo"></script>' +
+        (gate(flags => flags.shouldUseFizzExternalRuntime)
+          ? '<script src="react-dom/unstable_server-external-runtime" async=""></script>'
+          : '') +
         '<link rel="expect" href="#«R»" blocking="render"/><title>foo</title></head>' +
         '<body>bar<template id="«R»"></template>',
       '</body></html>',

filename: packages/react-markup/src/ReactFizzConfigMarkup.js
patch: @@ -208,14 +208,12 @@ export function writePreambleStart(
   destination: Destination,
   resumableState: ResumableState,
   renderState: RenderState,
-  willFlushAllSegments: boolean,
   skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
 ): void {
   return writePreambleStartImpl(
     destination,
     resumableState,
     renderState,
-    willFlushAllSegments,
     true, // skipExpect
   );
 }

filename: packages/react-server/src/ReactFizzServer.js
patch: @@ -4835,14 +4835,7 @@ function flushPreamble(
   preambleSegments: Array<Array<Segment>>,
 ) {
   // The preamble is ready.
-  const willFlushAllSegments =
-    request.allPendingTasks === 0 && request.trackedPostpones === null;
-  writePreambleStart(
-    destination,
-    request.resumableState,
-    request.renderState,
-    willFlushAllSegments,
-  );
+  writePreambleStart(destination, request.resumableState, request.renderState);
   for (let i = 0; i < preambleSegments.length; i++) {
     const segments = preambleSegments[i];
     for (let j = 0; j < segments.length; j++) {