[ServerRenderer] Setup for adding data attributes streaming format (#25567)
mofeiZ committed
Nov 1, 2022 at 12:04 UTC
6883d79445f38e2cdabf6fe1fa04cb44c2644c1c
5 files changed
+291
-180
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+99
-123
@@ -8,6 +8,7 @@
8
*/
9
10
'use strict';
11
+import {replaceScriptsAndMove, mergeOptions} from '../test-utils/FizzTestUtils';
12
13
let JSDOM;
14
let Stream;
@@ -30,6 +31,7 @@ let container;
31
let buffer = '';
32
let hasErrored = false;
33
let fatalError = undefined;
34
+const renderOptions = {};
35
36
describe('ReactDOMFizzServer', () => {
37
beforeEach(() => {
@@ -134,17 +136,7 @@ describe('ReactDOMFizzServer', () => {
136
container.nodeName === '#document' ? container.body : container;
137
while (fakeBody.firstChild) {
138
const node = fakeBody.firstChild;
137
- if (
138
- node.nodeName === 'SCRIPT' &&
139
- (CSPnonce === null || node.getAttribute('nonce') === CSPnonce)
140
- ) {
141
- const script = document.createElement('script');
142
- script.textContent = node.textContent;
143
- fakeBody.removeChild(node);
144
- parent.appendChild(script);
145
- } else {
146
- parent.appendChild(node);
147
- }
139
+ await replaceScriptsAndMove(window, CSPnonce, node, parent);
140
}
141
}
142
@@ -170,6 +162,7 @@ describe('ReactDOMFizzServer', () => {
162
document = jsdom.window.document;
163
container = document;
164
buffer = '';
165
+ await replaceScriptsAndMove(window, CSPnonce, document.documentElement);
166
}
167
168
function getVisibleChildren(element) {
@@ -179,6 +172,7 @@ describe('ReactDOMFizzServer', () => {
172
if (node.nodeType === 1) {
173
if (
174
node.tagName !== 'SCRIPT' &&
175
+ node.tagName !== 'script' &&
176
node.tagName !== 'TEMPLATE' &&
177
node.tagName !== 'template' &&
178
!node.hasAttribute('hidden') &&
@@ -288,6 +282,13 @@ describe('ReactDOMFizzServer', () => {
282
const As = as;
283
return <As>{readText(text)}</As>;
284
}
285
+ function renderToPipeableStream(jsx, options) {
286
+ // Merge options with renderOptions, which may contain featureFlag specific behavior
287
+ return ReactDOMFizzServer.renderToPipeableStream(
288
+ jsx,
289
+ mergeOptions(options, renderOptions),
290
+ );
291
+ }
292
293
it('should asynchronously load a lazy component', async () => {
294
let resolveA;
@@ -313,7 +314,7 @@ describe('ReactDOMFizzServer', () => {
314
};
315
316
await act(async () => {
316
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
317
+ const {pipe} = renderToPipeableStream(
318
<div>
319
<div>
320
<Suspense fallback={<Text text="Loading..." />}>
@@ -379,7 +380,7 @@ describe('ReactDOMFizzServer', () => {
380
// Server-side
381
const [App, resolve] = makeApp();
382
await act(async () => {
382
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
383
+ const {pipe} = renderToPipeableStream(<App />);
384
pipe(writable);
385
});
386
expect(getVisibleChildren(container)).toEqual(
@@ -432,7 +433,7 @@ describe('ReactDOMFizzServer', () => {
433
});
434
435
await act(async () => {
435
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
436
+ const {pipe} = renderToPipeableStream(
437
<div>
438
<Suspense fallback={<Text text="Loading..." />}>
439
<Lazy text="Hello" />
@@ -492,13 +493,10 @@ describe('ReactDOMFizzServer', () => {
493
loggedErrors.length = 0;
494
495
await act(async () => {
495
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
496
- <App isClient={false} />,
497
- {
498
- bootstrapScriptContent: '__INIT__();',
499
- onError,
500
- },
501
- );
496
+ const {pipe} = renderToPipeableStream(<App isClient={false} />, {
497
+ bootstrapScriptContent: '__INIT__();',
498
+ onError,
499
+ });
500
pipe(writable);
501
});
502
expect(loggedErrors).toEqual([]);
@@ -554,7 +552,7 @@ describe('ReactDOMFizzServer', () => {
552
});
553
554
await act(async () => {
557
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
555
+ const {pipe} = renderToPipeableStream(
556
<div>
557
<Suspense fallback={<Text text="Loading..." />}>
558
{lazyElement}
@@ -567,7 +565,7 @@ describe('ReactDOMFizzServer', () => {
565
// Because there is no content inside the Suspense boundary that could've
566
// been written, we expect to not see any additional partial data flushed
567
// yet.
570
- expect(container.firstChild.nextSibling).toBe(null);
568
+ expect(container.childNodes.length).toBe(1);
569
await act(async () => {
570
resolveElement({default: <Text text="Hello" />});
571
});
@@ -603,7 +601,7 @@ describe('ReactDOMFizzServer', () => {
601
}
602
603
await act(async () => {
606
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
604
+ const {pipe} = renderToPipeableStream(
605
<App isClient={false} />,
606
607
{
@@ -699,7 +697,7 @@ describe('ReactDOMFizzServer', () => {
697
loggedErrors.length = 0;
698
699
await act(async () => {
702
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
700
+ const {pipe} = renderToPipeableStream(
701
<App />,
702
703
{
@@ -767,7 +765,7 @@ describe('ReactDOMFizzServer', () => {
765
loggedErrors.length = 0;
766
767
await act(async () => {
770
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
768
+ const {pipe} = renderToPipeableStream(
769
<App />,
770
771
{
@@ -823,7 +821,7 @@ describe('ReactDOMFizzServer', () => {
821
822
it('should asynchronously load the suspense boundary', async () => {
823
await act(async () => {
826
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
824
+ const {pipe} = renderToPipeableStream(
825
<div>
826
<Suspense fallback={<Text text="Loading..." />}>
827
<AsyncText text="Hello World" />
@@ -862,7 +860,7 @@ describe('ReactDOMFizzServer', () => {
860
};
861
862
await act(async () => {
865
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
863
+ const {pipe} = renderToPipeableStream(<App />, {
864
bootstrapScriptContent: '__INIT__();',
865
});
866
pipe(writable);
@@ -936,7 +934,7 @@ describe('ReactDOMFizzServer', () => {
934
935
// We originally suspend the boundary and start streaming the loading state.
936
await act(async () => {
939
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
937
+ const {pipe} = renderToPipeableStream(
938
<App />,
939
940
{
@@ -1018,9 +1016,7 @@ describe('ReactDOMFizzServer', () => {
1016
1017
// We originally suspend the boundary and start streaming the loading state.
1018
await act(async () => {
1021
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1022
- <App showMore={false} />,
1023
- );
1019
+ const {pipe} = renderToPipeableStream(<App showMore={false} />);
1020
pipe(writable);
1021
});
1022
@@ -1093,7 +1089,7 @@ describe('ReactDOMFizzServer', () => {
1089
1090
let controls;
1091
await act(async () => {
1096
- controls = ReactDOMFizzServer.renderToPipeableStream(<App />, {onError});
1092
+ controls = renderToPipeableStream(<App />, {onError});
1093
controls.pipe(writable);
1094
});
1095
@@ -1163,7 +1159,7 @@ describe('ReactDOMFizzServer', () => {
1159
};
1160
1161
await act(async () => {
1166
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1162
+ const {pipe} = renderToPipeableStream(
1163
// We use two nested boundaries to flush out coverage of an old reentrancy bug.
1164
<Suspense fallback="Loading...">
1165
<Suspense fallback={<Text text="Loading A..." />}>
@@ -1187,7 +1183,7 @@ describe('ReactDOMFizzServer', () => {
1183
});
1184
1185
await act(async () => {
1190
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1186
+ const {pipe} = renderToPipeableStream(
1187
<Suspense fallback={<Text text="Loading B..." />}>
1188
<Text text="This will show B: " />
1189
<div>
@@ -1283,7 +1279,7 @@ describe('ReactDOMFizzServer', () => {
1279
}
1280
1281
await act(async () => {
1286
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
1282
+ const {pipe} = renderToPipeableStream(<App />);
1283
pipe(writable);
1284
});
1285
@@ -1370,7 +1366,7 @@ describe('ReactDOMFizzServer', () => {
1366
}
1367
1368
await act(async () => {
1373
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
1369
+ const {pipe} = renderToPipeableStream(<App />);
1370
pipe(writable);
1371
});
1372
@@ -1423,7 +1419,7 @@ describe('ReactDOMFizzServer', () => {
1419
}
1420
1421
await act(async () => {
1426
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1422
+ const {pipe} = renderToPipeableStream(
1423
<App />,
1424
1425
{
@@ -1509,7 +1505,7 @@ describe('ReactDOMFizzServer', () => {
1505
1506
try {
1507
await act(async () => {
1512
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<A />);
1508
+ const {pipe} = renderToPipeableStream(<A />);
1509
pipe(writable);
1510
});
1511
@@ -1607,7 +1603,7 @@ describe('ReactDOMFizzServer', () => {
1603
}
1604
1605
await act(async () => {
1610
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1606
+ const {pipe} = renderToPipeableStream(
1607
<TestProvider ctx="A">
1608
<div>
1609
<Suspense fallback={[<Text text="Loading: " />, <TestConsumer />]}>
@@ -1664,7 +1660,7 @@ describe('ReactDOMFizzServer', () => {
1660
}
1661
1662
await act(async () => {
1667
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1663
+ const {pipe} = renderToPipeableStream(
1664
<div>
1665
<PrintA />
1666
<div>
@@ -1725,7 +1721,7 @@ describe('ReactDOMFizzServer', () => {
1721
1722
const loggedErrors = [];
1723
await act(async () => {
1728
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1724
+ const {pipe} = renderToPipeableStream(
1725
<div>
1726
<PrintA />
1727
<div>
@@ -1793,7 +1789,7 @@ describe('ReactDOMFizzServer', () => {
1789
1790
let controls;
1791
await act(async () => {
1796
- controls = ReactDOMFizzServer.renderToPipeableStream(
1792
+ controls = renderToPipeableStream(
1793
<App isClient={false} />,
1794
1795
{
@@ -1876,7 +1872,7 @@ describe('ReactDOMFizzServer', () => {
1872
1873
it('should be able to abort the fallback if the main content finishes first', async () => {
1874
await act(async () => {
1879
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1875
+ const {pipe} = renderToPipeableStream(
1876
<Suspense fallback={<Text text="Loading Outer" />}>
1877
<div>
1878
<Suspense
@@ -1973,9 +1969,7 @@ describe('ReactDOMFizzServer', () => {
1969
await jest.runAllTimers();
1970
1971
await act(async () => {
1976
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1977
- <App isClient={false} />,
1978
- );
1972
+ const {pipe} = renderToPipeableStream(<App isClient={false} />);
1973
pipe(writable);
1974
});
1975
@@ -2085,7 +2079,7 @@ describe('ReactDOMFizzServer', () => {
2079
2080
const loggedErrors = [];
2081
await act(async () => {
2088
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2082
+ const {pipe} = renderToPipeableStream(
2083
<Suspense fallback="Loading...">
2084
<App />
2085
</Suspense>,
@@ -2170,7 +2164,7 @@ describe('ReactDOMFizzServer', () => {
2164
}
2165
const loggedErrors = [];
2166
await act(async () => {
2173
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2167
+ const {pipe} = renderToPipeableStream(
2168
<Suspense fallback="Loading...">
2169
<App />
2170
</Suspense>,
@@ -2259,7 +2253,7 @@ describe('ReactDOMFizzServer', () => {
2253
}
2254
2255
await act(async () => {
2262
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2256
+ const {pipe} = renderToPipeableStream(<App />);
2257
pipe(writable);
2258
});
2259
expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2349,7 +2343,7 @@ describe('ReactDOMFizzServer', () => {
2343
}
2344
2345
await act(async () => {
2352
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2346
+ const {pipe} = renderToPipeableStream(<App />);
2347
pipe(writable);
2348
});
2349
expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2439,7 +2433,7 @@ describe('ReactDOMFizzServer', () => {
2433
}
2434
2435
await act(async () => {
2442
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2436
+ const {pipe} = renderToPipeableStream(<App />);
2437
pipe(writable);
2438
});
2439
expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2492,7 +2486,7 @@ describe('ReactDOMFizzServer', () => {
2486
);
2487
}
2488
await act(async () => {
2495
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
2489
+ const {pipe} = renderToPipeableStream(<App />, {
2490
onError(error) {
2491
Scheduler.unstable_yieldValue('[s!] ' + error.message);
2492
},
@@ -2577,14 +2571,11 @@ describe('ReactDOMFizzServer', () => {
2571
);
2572
}
2573
await act(async () => {
2580
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2581
- <App color="red" />,
2582
- {
2583
- onError(error) {
2584
- Scheduler.unstable_yieldValue('[s!] ' + error.message);
2585
- },
2574
+ const {pipe} = renderToPipeableStream(<App color="red" />, {
2575
+ onError(error) {
2576
+ Scheduler.unstable_yieldValue('[s!] ' + error.message);
2577
},
2587
- );
2578
+ });
2579
pipe(writable);
2580
});
2581
expect(Scheduler).toHaveYielded(['[s!] Oops.']);
@@ -2678,7 +2669,7 @@ describe('ReactDOMFizzServer', () => {
2669
}
2670
2671
await act(async () => {
2681
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2672
+ const {pipe} = renderToPipeableStream(
2673
<App fallbackText="Loading..." />,
2674
{
2675
onError(error) {
@@ -2807,7 +2798,7 @@ describe('ReactDOMFizzServer', () => {
2798
}
2799
2800
await act(async () => {
2810
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2801
+ const {pipe} = renderToPipeableStream(<App />);
2802
pipe(writable);
2803
});
2804
expect(Scheduler).toHaveYielded(['Yay!']);
@@ -2959,7 +2950,7 @@ describe('ReactDOMFizzServer', () => {
2950
}
2951
2952
await act(async () => {
2962
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2953
+ const {pipe} = renderToPipeableStream(<App />);
2954
pipe(writable);
2955
});
2956
expect(Scheduler).toHaveYielded(['A', 'B']);
@@ -3025,7 +3016,7 @@ describe('ReactDOMFizzServer', () => {
3016
}
3017
3018
await act(async () => {
3028
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
3019
+ const {pipe} = renderToPipeableStream(<Foo />);
3020
pipe(writable);
3021
});
3022
@@ -3047,9 +3038,7 @@ describe('ReactDOMFizzServer', () => {
3038
]).map(item => <li key={item.get('value')}>{item.get('name')}</li>);
3039
3040
await act(async () => {
3050
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3051
- <ul>{mappedJSX}</ul>,
3052
- );
3041
+ const {pipe} = renderToPipeableStream(<ul>{mappedJSX}</ul>);
3042
pipe(writable);
3043
});
3044
expect(getVisibleChildren(container)).toEqual(
@@ -3081,10 +3070,7 @@ describe('ReactDOMFizzServer', () => {
3070
let abort;
3071
const loggedErrors = [];
3072
await act(async () => {
3084
- const {
3085
- pipe,
3086
- abort: abortImpl,
3087
- } = ReactDOMFizzServer.renderToPipeableStream(<App />, {
3073
+ const {pipe, abort: abortImpl} = renderToPipeableStream(<App />, {
3074
onError(error) {
3075
// In this test we contrive erroring with strings so we push the error whereas in most
3076
// other tests we contrive erroring with Errors and push the message.
@@ -3167,10 +3153,7 @@ describe('ReactDOMFizzServer', () => {
3153
let abort;
3154
const loggedErrors = [];
3155
await act(async () => {
3170
- const {
3171
- pipe,
3172
- abort: abortImpl,
3173
- } = ReactDOMFizzServer.renderToPipeableStream(<App />, {
3156
+ const {pipe, abort: abortImpl} = renderToPipeableStream(<App />, {
3157
onError(error) {
3158
loggedErrors.push(error.message);
3159
return 'a digest';
@@ -3232,7 +3215,7 @@ describe('ReactDOMFizzServer', () => {
3215
3216
it('warns in dev if you access digest from errorInfo in onRecoverableError', async () => {
3217
await act(async () => {
3235
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3218
+ const {pipe} = renderToPipeableStream(
3219
<div>
3220
<Suspense fallback={'loading...'}>
3221
<AsyncText text={'hello'} />
@@ -3309,7 +3292,7 @@ describe('ReactDOMFizzServer', () => {
3292
}
3293
3294
await act(async () => {
3312
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
3295
+ const {pipe} = renderToPipeableStream(<App />, {
3296
onError,
3297
});
3298
pipe(writable);
@@ -3357,7 +3340,7 @@ describe('ReactDOMFizzServer', () => {
3340
}
3341
3342
await act(async () => {
3360
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
3343
+ const {pipe} = renderToPipeableStream(<App />, {
3344
onError,
3345
});
3346
pipe(writable);
@@ -3397,7 +3380,7 @@ describe('ReactDOMFizzServer', () => {
3380
loggedErrors.length = 0;
3381
3382
await act(async () => {
3400
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
3383
+ const {pipe} = renderToPipeableStream(<App />, {
3384
onError,
3385
});
3386
pipe(writable);
@@ -3435,7 +3418,7 @@ describe('ReactDOMFizzServer', () => {
3418
3419
it('accepts an integrity property for bootstrapScripts and bootstrapModules', async () => {
3420
await actIntoEmptyDocument(() => {
3438
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3421
+ const {pipe} = renderToPipeableStream(
3422
<html>
3423
<head />
3424
<body>
@@ -3494,7 +3477,7 @@ describe('ReactDOMFizzServer', () => {
3477
const stringWithScriptsInIt =
3478
'prescription pre<scription pre<Scription pre</scRipTion pre</ScripTion </script><script><!-- <script> -->';
3479
await act(async () => {
3497
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<div />, {
3480
+ const {pipe} = renderToPipeableStream(<div />, {
3481
bootstrapScriptContent:
3482
'window.__test_outlet = "This should have been replaced";var x = "' +
3483
stringWithScriptsInIt +
@@ -3515,7 +3498,7 @@ describe('ReactDOMFizzServer', () => {
3498
el.textContent = '{"one":1,\u2028\u2029"two":2}';
3499
const stringWithLSAndPSCharacters = el.textContent;
3500
await act(async () => {
3518
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<div />, {
3501
+ const {pipe} = renderToPipeableStream(<div />, {
3502
bootstrapScriptContent:
3503
'let x = ' +
3504
stringWithLSAndPSCharacters +
@@ -3536,7 +3519,7 @@ describe('ReactDOMFizzServer', () => {
3519
// this boolean expression will be cast to a number due to the bitwise &. we will look for a truthy value (1) below
3520
const booleanLogicString = '1 < 2 & 3 > 1';
3521
await act(async () => {
3539
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<div />, {
3522
+ const {pipe} = renderToPipeableStream(<div />, {
3523
bootstrapScriptContent:
3524
'let x = ' + booleanLogicString + '; window.__test_outlet = x;',
3525
});
@@ -3549,7 +3532,7 @@ describe('ReactDOMFizzServer', () => {
3532
// @gate enableFizzExternalRuntime
3533
it('supports option to load runtime as an external script', async () => {
3534
await actIntoEmptyDocument(() => {
3552
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3535
+ const {pipe} = renderToPipeableStream(
3536
<html>
3537
<head />
3538
<body>
@@ -3608,7 +3591,7 @@ describe('ReactDOMFizzServer', () => {
3591
3592
const [ServerApp, serverResolve] = makeApp();
3593
await act(async () => {
3611
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<ServerApp />);
3594
+ const {pipe} = renderToPipeableStream(<ServerApp />);
3595
pipe(writable);
3596
});
3597
await act(() => {
@@ -3684,9 +3667,7 @@ describe('ReactDOMFizzServer', () => {
3667
3668
const [ServerApp, serverResolve] = makeApp();
3669
await act(async () => {
3687
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3688
- <ServerApp text="initial" />,
3689
- );
3670
+ const {pipe} = renderToPipeableStream(<ServerApp text="initial" />);
3671
pipe(writable);
3672
});
3673
await act(() => {
@@ -3763,9 +3744,7 @@ describe('ReactDOMFizzServer', () => {
3744
3745
try {
3746
await act(async () => {
3766
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3767
- <App text="initial" />,
3768
- );
3747
+ const {pipe} = renderToPipeableStream(<App text="initial" />);
3748
pipe(writable);
3749
});
3750
@@ -3854,7 +3833,7 @@ describe('ReactDOMFizzServer', () => {
3833
};
3834
3835
await act(async () => {
3857
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
3836
+ const {pipe} = renderToPipeableStream(<App />);
3837
pipe(writable);
3838
});
3839
@@ -3946,7 +3925,7 @@ describe('ReactDOMFizzServer', () => {
3925
3926
try {
3927
await act(async () => {
3949
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
3928
+ const {pipe} = renderToPipeableStream(<App />);
3929
pipe(writable);
3930
});
3931
@@ -4077,7 +4056,7 @@ describe('ReactDOMFizzServer', () => {
4056
4057
try {
4058
await act(async () => {
4080
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4059
+ const {pipe} = renderToPipeableStream(<App />);
4060
pipe(writable);
4061
});
4062
@@ -4200,7 +4179,7 @@ describe('ReactDOMFizzServer', () => {
4179
4180
try {
4181
await act(async () => {
4203
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4182
+ const {pipe} = renderToPipeableStream(<App />);
4183
pipe(writable);
4184
});
4185
@@ -4271,7 +4250,7 @@ describe('ReactDOMFizzServer', () => {
4250
);
4251
}
4252
await act(async () => {
4274
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4253
+ const {pipe} = renderToPipeableStream(<App />);
4254
pipe(writable);
4255
});
4256
@@ -4317,7 +4296,7 @@ describe('ReactDOMFizzServer', () => {
4296
);
4297
}
4298
await act(async () => {
4320
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4299
+ const {pipe} = renderToPipeableStream(<App />);
4300
pipe(writable);
4301
});
4302
@@ -4372,7 +4351,7 @@ describe('ReactDOMFizzServer', () => {
4351
);
4352
}
4353
await actIntoEmptyDocument(() => {
4375
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4354
+ const {pipe} = renderToPipeableStream(
4355
<html data-html="html">
4356
<AsyncNoOutput />
4357
<AsyncHead />
@@ -4413,7 +4392,7 @@ describe('ReactDOMFizzServer', () => {
4392
});
4393
4394
await actIntoEmptyDocument(() => {
4416
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4395
+ const {pipe} = renderToPipeableStream(
4396
<html>
4397
<head />
4398
<body>
@@ -4470,9 +4449,7 @@ describe('ReactDOMFizzServer', () => {
4449
}
4450
4451
await act(async () => {
4473
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4474
- <App name="Foo" />,
4475
- );
4452
+ const {pipe} = renderToPipeableStream(<App name="Foo" />);
4453
pipe(writable);
4454
});
4455
@@ -4510,9 +4487,7 @@ describe('ReactDOMFizzServer', () => {
4487
}
4488
4489
await act(async () => {
4513
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4514
- <App name="Foo" />,
4515
- );
4490
+ const {pipe} = renderToPipeableStream(<App name="Foo" />);
4491
pipe(writable);
4492
});
4493
@@ -4562,7 +4537,7 @@ describe('ReactDOMFizzServer', () => {
4537
}
4538
4539
await act(async () => {
4565
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4540
+ const {pipe} = renderToPipeableStream(<App />);
4541
pipe(writable);
4542
});
4543
@@ -4607,7 +4582,7 @@ describe('ReactDOMFizzServer', () => {
4582
}
4583
4584
await act(async () => {
4610
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4585
+ const {pipe} = renderToPipeableStream(<App />);
4586
await afterImmediate();
4587
await act(() => resolveText('ello'));
4588
pipe(writable);
@@ -4651,7 +4626,7 @@ describe('ReactDOMFizzServer', () => {
4626
}
4627
4628
await act(async () => {
4654
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4629
+ const {pipe} = renderToPipeableStream(<App />);
4630
await afterImmediate();
4631
await act(() => resolveText('world'));
4632
pipe(writable);
@@ -4689,7 +4664,7 @@ describe('ReactDOMFizzServer', () => {
4664
}
4665
4666
await act(async () => {
4692
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4667
+ const {pipe} = renderToPipeableStream(<App />);
4668
await afterImmediate();
4669
await act(() => resolveText('world'));
4670
pipe(writable);
@@ -4738,7 +4713,7 @@ describe('ReactDOMFizzServer', () => {
4713
}
4714
4715
await act(async () => {
4741
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4716
+ const {pipe} = renderToPipeableStream(<App />);
4717
await afterImmediate();
4718
await act(() => resolveText('world'));
4719
pipe(writable);
@@ -4823,12 +4798,13 @@ describe('ReactDOMFizzServer', () => {
4798
}
4799
4800
await act(async () => {
4826
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4801
+ const {pipe} = renderToPipeableStream(<App />);
4802
await afterImmediate();
4803
await act(() => resolveText('world'));
4804
pipe(writable);
4805
});
4806
4807
+ // strip inserted external runtime
4808
expect(container.innerHTML).toEqual(
4809
'<div><!--$-->hello<!-- -->world<!-- --><!--/$--><!--$-->world<!-- --><!--/$--><!--$-->hello<!-- -->world<!-- --><br><!--/$--><!--$-->world<!-- --><br><!--/$--></div>',
4810
);
@@ -4879,7 +4855,7 @@ describe('ReactDOMFizzServer', () => {
4855
4856
prepareJSDOMForTitle();
4857
await act(async () => {
4882
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4858
+ const {pipe} = renderToPipeableStream(<App />);
4859
pipe(writable);
4860
});
4861
expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
@@ -4903,7 +4879,7 @@ describe('ReactDOMFizzServer', () => {
4879
4880
prepareJSDOMForTitle();
4881
await act(async () => {
4906
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4882
+ const {pipe} = renderToPipeableStream(<App />);
4883
pipe(writable);
4884
});
4885
expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
@@ -4941,7 +4917,7 @@ describe('ReactDOMFizzServer', () => {
4917
prepareJSDOMForTitle();
4918
4919
await act(async () => {
4944
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4920
+ const {pipe} = renderToPipeableStream(<App />);
4921
pipe(writable);
4922
});
4923
if (__DEV__) {
@@ -5025,7 +5001,7 @@ describe('ReactDOMFizzServer', () => {
5001
prepareJSDOMForTitle();
5002
5003
await act(async () => {
5028
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5004
+ const {pipe} = renderToPipeableStream(<App />);
5005
pipe(writable);
5006
});
5007
if (__DEV__) {
@@ -5086,7 +5062,7 @@ describe('ReactDOMFizzServer', () => {
5062
}
5063
5064
await act(async () => {
5089
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5065
+ const {pipe} = renderToPipeableStream(<App />);
5066
pipe(writable);
5067
});
5068
@@ -5146,7 +5122,7 @@ describe('ReactDOMFizzServer', () => {
5122
}
5123
5124
await act(async () => {
5149
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5125
+ const {pipe} = renderToPipeableStream(<App />);
5126
pipe(writable);
5127
});
5128
expect(getVisibleChildren(container)).toEqual(['AB', 'C']);
@@ -5185,7 +5161,7 @@ describe('ReactDOMFizzServer', () => {
5161
}
5162
5163
await act(async () => {
5188
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5164
+ const {pipe} = renderToPipeableStream(<App />);
5165
pipe(writable);
5166
});
5167
@@ -5254,7 +5230,7 @@ describe('ReactDOMFizzServer', () => {
5230
5231
const reportedServerErrors = [];
5232
await act(async () => {
5257
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
5233
+ const {pipe} = renderToPipeableStream(<App />, {
5234
onError(error) {
5235
reportedServerErrors.push(error);
5236
},
@@ -5315,7 +5291,7 @@ describe('ReactDOMFizzServer', () => {
5291
}
5292
5293
await act(async () => {
5318
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5294
+ const {pipe} = renderToPipeableStream(<App />);
5295
pipe(writable);
5296
});
5297
expect(getVisibleChildren(container)).toEqual('Hi');
@@ -5342,7 +5318,7 @@ describe('ReactDOMFizzServer', () => {
5318
);
5319
}
5320
await act(async () => {
5345
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5321
+ const {pipe} = renderToPipeableStream(<App />);
5322
pipe(writable);
5323
});
5324
expect(getVisibleChildren(container)).toEqual(<button>0</button>);
@@ -5373,7 +5349,7 @@ describe('ReactDOMFizzServer', () => {
5349
let caughtError;
5350
try {
5351
await act(async () => {
5376
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
5352
+ const {pipe} = renderToPipeableStream(<App />, {
5353
onError(e) {
5354
reportedServerErrors.push(e);
5355
},
@@ -5402,7 +5378,7 @@ describe('ReactDOMFizzServer', () => {
5378
}
5379
}
5380
await act(async () => {
5405
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5381
+ const {pipe} = renderToPipeableStream(<App />);
5382
pipe(writable);
5383
});
5384
expect(getVisibleChildren(container)).toEqual(<div />);
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+58
-55
@@ -8,6 +8,7 @@
8
*/
9
10
'use strict';
11
+import {replaceScriptsAndMove, mergeOptions} from '../test-utils/FizzTestUtils';
12
13
let JSDOM;
14
let Stream;
@@ -25,6 +26,7 @@ let container;
26
let buffer = '';
27
let hasErrored = false;
28
let fatalError = undefined;
29
+const renderOptions = {};
30
31
describe('ReactDOMFloat', () => {
32
beforeEach(() => {
@@ -100,17 +102,7 @@ describe('ReactDOMFloat', () => {
102
container.nodeName === '#document' ? container.body : container;
103
while (fakeBody.firstChild) {
104
const node = fakeBody.firstChild;
103
- if (
104
- node.nodeName === 'SCRIPT' &&
105
- (CSPnonce === null || node.getAttribute('nonce') === CSPnonce)
106
- ) {
107
- const script = document.createElement('script');
108
- script.textContent = node.textContent;
109
- fakeBody.removeChild(node);
110
- parent.appendChild(script);
111
- } else {
112
- parent.appendChild(node);
113
- }
105
+ await replaceScriptsAndMove(document.defaultView, CSPnonce, node, parent);
106
}
107
}
108
@@ -135,6 +127,7 @@ describe('ReactDOMFloat', () => {
127
document = jsdom.window.document;
128
container = document;
129
buffer = '';
130
+ await replaceScriptsAndMove(jsdom.window, null, document.documentElement);
131
}
132
133
function getMeaningfulChildren(element) {
@@ -148,6 +141,8 @@ describe('ReactDOMFloat', () => {
141
node.hasAttribute('data-meaningful') ||
142
(node.tagName === 'SCRIPT' &&
143
node.hasAttribute('src') &&
144
+ node.getAttribute('src') !==
145
+ renderOptions.unstable_externalRuntimeSrc &&
146
node.hasAttribute('async')) ||
147
(node.tagName !== 'SCRIPT' &&
148
node.tagName !== 'TEMPLATE' &&
@@ -235,6 +230,14 @@ describe('ReactDOMFloat', () => {
230
return readText(text);
231
}
232
233
+ function renderToPipeableStream(jsx, options) {
234
+ // Merge options with renderOptions, which may contain featureFlag specific behavior
235
+ return ReactDOMFizzServer.renderToPipeableStream(
236
+ jsx,
237
+ mergeOptions(options, renderOptions),
238
+ );
239
+ }
240
+
241
// @gate enableFloat
242
it('can render resources before singletons', async () => {
243
const root = ReactDOMClient.createRoot(document);
@@ -569,7 +572,7 @@ describe('ReactDOMFloat', () => {
572
});
573
574
await actIntoEmptyDocument(() => {
572
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
575
+ const {pipe} = renderToPipeableStream(
576
<>
577
<title>foo</title>
578
<html>
@@ -635,7 +638,7 @@ describe('ReactDOMFloat', () => {
638
return 'foo';
639
}
640
await actIntoEmptyDocument(() => {
638
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
641
+ const {pipe} = renderToPipeableStream(
642
<html>
643
<head />
644
<body>
@@ -786,7 +789,7 @@ describe('ReactDOMFloat', () => {
789
}
790
791
await actIntoEmptyDocument(() => {
789
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<ServerApp />);
792
+ const {pipe} = renderToPipeableStream(<ServerApp />);
793
pipe(writable);
794
});
795
expect(getMeaningfulChildren(document)).toEqual(
@@ -846,7 +849,7 @@ describe('ReactDOMFloat', () => {
849
return 'foo';
850
}
851
await actIntoEmptyDocument(() => {
849
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
852
+ const {pipe} = renderToPipeableStream(
853
<html>
854
<head />
855
<body>
@@ -877,7 +880,7 @@ describe('ReactDOMFloat', () => {
880
return 'foo';
881
}
882
await actIntoEmptyDocument(() => {
880
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
883
+ const {pipe} = renderToPipeableStream(
884
<html>
885
<head />
886
<body>
@@ -996,7 +999,7 @@ describe('ReactDOMFloat', () => {
999
);
1000
}
1001
await actIntoEmptyDocument(() => {
999
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1002
+ const {pipe} = renderToPipeableStream(
1003
<App srcs={['server', 'shared']} />,
1004
);
1005
pipe(writable);
@@ -1148,7 +1151,7 @@ describe('ReactDOMFloat', () => {
1151
// @gate enableFloat
1152
it('supports preconnects, prefetc-dns, and arbitrary other link types', async () => {
1153
await actIntoEmptyDocument(() => {
1151
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1154
+ const {pipe} = renderToPipeableStream(
1155
<html>
1156
<head />
1157
<body>
@@ -1259,7 +1262,7 @@ describe('ReactDOMFloat', () => {
1262
// @gate enableFloat
1263
it('can render <base> as a Resource', async () => {
1264
await actIntoEmptyDocument(() => {
1262
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1265
+ const {pipe} = renderToPipeableStream(
1266
<html>
1267
<head />
1268
<body>
@@ -1317,7 +1320,7 @@ describe('ReactDOMFloat', () => {
1320
// @gate enableFloat
1321
it('can render icons and apple-touch-icons as Resources', async () => {
1322
await actIntoEmptyDocument(() => {
1320
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1323
+ const {pipe} = renderToPipeableStream(
1324
<>
1325
<html>
1326
<head />
@@ -1371,7 +1374,7 @@ describe('ReactDOMFloat', () => {
1374
// @gate enableFloat
1375
it('can hydrate the right instances for deeply nested structured metas', async () => {
1376
await actIntoEmptyDocument(() => {
1374
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1377
+ const {pipe} = renderToPipeableStream(
1378
<>
1379
<html>
1380
<head />
@@ -1463,7 +1466,7 @@ describe('ReactDOMFloat', () => {
1466
// @gate enableFloat
1467
it('can insert meta tags in the expected location', async () => {
1468
await actIntoEmptyDocument(() => {
1466
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1469
+ const {pipe} = renderToPipeableStream(
1470
<>
1471
<html>
1472
<head />
@@ -1615,7 +1618,7 @@ describe('ReactDOMFloat', () => {
1618
// @gate enableFloat
1619
it('can render meta tags with og properties with structured data', async () => {
1620
await actIntoEmptyDocument(() => {
1618
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1621
+ const {pipe} = renderToPipeableStream(
1622
<>
1623
<html>
1624
<head />
@@ -1778,7 +1781,7 @@ describe('ReactDOMFloat', () => {
1781
// @gate enableFloat
1782
it('can render meta tags as resources', async () => {
1783
await actIntoEmptyDocument(() => {
1781
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1784
+ const {pipe} = renderToPipeableStream(
1785
<>
1786
<html>
1787
<head />
@@ -1843,7 +1846,7 @@ describe('ReactDOMFloat', () => {
1846
// @gate enableFloat
1847
it('can rendering title tags anywhere in the tree', async () => {
1848
await actIntoEmptyDocument(() => {
1846
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1849
+ const {pipe} = renderToPipeableStream(
1850
<>
1851
<title>before</title>
1852
<>
@@ -1918,7 +1921,7 @@ describe('ReactDOMFloat', () => {
1921
// @gate enableFloat
1922
it('prepends new titles on the client so newer ones override older ones, including orphaned server rendered titles', async () => {
1923
await actIntoEmptyDocument(() => {
1921
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1924
+ const {pipe} = renderToPipeableStream(
1925
<html>
1926
<head>
1927
<title>server</title>
@@ -2032,7 +2035,7 @@ describe('ReactDOMFloat', () => {
2035
// @gate enableFloat && enableHostSingletons && (enableClientRenderFallbackOnTextMismatch || !__DEV__)
2036
it('can render a title before a singleton even if that singleton clears its contents', async () => {
2037
await actIntoEmptyDocument(() => {
2035
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2038
+ const {pipe} = renderToPipeableStream(
2039
<>
2040
<title>foo</title>
2041
<html>
@@ -2097,7 +2100,7 @@ describe('ReactDOMFloat', () => {
2100
// @gate enableFloat
2101
it('treats link rel stylesheet elements as a style resource when it includes a precedence when server rendering', async () => {
2102
await actIntoEmptyDocument(() => {
2100
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2103
+ const {pipe} = renderToPipeableStream(
2104
<html>
2105
<head />
2106
<body>
@@ -2150,7 +2153,7 @@ describe('ReactDOMFloat', () => {
2153
// @gate enableFloat
2154
it('treats link rel stylesheet elements as a style resource when it includes a precedence when hydrating', async () => {
2155
await actIntoEmptyDocument(() => {
2153
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2156
+ const {pipe} = renderToPipeableStream(
2157
<html>
2158
<head />
2159
<body>
@@ -2188,7 +2191,7 @@ describe('ReactDOMFloat', () => {
2191
// @gate enableFloat
2192
it('preloads stylesheets without a precedence prop when server rendering', async () => {
2193
await actIntoEmptyDocument(() => {
2191
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2194
+ const {pipe} = renderToPipeableStream(
2195
<html>
2196
<head />
2197
<body>
@@ -2216,7 +2219,7 @@ describe('ReactDOMFloat', () => {
2219
// @gate enableFloat
2220
it('hoists style resources to the correct precedence', async () => {
2221
await actIntoEmptyDocument(() => {
2219
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2222
+ const {pipe} = renderToPipeableStream(
2223
<html>
2224
<head />
2225
<body>
@@ -2311,7 +2314,7 @@ describe('ReactDOMFloat', () => {
2314
// @gate enableFloat && enableHostSingletons && enableClientRenderFallbackOnTextMismatch
2315
it('retains styles even when a new html, head, and/body mount', async () => {
2316
await actIntoEmptyDocument(() => {
2314
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2317
+ const {pipe} = renderToPipeableStream(
2318
<html>
2319
<head />
2320
<body>
@@ -2363,7 +2366,7 @@ describe('ReactDOMFloat', () => {
2366
// @gate enableFloat && !enableHostSingletons
2367
it('retains styles even when a new html, head, and/body mount - without HostSingleton', async () => {
2368
await actIntoEmptyDocument(() => {
2366
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2369
+ const {pipe} = renderToPipeableStream(
2370
<html>
2371
<head />
2372
<body>
@@ -2468,7 +2471,7 @@ describe('ReactDOMFloat', () => {
2471
// @gate enableFloat
2472
it('treats async scripts without onLoad or onError as Resources', async () => {
2473
await actIntoEmptyDocument(() => {
2471
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2474
+ const {pipe} = renderToPipeableStream(
2475
<html>
2476
<head />
2477
<body>
@@ -2553,7 +2556,7 @@ describe('ReactDOMFloat', () => {
2556
);
2557
}
2558
await actIntoEmptyDocument(() => {
2556
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2559
+ const {pipe} = renderToPipeableStream(<App />);
2560
pipe(writable);
2561
});
2562
@@ -2636,7 +2639,7 @@ describe('ReactDOMFloat', () => {
2639
// @gate enableFloat
2640
it('treats stylesheet links with a precedence as a resource', async () => {
2641
await actIntoEmptyDocument(() => {
2639
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2642
+ const {pipe} = renderToPipeableStream(
2643
<html>
2644
<head />
2645
<body>
@@ -2689,7 +2692,7 @@ describe('ReactDOMFloat', () => {
2692
}
2693
2694
await actIntoEmptyDocument(() => {
2692
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2695
+ const {pipe} = renderToPipeableStream(
2696
<html>
2697
<head />
2698
<body>
@@ -2745,7 +2748,7 @@ describe('ReactDOMFloat', () => {
2748
ReactDOM.preinit('preset', {as: 'style', precedence: 'preset'});
2749
}
2750
await actIntoEmptyDocument(() => {
2748
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
2751
+ const {pipe} = renderToPipeableStream(
2752
<html>
2753
<head />
2754
<body>
@@ -3109,7 +3112,7 @@ describe('ReactDOMFloat', () => {
3112
// @gate enableFloat
3113
it('normalizes style resource precedence for all boundaries inlined as part of the shell flush', async () => {
3114
await actIntoEmptyDocument(() => {
3112
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3115
+ const {pipe} = renderToPipeableStream(
3116
<html>
3117
<head />
3118
<body>
@@ -3199,7 +3202,7 @@ describe('ReactDOMFloat', () => {
3202
// @gate enableFloat
3203
it('style resources are inserted according to precedence order on the client', async () => {
3204
await actIntoEmptyDocument(() => {
3202
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3205
+ const {pipe} = renderToPipeableStream(
3206
<html>
3207
<head />
3208
<body>
@@ -3328,7 +3331,7 @@ describe('ReactDOMFloat', () => {
3331
return null;
3332
}
3333
await actIntoEmptyDocument(() => {
3331
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3334
+ const {pipe} = renderToPipeableStream(
3335
<html>
3336
<head />
3337
<body>
@@ -3406,7 +3409,7 @@ describe('ReactDOMFloat', () => {
3409
return children;
3410
}
3411
await actIntoEmptyDocument(() => {
3409
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3412
+ const {pipe} = renderToPipeableStream(
3413
<html>
3414
<head />
3415
<body>
@@ -3529,7 +3532,7 @@ describe('ReactDOMFloat', () => {
3532
return children;
3533
}
3534
await actIntoEmptyDocument(() => {
3532
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3535
+ const {pipe} = renderToPipeableStream(
3536
<html>
3537
<head />
3538
<body>
@@ -3761,7 +3764,7 @@ describe('ReactDOMFloat', () => {
3764
}
3765
try {
3766
await actIntoEmptyDocument(() => {
3764
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
3767
+ const {pipe} = renderToPipeableStream(<App />);
3768
pipe(writable);
3769
});
3770
expect(getMeaningfulChildren(document)).toEqual(
@@ -3869,7 +3872,7 @@ describe('ReactDOMFloat', () => {
3872
return children;
3873
}
3874
await actIntoEmptyDocument(() => {
3872
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
3875
+ const {pipe} = renderToPipeableStream(
3876
<html>
3877
<head />
3878
<body>
@@ -3998,7 +4001,7 @@ describe('ReactDOMFloat', () => {
4001
};
4002
try {
4003
await actIntoEmptyDocument(() => {
4001
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4004
+ const {pipe} = renderToPipeableStream(
4005
<html>
4006
<head />
4007
<body>
@@ -4082,7 +4085,7 @@ describe('ReactDOMFloat', () => {
4085
};
4086
try {
4087
await actIntoEmptyDocument(() => {
4085
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4088
+ const {pipe} = renderToPipeableStream(
4089
<html>
4090
<head>
4091
<Component scenarios={scenarios} />
@@ -4719,7 +4722,7 @@ describe('ReactDOMFloat', () => {
4722
};
4723
try {
4724
await actIntoEmptyDocument(() => {
4722
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4725
+ const {pipe} = renderToPipeableStream(
4726
<html>
4727
<head>
4728
<link
@@ -4832,7 +4835,7 @@ describe('ReactDOMFloat', () => {
4835
};
4836
try {
4837
await actIntoEmptyDocument(() => {
4835
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4838
+ const {pipe} = renderToPipeableStream(
4839
<html>
4840
<head>
4841
<link
@@ -4945,7 +4948,7 @@ describe('ReactDOMFloat', () => {
4948
};
4949
try {
4950
await actIntoEmptyDocument(() => {
4948
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4951
+ const {pipe} = renderToPipeableStream(
4952
<html>
4953
<head>
4954
<link
@@ -5056,7 +5059,7 @@ describe('ReactDOMFloat', () => {
5059
};
5060
try {
5061
await actIntoEmptyDocument(() => {
5059
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
5062
+ const {pipe} = renderToPipeableStream(
5063
<html>
5064
<head>
5065
<script src="foo" async={true} data-foo="a current value" />
@@ -5106,7 +5109,7 @@ describe('ReactDOMFloat', () => {
5109
};
5110
try {
5111
await actIntoEmptyDocument(() => {
5109
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
5112
+ const {pipe} = renderToPipeableStream(
5113
<html>
5114
<head>
5115
<link
@@ -5201,7 +5204,7 @@ describe('ReactDOMFloat', () => {
5204
};
5205
try {
5206
await actIntoEmptyDocument(() => {
5204
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
5207
+ const {pipe} = renderToPipeableStream(
5208
<html>
5209
<head>
5210
<link
@@ -5262,7 +5265,7 @@ describe('ReactDOMFloat', () => {
5265
// @gate enableFloat
5266
it('escapes hrefs when selecting matching elements in the document when rendering Resources', async () => {
5267
await actIntoEmptyDocument(() => {
5265
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
5268
+ const {pipe} = renderToPipeableStream(
5269
<html>
5270
<head />
5271
<body>
@@ -5324,7 +5327,7 @@ describe('ReactDOMFloat', () => {
5327
// @gate enableFloat
5328
it('escapes hrefs when selecting matching elements in the document when using preload and preinit', async () => {
5329
await actIntoEmptyDocument(() => {
5327
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
5330
+ const {pipe} = renderToPipeableStream(
5331
<html>
5332
<head />
5333
<body>
packages/react-dom/src/server/ReactDOMServerExternalRuntime.js
+9
-2
@@ -1,10 +1,17 @@
1
-// TODO: Add Flow types
1
+/**
2
+ * This file is compiled to a standalone browser script by rollup and loaded by Fizz
3
+ * clients. Therefore, it should be fast and not have many external dependencies.
4
+ * @flow
5
+ */
6
+
7
+// Imports are resolved statically by the closure compiler in release bundles
8
+// and by rollup in jest unit tests
9
import {
10
clientRenderBoundary,
11
completeBoundaryWithStyles,
12
completeBoundary,
13
completeSegment,
7
-} from 'react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet';
14
+} from '../../../react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSet';
15
16
// Intentionally does nothing. Implementation will be added in future PR.
17
// eslint-disable-next-line no-unused-vars
packages/react-dom/src/test-utils/FizzTestUtils.js
new
+123
@@ -0,0 +1,123 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+'use strict';
10
+
11
+import * as tmp from 'tmp';
12
+import * as fs from 'fs';
13
+import replace from 'rollup-plugin-replace';
14
+import {rollup} from 'rollup';
15
+
16
+const rollupCache: Map<string, string | null> = new Map();
17
+
18
+// Utility function to read and bundle a standalone browser script
19
+async function getRollupResult(scriptSrc: string): Promise<string | null> {
20
+ const cachedResult = rollupCache.get(scriptSrc);
21
+ if (cachedResult !== undefined) {
22
+ return cachedResult;
23
+ }
24
+ let tmpFile;
25
+ try {
26
+ tmpFile = tmp.fileSync();
27
+ const rollupConfig = {
28
+ input: require.resolve(scriptSrc),
29
+ onwarn: console.warn,
30
+ plugins: [replace({__DEV__: 'true'})],
31
+ output: {
32
+ externalLiveBindings: false,
33
+ freeze: false,
34
+ interop: false,
35
+ esModule: false,
36
+ },
37
+ };
38
+ const outputConfig = {
39
+ file: tmpFile.name,
40
+ format: 'iife',
41
+ };
42
+ const bundle = await rollup(rollupConfig);
43
+ await bundle.write(outputConfig);
44
+ const bundleBuffer = Buffer.alloc(4096);
45
+ let bundleStr = '';
46
+ while (true) {
47
+ // $FlowFixMe[incompatible-call]
48
+ const bytes = fs.readSync(tmpFile.fd, bundleBuffer);
49
+ if (bytes <= 0) {
50
+ break;
51
+ }
52
+ bundleStr += bundleBuffer.slice(0, bytes).toString();
53
+ }
54
+ rollupCache.set(scriptSrc, bundleStr);
55
+ return bundleStr;
56
+ } catch (e) {
57
+ rollupCache.set(scriptSrc, null);
58
+ return null;
59
+ } finally {
60
+ if (tmpFile) {
61
+ tmpFile.removeCallback();
62
+ }
63
+ }
64
+}
65
+
66
+// Utility function to process received HTML nodes and execute
67
+// embedded scripts by:
68
+// 1. Matching nonce attributes and moving node into an existing
69
+// parent container (if passed)
70
+// 2. Resolving scripts with sources
71
+async function replaceScriptsAndMove(
72
+ window: any,
73
+ CSPnonce: string | null,
74
+ node: Node,
75
+ parent: Node | null,
76
+) {
77
+ if (
78
+ node.nodeType === 1 &&
79
+ (node.nodeName === 'SCRIPT' || node.nodeName === 'script')
80
+ ) {
81
+ // $FlowFixMe[incompatible-cast]
82
+ const element = (node: HTMLElement);
83
+ const script = window.document.createElement('SCRIPT');
84
+ const scriptSrc = element.getAttribute('src');
85
+ if (scriptSrc) {
86
+ const rollupOutput = await getRollupResult(scriptSrc);
87
+ if (rollupOutput) {
88
+ // Manually call eval(...) here, since changing the HTML text content
89
+ // may interfere with hydration
90
+ window.eval(rollupOutput);
91
+ }
92
+ for (let i = 0; i < element.attributes.length; i++) {
93
+ const attr = element.attributes.item(i);
94
+ script.setAttribute(attr.name, attr.value);
95
+ }
96
+ } else if (element === null || element.getAttribute('nonce') === CSPnonce) {
97
+ script.textContent = node.textContent;
98
+ }
99
+ if (parent) {
100
+ element.parentNode?.removeChild(element);
101
+ parent.appendChild(script);
102
+ } else {
103
+ element.parentNode?.replaceChild(script, element);
104
+ }
105
+ } else {
106
+ for (let i = 0; i < node.childNodes.length; i++) {
107
+ const inner = node.childNodes[i];
108
+ await replaceScriptsAndMove(window, CSPnonce, inner, null);
109
+ }
110
+ if (parent != null) {
111
+ parent.appendChild(node);
112
+ }
113
+ }
114
+}
115
+
116
+function mergeOptions(options: Object, defaultOptions: Object): Object {
117
+ return {
118
+ ...defaultOptions,
119
+ ...options,
120
+ };
121
+}
122
+
123
+export {replaceScriptsAndMove, mergeOptions};
packages/shared/ReactFeatureFlags.js
+2
@@ -128,6 +128,8 @@ export const enableUseMemoCacheHook = __EXPERIMENTAL__;
128
export const enableUseEventHook = __EXPERIMENTAL__;
129
130
// Test in www before enabling in open source.
131
+// Enables DOM-server to stream its instruction set as data-attributes
132
+// (handled with an MutationObserver) instead of inline-scripts
133
export const enableFizzExternalRuntime = false;
134
135
// -----------------------------------------------------------------------------