From 9bb7288fd2255822eba8dbd8b1399b133df072df Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 12 May 2025 17:11:28 -0400 Subject: [PATCH] Gate rel="expect" behind enableFizzBlockingRender --- .../src/server/ReactFizzConfigDOM.js | 38 +++++++++------- .../src/__tests__/ReactDOMFizzServer-test.js | 23 ++++++++-- .../ReactDOMFizzServerBrowser-test.js | 22 ++++++++-- .../__tests__/ReactDOMFizzServerEdge-test.js | 12 +++-- .../__tests__/ReactDOMFizzServerNode-test.js | 12 +++-- .../ReactDOMFizzStaticBrowser-test.js | 23 +++++++--- .../__tests__/ReactDOMFizzStaticNode-test.js | 12 +++-- .../src/__tests__/ReactDOMFloat-test.js | 10 ++++- .../src/__tests__/ReactDOMLegacyFloat-test.js | 11 ++++- .../src/__tests__/ReactRenderDocument-test.js | 44 +++++++++++++++---- .../src/__tests__/ReactFlightDOM-test.js | 22 ++++++++-- .../__tests__/ReactFlightDOMBrowser-test.js | 12 ++++- packages/shared/ReactFeatureFlags.js | 2 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + ...actFeatureFlags.test-renderer.native-fb.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 19 files changed, 194 insertions(+), 55 deletions(-) diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index a54a623bb39..85ec4ae7364 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -34,6 +34,7 @@ import {Children} from 'react'; import { enableFizzExternalRuntime, enableSrcObject, + enableFizzBlockingRender, } from 'shared/ReactFeatureFlags'; import type { @@ -4146,16 +4147,21 @@ export function writeCompletedRoot( // we need to track the paint time of the shell so we know how much to throttle the reveal. writeShellTimeInstruction(destination, resumableState, renderState); } - const preamble = renderState.preamble; - if (preamble.htmlChunks || preamble.headChunks) { - // If we rendered the whole document, then we emitted a rel="expect" that needs a - // matching target. Normally we use one of the bootstrap scripts for this but if - // there are none, then we need to emit a tag to complete the shell. - if ((resumableState.instructions & SentCompletedShellId) === NothingSent) { - writeChunk(destination, startChunkForTag('template')); - writeCompletedShellIdAttribute(destination, resumableState); - writeChunk(destination, endOfStartTag); - writeChunk(destination, endChunkForTag('template')); + if (enableFizzBlockingRender) { + const preamble = renderState.preamble; + if (preamble.htmlChunks || preamble.headChunks) { + // If we rendered the whole document, then we emitted a rel="expect" that needs a + // matching target. Normally we use one of the bootstrap scripts for this but if + // there are none, then we need to emit a tag to complete the shell. + if ( + (resumableState.instructions & SentCompletedShellId) === + NothingSent + ) { + writeChunk(destination, startChunkForTag('template')); + writeCompletedShellIdAttribute(destination, resumableState); + writeChunk(destination, endOfStartTag); + writeChunk(destination, endChunkForTag('template')); + } } } return writeBootstrap(destination, renderState); @@ -5040,11 +5046,13 @@ function writeBlockingRenderInstruction( resumableState: ResumableState, renderState: RenderState, ): void { - const idPrefix = resumableState.idPrefix; - const shellId = '\u00AB' + idPrefix + 'R\u00BB'; - writeChunk(destination, blockingRenderChunkStart); - writeChunk(destination, stringToChunk(escapeTextForBrowser(shellId))); - writeChunk(destination, blockingRenderChunkEnd); + if (enableFizzBlockingRender) { + const idPrefix = resumableState.idPrefix; + const shellId = '\u00AB' + idPrefix + 'R\u00BB'; + writeChunk(destination, blockingRenderChunkStart); + writeChunk(destination, stringToChunk(escapeTextForBrowser(shellId))); + writeChunk(destination, blockingRenderChunkEnd); + } } const completedShellIdAttributeStart = stringToPrecomputedChunk(' id="'); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 1be040bfe36..8bb3e2f4b74 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -3590,7 +3590,9 @@ describe('ReactDOMFizzServer', () => { (gate(flags => flags.shouldUseFizzExternalRuntime) ? '' : '') + - '', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); }); @@ -4523,7 +4525,15 @@ describe('ReactDOMFizzServer', () => { // the html should be as-is expect(document.documentElement.innerHTML).toEqual( - '

hello world!

', + '' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '

hello world!

' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); @@ -6512,7 +6522,14 @@ describe('ReactDOMFizzServer', () => { (gate(flags => flags.shouldUseFizzExternalRuntime) ? '' : '') + - '', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js index f5b01d24624..7c0aa14c260 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js @@ -84,9 +84,15 @@ describe('ReactDOMFizzServerBrowser', () => { ), ); const result = await readResult(stream); - expect(result).toMatchInlineSnapshot( - `"hello world"`, - ); + if (gate(flags => flags.enableFizzBlockingRender)) { + expect(result).toMatchInlineSnapshot( + `"hello world"`, + ); + } else { + expect(result).toMatchInlineSnapshot( + `"hello world"`, + ); + } }); it('should emit bootstrap script src at the end', async () => { @@ -529,7 +535,15 @@ describe('ReactDOMFizzServerBrowser', () => { const result = await readResult(stream); expect(result).toEqual( - 'foobar', + '' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + 'foobar' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerEdge-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerEdge-test.js index 1eefe1a4082..801baa9678a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerEdge-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerEdge-test.js @@ -71,8 +71,14 @@ describe('ReactDOMFizzServerEdge', () => { setTimeout(resolve, 1); }); - expect(result).toMatchInlineSnapshot( - `"
hello
"`, - ); + if (gate(flags => flags.enableFizzBlockingRender)) { + expect(result).toMatchInlineSnapshot( + `"
hello
"`, + ); + } else { + expect(result).toMatchInlineSnapshot( + `"
hello
"`, + ); + } }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js index 2704c243eba..d4f3486a1db 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js @@ -78,9 +78,15 @@ describe('ReactDOMFizzServerNode', () => { pipe(writable); }); // with Float, we emit empty heads if they are elided when rendering - expect(output.result).toMatchInlineSnapshot( - `"hello world"`, - ); + if (gate(flags => flags.enableFizzBlockingRender)) { + expect(output.result).toMatchInlineSnapshot( + `"hello world"`, + ); + } else { + expect(output.result).toMatchInlineSnapshot( + `"hello world"`, + ); + } }); it('should emit bootstrap script src at the end', async () => { diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js index 578b2bf916f..55a89bc525a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js @@ -195,9 +195,15 @@ describe('ReactDOMFizzStaticBrowser', () => { ), ); const prelude = await readContent(result.prelude); - expect(prelude).toMatchInlineSnapshot( - `"hello world"`, - ); + if (gate(flags => flags.enableFizzBlockingRender)) { + expect(prelude).toMatchInlineSnapshot( + `"hello world"`, + ); + } else { + expect(prelude).toMatchInlineSnapshot( + `"hello world"`, + ); + } }); it('should emit bootstrap script src at the end', async () => { @@ -1438,8 +1444,15 @@ describe('ReactDOMFizzStaticBrowser', () => { expect(await readContent(content)).toBe( '' + '' + - '' + - 'Hello', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '' + + 'Hello' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js index d9bd7c70db0..3aa7a70cb80 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js @@ -63,9 +63,15 @@ describe('ReactDOMFizzStaticNode', () => { , ); const prelude = await readContent(result.prelude); - expect(prelude).toMatchInlineSnapshot( - `"hello world"`, - ); + if (gate(flags => flags.enableFizzBlockingRender)) { + expect(prelude).toMatchInlineSnapshot( + `"hello world"`, + ); + } else { + expect(prelude).toMatchInlineSnapshot( + `"hello world"`, + ); + } }); // @gate experimental diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index b7f42bcf8db..2d3f1f0b8b6 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -704,8 +704,14 @@ describe('ReactDOMFloat', () => { (gate(flags => flags.shouldUseFizzExternalRuntime) ? '' : '') + - 'foo' + - 'bar', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + 'foo' + + 'bar' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), '', ]); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMLegacyFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMLegacyFloat-test.js index f2cabafc9f5..f6d868c8413 100644 --- a/packages/react-dom/src/__tests__/ReactDOMLegacyFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMLegacyFloat-test.js @@ -34,8 +34,15 @@ describe('ReactDOMFloat', () => { ); expect(result).toEqual( - '' + - 'title', + '' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + 'title' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); }); diff --git a/packages/react-dom/src/__tests__/ReactRenderDocument-test.js b/packages/react-dom/src/__tests__/ReactRenderDocument-test.js index 2b54bc90090..3501f1bd92a 100644 --- a/packages/react-dom/src/__tests__/ReactRenderDocument-test.js +++ b/packages/react-dom/src/__tests__/ReactRenderDocument-test.js @@ -78,14 +78,20 @@ describe('rendering React components at document', () => { root = ReactDOMClient.hydrateRoot(testDocument, ); }); expect(testDocument.body.innerHTML).toBe( - 'Hello world' + '', + 'Hello world' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); await act(() => { root.render(); }); expect(testDocument.body.innerHTML).toBe( - 'Hello moon' + '', + 'Hello moon' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); expect(body === testDocument.body).toBe(true); @@ -112,7 +118,10 @@ describe('rendering React components at document', () => { root = ReactDOMClient.hydrateRoot(testDocument, ); }); expect(testDocument.body.innerHTML).toBe( - 'Hello world' + '', + 'Hello world' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); const originalDocEl = testDocument.documentElement; @@ -124,9 +133,15 @@ describe('rendering React components at document', () => { expect(testDocument.firstChild).toBe(originalDocEl); expect(testDocument.head).toBe(originalHead); expect(testDocument.body).toBe(originalBody); - expect(originalBody.innerHTML).toBe(''); + expect(originalBody.innerHTML).toBe( + gate(flags => flags.enableFizzBlockingRender) + ? '' + : '', + ); expect(originalHead.innerHTML).toBe( - '', + gate(flags => flags.enableFizzBlockingRender) + ? '' + : '', ); }); @@ -166,7 +181,10 @@ describe('rendering React components at document', () => { }); expect(testDocument.body.innerHTML).toBe( - 'Hello world' + '', + 'Hello world' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); await act(() => { @@ -174,7 +192,9 @@ describe('rendering React components at document', () => { }); expect(testDocument.body.innerHTML).toBe( - '' + 'Goodbye world', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + 'Goodbye world', ); }); @@ -205,7 +225,10 @@ describe('rendering React components at document', () => { }); expect(testDocument.body.innerHTML).toBe( - 'Hello world' + '', + 'Hello world' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); }); @@ -341,7 +364,10 @@ describe('rendering React components at document', () => { expect(testDocument.body.innerHTML).toBe( favorSafetyOverHydrationPerf ? 'Hello world' - : 'Goodbye world', + : 'Goodbye world' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : ''), ); }); diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 80562624eb1..05a6a227c2b 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -1921,14 +1921,28 @@ describe('ReactFlightDOM', () => { expect(content1).toEqual( '' + '' + - '' + - '

hello world

', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '' + + '

hello world

' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); expect(content2).toEqual( '' + '' + - '' + - '

hello world

', + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '' + + '

hello world

' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 4313c379b70..9d668ea3c3e 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -1899,8 +1899,16 @@ describe('ReactFlightDOMBrowser', () => { } expect(content).toEqual( - '' + - '

hello world

', + '' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '' + + '

hello world

' + + (gate(flags => flags.enableFizzBlockingRender) + ? '' + : '') + + '', ); }); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 217b095a29a..3c3f7292b2a 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -98,6 +98,8 @@ export const enableScrollEndPolyfill = __EXPERIMENTAL__; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = __EXPERIMENTAL__; // rel="expect" + export const enableSrcObject = __EXPERIMENTAL__; export const enableHydrationChangeEvent = __EXPERIMENTAL__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 8383c0dc351..f29c701ff25 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -82,6 +82,7 @@ export const enableViewTransition = false; export const enableGestureTransition = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = true; export const enableSrcObject = false; export const enableHydrationChangeEvent = true; export const enableDefaultTransitionIndicator = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index acc04dfb055..1591162982b 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -72,6 +72,7 @@ export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = true; export const enableSrcObject = false; export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 4e51e7260e9..2ddb4bf8e9e 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -72,6 +72,7 @@ export const enableFastAddPropertiesInDiffing = true; export const enableLazyPublicInstanceInFabric = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = true; export const enableSrcObject = false; export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index c9bd058f1fe..295e7462624 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -69,6 +69,7 @@ export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = true; export const enableSrcObject = false; export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index baceb1e6b72..12c6283da26 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -83,6 +83,7 @@ export const enableFastAddPropertiesInDiffing = false; export const enableLazyPublicInstanceInFabric = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = true; export const enableSrcObject = false; export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 5ecd0073edd..ccbd239d85e 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -112,6 +112,7 @@ export const enableLazyPublicInstanceInFabric = false; export const enableGestureTransition = false; export const enableSuspenseyImages = false; +export const enableFizzBlockingRender = true; export const enableSrcObject = false; export const enableHydrationChangeEvent = false; export const enableDefaultTransitionIndicator = false;