[Fizz] Always load the external runtime if one is provided (#33091)
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.
Sebastian Markbåge committed
May 1, 2025 at 18:14 UTC
f739642745577a8e4dcb9753836ac3589b9c590a
5 files changed
+26
-24
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+1
-6
@@ -5089,15 +5089,10 @@ export function writePreambleStart(
5089
destination: Destination,
5090
resumableState: ResumableState,
5091
renderState: RenderState,
5092
- willFlushAllSegments: boolean,
5092
skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
5093
): void {
5094
// This function must be called exactly once on every request
5096
- if (
5097
- enableFizzExternalRuntime &&
5098
- !willFlushAllSegments &&
5099
- renderState.externalRuntimeScript
5100
- ) {
5095
+ if (enableFizzExternalRuntime && renderState.externalRuntimeScript) {
5096
// If the root segment is incomplete due to suspended tasks
5097
// (e.g. willFlushAllSegments = false) and we are using data
5098
// streaming format, ensure the external runtime is sent.
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+21
-8
@@ -3587,6 +3587,9 @@ describe('ReactDOMFizzServer', () => {
3587
'<script type="importmap">' +
3588
JSON.stringify(importMap) +
3589
'</script><script async="" src="foo"></script>' +
3590
+ (gate(flags => flags.shouldUseFizzExternalRuntime)
3591
+ ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
3592
+ : '') +
3593
'<link rel="expect" href="#«R»" blocking="render">',
3594
);
3595
});
@@ -4501,7 +4504,8 @@ describe('ReactDOMFizzServer', () => {
4504
expect(document.getElementsByTagName('script').length).toEqual(1);
4505
});
4506
4504
- it('does not send the external runtime for static pages', async () => {
4507
+ // @gate shouldUseFizzExternalRuntime
4508
+ it('does (unfortunately) send the external runtime for static pages', async () => {
4509
await act(() => {
4510
const {pipe} = renderToPipeableStream(
4511
<html>
@@ -4515,11 +4519,11 @@ describe('ReactDOMFizzServer', () => {
4519
});
4520
4521
// no scripts should be sent
4518
- expect(document.getElementsByTagName('script').length).toEqual(0);
4522
+ expect(document.getElementsByTagName('script').length).toEqual(1);
4523
4524
// the html should be as-is
4525
expect(document.documentElement.innerHTML).toEqual(
4522
- '<head><link rel="expect" href="#«R»" blocking="render"></head><body><p>hello world!</p><template id="«R»"></template></body>',
4526
+ '<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>',
4527
);
4528
});
4529
@@ -5317,7 +5321,9 @@ describe('ReactDOMFizzServer', () => {
5321
});
5322
5323
expect(container.innerHTML).toEqual(
5320
- '<div>hello<b>world, <!-- -->Foo</b>!</div>',
5324
+ (gate(flags => flags.shouldUseFizzExternalRuntime)
5325
+ ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
5326
+ : '') + '<div>hello<b>world, <!-- -->Foo</b>!</div>',
5327
);
5328
const errors = [];
5329
ReactDOMClient.hydrateRoot(container, <App name="Foo" />, {
@@ -5518,7 +5524,7 @@ describe('ReactDOMFizzServer', () => {
5524
pipe(writable);
5525
});
5526
5521
- expect(container.firstElementChild.outerHTML).toEqual(
5527
+ expect(container.lastElementChild.outerHTML).toEqual(
5528
'<div>hello<b>world<!-- --></b></div>',
5529
);
5530
@@ -5556,7 +5562,7 @@ describe('ReactDOMFizzServer', () => {
5562
pipe(writable);
5563
});
5564
5559
- expect(container.firstElementChild.outerHTML).toEqual(
5565
+ expect(container.lastElementChild.outerHTML).toEqual(
5566
'<div>hello<b>world</b></div>',
5567
);
5568
@@ -5696,7 +5702,10 @@ describe('ReactDOMFizzServer', () => {
5702
});
5703
5704
expect(container.innerHTML).toEqual(
5699
- '<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
5705
+ (gate(flags => flags.shouldUseFizzExternalRuntime)
5706
+ ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
5707
+ : '') +
5708
+ '<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
5709
);
5710
5711
const errors = [];
@@ -6499,7 +6508,11 @@ describe('ReactDOMFizzServer', () => {
6508
});
6509
6510
expect(document.documentElement.outerHTML).toEqual(
6502
- '<html><head><link rel="expect" href="#«R»" blocking="render"></head><body><script>try { foo() } catch (e) {} ;</script><template id="«R»"></template></body></html>',
6511
+ '<html><head>' +
6512
+ (gate(flags => flags.shouldUseFizzExternalRuntime)
6513
+ ? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
6514
+ : '') +
6515
+ '<link rel="expect" href="#«R»" blocking="render"></head><body><script>try { foo() } catch (e) {} ;</script><template id="«R»"></template></body></html>',
6516
);
6517
});
6518
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+3
@@ -701,6 +701,9 @@ describe('ReactDOMFloat', () => {
701
});
702
expect(chunks).toEqual([
703
'<!DOCTYPE html><html><head><script async="" src="foo"></script>' +
704
+ (gate(flags => flags.shouldUseFizzExternalRuntime)
705
+ ? '<script src="react-dom/unstable_server-external-runtime" async=""></script>'
706
+ : '') +
707
'<link rel="expect" href="#«R»" blocking="render"/><title>foo</title></head>' +
708
'<body>bar<template id="«R»"></template>',
709
'</body></html>',
packages/react-markup/src/ReactFizzConfigMarkup.js
-2
@@ -208,14 +208,12 @@ export function writePreambleStart(
208
destination: Destination,
209
resumableState: ResumableState,
210
renderState: RenderState,
211
- willFlushAllSegments: boolean,
211
skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
212
): void {
213
return writePreambleStartImpl(
214
destination,
215
resumableState,
216
renderState,
218
- willFlushAllSegments,
217
true, // skipExpect
218
);
219
}
packages/react-server/src/ReactFizzServer.js
+1
-8
@@ -4835,14 +4835,7 @@ function flushPreamble(
4835
preambleSegments: Array<Array<Segment>>,
4836
) {
4837
// The preamble is ready.
4838
- const willFlushAllSegments =
4839
- request.allPendingTasks === 0 && request.trackedPostpones === null;
4840
- writePreambleStart(
4841
- destination,
4842
- request.resumableState,
4843
- request.renderState,
4844
- willFlushAllSegments,
4845
- );
4838
+ writePreambleStart(destination, request.resumableState, request.renderState);
4839
for (let i = 0; i < preambleSegments.length; i++) {
4840
const segments = preambleSegments[i];
4841
for (let j = 0; j < segments.length; j++) {