@samitouri / QOS-React / commits / 6883d79445

[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 // -----------------------------------------------------------------------------