Repository: facebook/react
Pull request: [Flight] Bypass caches in Flight fixture if requested

When the "Disable cache" checkbox is checked in the dev tools, we should bypass the caching of the 3rd-party component in the Flight fixture. This allows us to quickly compare the different output (with and without caching) in React's custom performance tracks.

Changed files:
filename: fixtures/flight/server/global.js
patch: @@ -101,6 +101,9 @@ async function renderApp(req, res, next) {
   } else if (req.get('Content-type')) {
     proxiedHeaders['Content-type'] = req.get('Content-type');
   }
+  if (req.headers['cache-control']) {
+    proxiedHeaders['Cache-Control'] = req.get('cache-control');
+  }
 
   const requestsPrerender = req.path === '/prerender';
 

filename: fixtures/flight/server/region.js
patch: @@ -50,7 +50,7 @@ const {readFile} = require('fs').promises;
 
 const React = require('react');
 
-async function renderApp(res, returnValue, formState) {
+async function renderApp(res, returnValue, formState, noCache) {
   const {renderToPipeableStream} = await import(
     'react-server-dom-webpack/server'
   );
@@ -97,15 +97,15 @@ async function renderApp(res, returnValue, formState) {
         key: filename,
       })
     ),
-    React.createElement(App)
+    React.createElement(App, {noCache})
   );
   // For client-invoked server actions we refresh the tree and return a return value.
   const payload = {root, returnValue, formState};
   const {pipe} = renderToPipeableStream(payload, moduleMap);
   pipe(res);
 }
 
-async function prerenderApp(res, returnValue, formState) {
+async function prerenderApp(res, returnValue, formState, noCache) {
   const {unstable_prerenderToNodeStream: prerenderToNodeStream} = await import(
     'react-server-dom-webpack/static'
   );
@@ -152,7 +152,7 @@ async function prerenderApp(res, returnValue, formState) {
         key: filename,
       })
     ),
-    React.createElement(App, {prerender: true})
+    React.createElement(App, {prerender: true, noCache})
   );
   // For client-invoked server actions we refresh the tree and return a return value.
   const payload = {root, returnValue, formState};
@@ -161,14 +161,17 @@ async function prerenderApp(res, returnValue, formState) {
 }
 
 app.get('/', async function (req, res) {
+  const noCache = req.get('cache-control') === 'no-cache';
+
   if ('prerender' in req.query) {
-    await prerenderApp(res, null, null);
+    await prerenderApp(res, null, null, noCache);
   } else {
-    await renderApp(res, null, null);
+    await renderApp(res, null, null, noCache);
   }
 });
 
 app.post('/', bodyParser.text(), async function (req, res) {
+  const noCache = req.headers['cache-control'] === 'no-cache';
   const {decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState} =
     await import('react-server-dom-webpack/server');
   const serverReference = req.get('rsc-action');
@@ -201,7 +204,7 @@ app.post('/', bodyParser.text(), async function (req, res) {
       // We handle the error on the client
     }
     // Refresh the client and return the value
-    renderApp(res, result, null);
+    renderApp(res, result, null, noCache);
   } else {
     // This is the progressive enhancement case
     const UndiciRequest = require('undici').Request;
@@ -217,11 +220,11 @@ app.post('/', bodyParser.text(), async function (req, res) {
       // Wait for any mutations
       const result = await action();
       const formState = decodeFormState(result, formData);
-      renderApp(res, null, formState);
+      renderApp(res, null, formState, noCache);
     } catch (x) {
       const {setServerState} = await import('../src/ServerState.js');
       setServerState('Error: ' + x.message);
-      renderApp(res, null, null);
+      renderApp(res, null, null, noCache);
     }
   }
 });

filename: fixtures/flight/src/App.js
patch: @@ -47,8 +47,8 @@ async function ThirdPartyComponent() {
 // Using Web streams for tee'ing convenience here.
 let cachedThirdPartyReadableWeb;
 
-function fetchThirdParty(Component) {
-  if (cachedThirdPartyReadableWeb) {
+function fetchThirdParty(noCache) {
+  if (cachedThirdPartyReadableWeb && !noCache) {
     const [readableWeb1, readableWeb2] = cachedThirdPartyReadableWeb.tee();
     cachedThirdPartyReadableWeb = readableWeb1;
 
@@ -79,16 +79,16 @@ function fetchThirdParty(Component) {
   return result;
 }
 
-async function ServerComponent() {
+async function ServerComponent({noCache}) {
   await new Promise(resolve => setTimeout(() => resolve('deferred text'), 50));
-  return await fetchThirdParty();
+  return fetchThirdParty(noCache);
 }
 
-export default async function App({prerender}) {
+export default async function App({prerender, noCache}) {
   const res = await fetch('http://localhost:3001/todos');
   const todos = await res.json();
 
-  const dedupedChild = <ServerComponent />;
+  const dedupedChild = <ServerComponent noCache={noCache} />;
   const message = getServerState();
   return (
     <html lang="en">