@samitouri / QOS-React-2 / commits / 579c008a75

[Fizz/Flight] pipeToNodeWritable(..., writable).startWriting() -> renderToPipeableStream(...).pipe(writable) (#22450)

* Rename pipeToNodeWritable to renderToNodePipe * Add startWriting API to Flight We don't really need it in this case because there's way less reason to delay the stream in Flight. * Pass the destination to startWriting instead of renderToNode * Rename startWriting to pipe This mirrors the ReadableStream API in Node * Error codes * Rename to renderToPipeableStream This mimics the renderToReadableStream API for the browser.

Sebastian Markbåge committed Oct 6, 2021 at 00:31 UTC 579c008a752fae70f670047d0ab687552fd8d160
11 files changed +169 -178
fixtures/flight/server/handler.server.js
+6 -2
@@ -1,6 +1,6 @@
1 'use strict';
2
3 -const {pipeToNodeWritable} = require('react-server-dom-webpack/writer');
3 +const {renderToPipeableStream} = require('react-server-dom-webpack/writer');
4 const {readFile} = require('fs');
5 const {resolve} = require('path');
6 const React = require('react');
@@ -20,7 +20,11 @@ module.exports = function(req, res) {
20 const App = m.default.default || m.default;
21 res.setHeader('Access-Control-Allow-Origin', '*');
22 const moduleMap = JSON.parse(data);
23 - pipeToNodeWritable(React.createElement(App), res, moduleMap);
23 + const {pipe} = renderToPipeableStream(
24 + React.createElement(App),
25 + moduleMap
26 + );
27 + pipe(res);
28 }
29 );
30 });
fixtures/ssr/server/render.js
+13 -17
@@ -1,5 +1,5 @@
1 import React from 'react';
2 -import {pipeToNodeWritable} from 'react-dom/server';
2 +import {renderToPipeableStream} from 'react-dom/server';
3
4 import App from '../src/components/App';
5
@@ -20,22 +20,18 @@ export default function render(url, res) {
20 console.error('Fatal', error);
21 });
22 let didError = false;
23 - const {startWriting, abort} = pipeToNodeWritable(
24 - <App assets={assets} />,
25 - res,
26 - {
27 - onCompleteShell() {
28 - // If something errored before we started streaming, we set the error code appropriately.
29 - res.statusCode = didError ? 500 : 200;
30 - res.setHeader('Content-type', 'text/html');
31 - startWriting();
32 - },
33 - onError(x) {
34 - didError = true;
35 - console.error(x);
36 - },
37 - }
38 - );
23 + const {pipe, abort} = renderToPipeableStream(<App assets={assets} />, {
24 + onCompleteShell() {
25 + // If something errored before we started streaming, we set the error code appropriately.
26 + res.statusCode = didError ? 500 : 200;
27 + res.setHeader('Content-type', 'text/html');
28 + pipe(res);
29 + },
30 + onError(x) {
31 + didError = true;
32 + console.error(x);
33 + },
34 + });
35 // Abandon and switch to client rendering after 5 seconds.
36 // Try lowering this to see the client recover.
37 setTimeout(abort, 5000);
fixtures/ssr2/server/render.js
+3 -4
@@ -8,7 +8,7 @@
8
9 import * as React from 'react';
10 // import {renderToString} from 'react-dom/server';
11 -import {pipeToNodeWritable} from 'react-dom/server';
11 +import {renderToPipeableStream} from 'react-dom/server';
12 import App from '../src/App';
13 import {DataProvider} from '../src/data';
14 import {API_DELAY, ABORT_DELAY} from './delays';
@@ -37,17 +37,16 @@ module.exports = function render(url, res) {
37 });
38 let didError = false;
39 const data = createServerData();
40 - const {startWriting, abort} = pipeToNodeWritable(
40 + const {pipe, abort} = renderToPipeableStream(
41 <DataProvider data={data}>
42 <App assets={assets} />
43 </DataProvider>,
44 - res,
44 {
45 onCompleteShell() {
46 // If something errored before we started streaming, we set the error code appropriately.
47 res.statusCode = didError ? 500 : 200;
48 res.setHeader('Content-type', 'text/html');
50 - startWriting();
49 + pipe(res);
50 },
51 onError(x) {
52 didError = true;
packages/react-dom/npm/server.node.js
+1 -1
@@ -14,4 +14,4 @@ exports.renderToString = l.renderToString;
14 exports.renderToStaticMarkup = l.renderToStaticMarkup;
15 exports.renderToNodeStream = l.renderToNodeStream;
16 exports.renderToStaticNodeStream = l.renderToStaticNodeStream;
17 -exports.pipeToNodeWritable = s.pipeToNodeWritable;
17 +exports.renderToPipeableStream = s.renderToPipeableStream;
packages/react-dom/server.node.js
+2 -2
@@ -36,8 +36,8 @@ export function renderToStaticNodeStream() {
36 );
37 }
38
39 -export function pipeToNodeWritable() {
40 - return require('./src/server/ReactDOMFizzServerNode').pipeToNodeWritable.apply(
39 +export function renderToPipeableStream() {
40 + return require('./src/server/ReactDOMFizzServerNode').renderToPipeableStream.apply(
41 this,
42 arguments,
43 );
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+60 -91
@@ -239,7 +239,7 @@ describe('ReactDOMFizzServer', () => {
239 };
240
241 await act(async () => {
242 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
242 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
243 <div>
244 <div>
245 <Suspense fallback={<Text text="Loading..." />}>
@@ -252,9 +252,8 @@ describe('ReactDOMFizzServer', () => {
252 </Suspense>
253 </div>
254 </div>,
255 - writable,
255 );
257 - startWriting();
256 + pipe(writable);
257 });
258 expect(getVisibleChildren(container)).toEqual(
259 <div>
@@ -304,16 +303,16 @@ describe('ReactDOMFizzServer', () => {
303 }
304
305 await act(async () => {
307 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
306 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
307 <App isClient={false} />,
309 - writable,
308 +
309 {
310 onError(x) {
311 loggedErrors.push(x);
312 },
313 },
314 );
316 - startWriting();
315 + pipe(writable);
316 });
317 expect(loggedErrors).toEqual([]);
318
@@ -356,15 +355,14 @@ describe('ReactDOMFizzServer', () => {
355 });
356
357 await act(async () => {
359 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
358 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
359 <div>
360 <Suspense fallback={<Text text="Loading..." />}>
361 {lazyElement}
362 </Suspense>
363 </div>,
365 - writable,
364 );
367 - startWriting();
365 + pipe(writable);
366 });
367 expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
368 await act(async () => {
@@ -396,16 +394,16 @@ describe('ReactDOMFizzServer', () => {
394 }
395
396 await act(async () => {
399 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
397 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
398 <App isClient={false} />,
401 - writable,
399 +
400 {
401 onError(x) {
402 loggedErrors.push(x);
403 },
404 },
405 );
408 - startWriting();
406 + pipe(writable);
407 });
408 expect(loggedErrors).toEqual([]);
409
@@ -441,15 +439,14 @@ describe('ReactDOMFizzServer', () => {
439 // @gate experimental
440 it('should asynchronously load the suspense boundary', async () => {
441 await act(async () => {
444 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
442 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
443 <div>
444 <Suspense fallback={<Text text="Loading..." />}>
445 <AsyncText text="Hello World" />
446 </Suspense>
447 </div>,
450 - writable,
448 );
452 - startWriting();
449 + pipe(writable);
450 });
451 expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
452 await act(async () => {
@@ -475,11 +472,8 @@ describe('ReactDOMFizzServer', () => {
472 }
473
474 await act(async () => {
478 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
479 - <App />,
480 - writable,
481 - );
482 - startWriting();
475 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
476 + pipe(writable);
477 });
478
479 // We're still showing a fallback.
@@ -550,16 +544,16 @@ describe('ReactDOMFizzServer', () => {
544
545 // We originally suspend the boundary and start streaming the loading state.
546 await act(async () => {
553 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
547 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
548 <App />,
555 - writable,
549 +
550 {
551 onError(x) {
552 loggedErrors.push(x);
553 },
554 },
555 );
562 - startWriting();
556 + pipe(writable);
557 });
558
559 // We're still showing a fallback.
@@ -633,11 +627,10 @@ describe('ReactDOMFizzServer', () => {
627
628 // We originally suspend the boundary and start streaming the loading state.
629 await act(async () => {
636 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
630 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
631 <App showMore={false} />,
638 - writable,
632 );
640 - startWriting();
633 + pipe(writable);
634 });
635
636 const root = ReactDOM.createRoot(container, {hydrate: true});
@@ -701,8 +694,8 @@ describe('ReactDOMFizzServer', () => {
694
695 let controls;
696 await act(async () => {
704 - controls = ReactDOMFizzServer.pipeToNodeWritable(<App />, writable);
705 - controls.startWriting();
697 + controls = ReactDOMFizzServer.renderToPipeableStream(<App />);
698 + controls.pipe(writable);
699 });
700
701 // We're still showing a fallback.
@@ -753,7 +746,7 @@ describe('ReactDOMFizzServer', () => {
746 };
747
748 await act(async () => {
756 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
749 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
750 // We use two nested boundaries to flush out coverage of an old reentrancy bug.
751 <Suspense fallback="Loading...">
752 <Suspense fallback={<Text text="Loading A..." />}>
@@ -765,12 +758,11 @@ describe('ReactDOMFizzServer', () => {
758 </>
759 </Suspense>
760 </Suspense>,
768 - writableA,
761 {
762 identifierPrefix: 'A_',
763 onCompleteShell() {
764 writableA.write('<div id="container-A">');
773 - startWriting();
765 + pipe(writableA);
766 writableA.write('</div>');
767 },
768 },
@@ -778,19 +770,18 @@ describe('ReactDOMFizzServer', () => {
770 });
771
772 await act(async () => {
781 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
773 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
774 <Suspense fallback={<Text text="Loading B..." />}>
775 <Text text="This will show B: " />
776 <div>
777 <AsyncText text="B" />
778 </div>
779 </Suspense>,
788 - writableB,
780 {
781 identifierPrefix: 'B_',
782 onCompleteShell() {
783 writableB.write('<div id="container-B">');
793 - startWriting();
784 + pipe(writableB);
785 writableB.write('</div>');
786 },
787 },
@@ -876,11 +867,8 @@ describe('ReactDOMFizzServer', () => {
867 }
868
869 await act(async () => {
879 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
880 - <App />,
881 - writable,
882 - );
883 - startWriting();
870 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
871 + pipe(writable);
872 });
873
874 expect(getVisibleChildren(container)).toEqual(
@@ -967,11 +955,8 @@ describe('ReactDOMFizzServer', () => {
955 }
956
957 await act(async () => {
970 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
971 - <App />,
972 - writable,
973 - );
974 - startWriting();
958 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
959 + pipe(writable);
960 });
961
962 expect(getVisibleChildren(container)).toEqual(
@@ -1024,14 +1009,14 @@ describe('ReactDOMFizzServer', () => {
1009 }
1010
1011 await act(async () => {
1027 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1012 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1013 <App />,
1029 - writable,
1014 +
1015 {
1016 namespaceURI: 'http://www.w3.org/2000/svg',
1017 onCompleteShell() {
1018 writable.write('<svg>');
1034 - startWriting();
1019 + pipe(writable);
1020 writable.write('</svg>');
1021 },
1022 },
@@ -1111,11 +1096,8 @@ describe('ReactDOMFizzServer', () => {
1096
1097 try {
1098 await act(async () => {
1114 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1115 - <A />,
1116 - writable,
1117 - );
1118 - startWriting();
1099 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<A />);
1100 + pipe(writable);
1101 });
1102
1103 expect(getVisibleChildren(container)).toEqual(
@@ -1213,7 +1195,7 @@ describe('ReactDOMFizzServer', () => {
1195 }
1196
1197 await act(async () => {
1216 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1198 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1199 <TestProvider ctx="A">
1200 <div>
1201 <Suspense fallback={[<Text text="Loading: " />, <TestConsumer />]}>
@@ -1224,9 +1206,8 @@ describe('ReactDOMFizzServer', () => {
1206 </Suspense>
1207 </div>
1208 </TestProvider>,
1227 - writable,
1209 );
1229 - startWriting();
1210 + pipe(writable);
1211 });
1212 expect(getVisibleChildren(container)).toEqual(
1213 <div>
@@ -1272,7 +1253,7 @@ describe('ReactDOMFizzServer', () => {
1253 }
1254
1255 await act(async () => {
1275 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1256 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1257 <div>
1258 <PrintA />
1259 <div>
@@ -1287,9 +1268,8 @@ describe('ReactDOMFizzServer', () => {
1268 </div>
1269 <PrintA />
1270 </div>,
1290 - writable,
1271 );
1292 - startWriting();
1272 + pipe(writable);
1273 });
1274 expect(getVisibleChildren(container)).toEqual(
1275 <div>
@@ -1335,7 +1315,7 @@ describe('ReactDOMFizzServer', () => {
1315
1316 const loggedErrors = [];
1317 await act(async () => {
1338 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1318 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1319 <div>
1320 <PrintA />
1321 <div>
@@ -1355,14 +1335,14 @@ describe('ReactDOMFizzServer', () => {
1335 </div>
1336 <PrintA />
1337 </div>,
1358 - writable,
1338 +
1339 {
1340 onError(x) {
1341 loggedErrors.push(x);
1342 },
1343 },
1344 );
1365 - startWriting();
1345 + pipe(writable);
1346 });
1347 expect(loggedErrors.length).toBe(1);
1348 expect(loggedErrors[0].message).toEqual('A0.1.1');
@@ -1396,16 +1376,16 @@ describe('ReactDOMFizzServer', () => {
1376 const loggedErrors = [];
1377 let controls;
1378 await act(async () => {
1399 - controls = ReactDOMFizzServer.pipeToNodeWritable(
1379 + controls = ReactDOMFizzServer.renderToPipeableStream(
1380 <App isClient={false} />,
1401 - writable,
1381 +
1382 {
1383 onError(x) {
1384 loggedErrors.push(x);
1385 },
1386 },
1387 );
1408 - controls.startWriting();
1388 + controls.pipe(writable);
1389 });
1390
1391 // We're still showing a fallback.
@@ -1456,7 +1436,7 @@ describe('ReactDOMFizzServer', () => {
1436 // @gate experimental
1437 it('should be able to abort the fallback if the main content finishes first', async () => {
1438 await act(async () => {
1459 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1439 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1440 <Suspense fallback={<Text text="Loading Outer" />}>
1441 <div>
1442 <Suspense
@@ -1470,9 +1450,8 @@ describe('ReactDOMFizzServer', () => {
1450 </Suspense>
1451 </div>
1452 </Suspense>,
1473 - writable,
1453 );
1475 - startWriting();
1454 + pipe(writable);
1455 });
1456 expect(getVisibleChildren(container)).toEqual('Loading Outer');
1457 // We should have received a partial segment containing the a partial of the fallback.
@@ -1554,11 +1533,10 @@ describe('ReactDOMFizzServer', () => {
1533 await jest.runAllTimers();
1534
1535 await act(async () => {
1557 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1536 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1537 <App isClient={false} />,
1559 - writable,
1538 );
1561 - startWriting();
1539 + pipe(writable);
1540 });
1541
1542 // Nothing is output since root has a suspense with avoidedThisFallback that hasn't resolved
@@ -1669,18 +1647,18 @@ describe('ReactDOMFizzServer', () => {
1647
1648 const loggedErrors = [];
1649 await act(async () => {
1672 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1650 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1651 <Suspense fallback="Loading...">
1652 <App />
1653 </Suspense>,
1676 - writable,
1654 +
1655 {
1656 onError(x) {
1657 loggedErrors.push(x);
1658 },
1659 },
1660 );
1683 - startWriting();
1661 + pipe(writable);
1662 });
1663 expect(Scheduler).toHaveYielded(['server']);
1664
@@ -1752,18 +1730,18 @@ describe('ReactDOMFizzServer', () => {
1730
1731 const loggedErrors = [];
1732 await act(async () => {
1755 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1733 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
1734 <Suspense fallback="Loading...">
1735 <App />
1736 </Suspense>,
1759 - writable,
1737 +
1738 {
1739 onError(x) {
1740 loggedErrors.push(x);
1741 },
1742 },
1743 );
1766 - startWriting();
1744 + pipe(writable);
1745 });
1746 expect(Scheduler).toHaveYielded(['server']);
1747
@@ -1838,11 +1816,8 @@ describe('ReactDOMFizzServer', () => {
1816 }
1817
1818 await act(async () => {
1841 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1842 - <App />,
1843 - writable,
1844 - );
1845 - startWriting();
1819 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
1820 + pipe(writable);
1821 });
1822 expect(Scheduler).toHaveYielded(['Yay!']);
1823
@@ -1922,11 +1897,8 @@ describe('ReactDOMFizzServer', () => {
1897 }
1898
1899 await act(async () => {
1925 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
1926 - <App />,
1927 - writable,
1928 - );
1929 - startWriting();
1900 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
1901 + pipe(writable);
1902 });
1903 expect(Scheduler).toHaveYielded(['Yay!']);
1904
@@ -1997,11 +1969,8 @@ describe('ReactDOMFizzServer', () => {
1969 }
1970
1971 await act(async () => {
2000 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
2001 - <App />,
2002 - writable,
2003 - );
2004 - startWriting();
1972 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
1973 + pipe(writable);
1974 });
1975 expect(Scheduler).toHaveYielded(['Yay!']);
1976
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
+28 -32
@@ -57,13 +57,12 @@ describe('ReactDOMFizzServer', () => {
57 }
58
59 // @gate experimental
60 - it('should call pipeToNodeWritable', () => {
60 + it('should call renderToPipeableStream', () => {
61 const {writable, output} = getTestWritable();
62 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
62 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
63 <div>hello world</div>,
64 - writable,
64 );
66 - startWriting();
65 + pipe(writable);
66 jest.runAllTimers();
67 expect(output.result).toMatchInlineSnapshot(`"<div>hello world</div>"`);
68 });
@@ -71,13 +70,12 @@ describe('ReactDOMFizzServer', () => {
70 // @gate experimental
71 it('should emit DOCTYPE at the root of the document', () => {
72 const {writable, output} = getTestWritable();
74 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
73 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
74 <html>
75 <body>hello world</body>
76 </html>,
78 - writable,
77 );
80 - startWriting();
78 + pipe(writable);
79 jest.runAllTimers();
80 expect(output.result).toMatchInlineSnapshot(
81 `"<!DOCTYPE html><html><body>hello world</body></html>"`,
@@ -85,18 +83,17 @@ describe('ReactDOMFizzServer', () => {
83 });
84
85 // @gate experimental
88 - it('should start writing after startWriting', () => {
86 + it('should start writing after pipe', () => {
87 const {writable, output} = getTestWritable();
90 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
88 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
89 <div>hello world</div>,
92 - writable,
90 );
91 jest.runAllTimers();
92 // First we write our header.
93 output.result +=
94 '<!doctype html><html><head><title>test</title><head><body>';
95 // Then React starts writing.
99 - startWriting();
96 + pipe(writable);
97 expect(output.result).toMatchInlineSnapshot(
98 `"<!doctype html><html><head><title>test</title><head><body><div>hello world</div>"`,
99 );
@@ -115,13 +112,13 @@ describe('ReactDOMFizzServer', () => {
112 }
113 let isCompleteCalls = 0;
114 const {writable, output} = getTestWritable();
118 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
115 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
116 <div>
117 <Suspense fallback="Loading">
118 <Wait />
119 </Suspense>
120 </div>,
124 - writable,
121 +
122 {
123 onCompleteAll() {
124 isCompleteCalls++;
@@ -144,7 +141,7 @@ describe('ReactDOMFizzServer', () => {
141 output.result +=
142 '<!doctype html><html><head><title>test</title><head><body>';
143 // Then React starts writing.
147 - startWriting();
144 + pipe(writable);
145 expect(output.result).toMatchInlineSnapshot(
146 `"<!doctype html><html><head><title>test</title><head><body><div><!--$-->Done<!-- --><!--/$--></div>"`,
147 );
@@ -154,11 +151,11 @@ describe('ReactDOMFizzServer', () => {
151 it('should error the stream when an error is thrown at the root', async () => {
152 const reportedErrors = [];
153 const {writable, output, completed} = getTestWritable();
157 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
154 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
155 <div>
156 <Throw />
157 </div>,
161 - writable,
158 +
159 {
160 onError(x) {
161 reportedErrors.push(x);
@@ -167,7 +164,7 @@ describe('ReactDOMFizzServer', () => {
164 );
165
166 // The stream is errored once we start writing.
170 - startWriting();
167 + pipe(writable);
168
169 await completed;
170
@@ -181,20 +178,20 @@ describe('ReactDOMFizzServer', () => {
178 it('should error the stream when an error is thrown inside a fallback', async () => {
179 const reportedErrors = [];
180 const {writable, output, completed} = getTestWritable();
184 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
181 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
182 <div>
183 <Suspense fallback={<Throw />}>
184 <InfiniteSuspend />
185 </Suspense>
186 </div>,
190 - writable,
187 +
188 {
189 onError(x) {
190 reportedErrors.push(x);
191 },
192 },
193 );
197 - startWriting();
194 + pipe(writable);
195
196 await completed;
197
@@ -207,20 +204,20 @@ describe('ReactDOMFizzServer', () => {
204 it('should not error the stream when an error is thrown inside suspense boundary', async () => {
205 const reportedErrors = [];
206 const {writable, output, completed} = getTestWritable();
210 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
207 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
208 <div>
209 <Suspense fallback={<div>Loading</div>}>
210 <Throw />
211 </Suspense>
212 </div>,
216 - writable,
213 +
214 {
215 onError(x) {
216 reportedErrors.push(x);
217 },
218 },
219 );
223 - startWriting();
220 + pipe(writable);
221
222 await completed;
223
@@ -242,13 +239,12 @@ describe('ReactDOMFizzServer', () => {
239 function Content() {
240 return 'Hi';
241 }
245 - const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
242 + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
243 <Suspense fallback={<Fallback />}>
244 <Content />
245 </Suspense>,
249 - writable,
246 );
251 - startWriting();
247 + pipe(writable);
248
249 await completed;
250
@@ -261,20 +257,20 @@ describe('ReactDOMFizzServer', () => {
257 it('should be able to complete by aborting even if the promise never resolves', async () => {
258 let isCompleteCalls = 0;
259 const {writable, output, completed} = getTestWritable();
264 - const {startWriting, abort} = ReactDOMFizzServer.pipeToNodeWritable(
260 + const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(
261 <div>
262 <Suspense fallback={<div>Loading</div>}>
263 <InfiniteSuspend />
264 </Suspense>
265 </div>,
270 - writable,
266 +
267 {
268 onCompleteAll() {
269 isCompleteCalls++;
270 },
271 },
272 );
277 - startWriting();
273 + pipe(writable);
274
275 jest.runAllTimers();
276
@@ -294,7 +290,7 @@ describe('ReactDOMFizzServer', () => {
290 it('should be able to complete by abort when the fallback is also suspended', async () => {
291 let isCompleteCalls = 0;
292 const {writable, output, completed} = getTestWritable();
297 - const {startWriting, abort} = ReactDOMFizzServer.pipeToNodeWritable(
293 + const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(
294 <div>
295 <Suspense fallback="Loading">
296 <Suspense fallback={<InfiniteSuspend />}>
@@ -302,14 +298,14 @@ describe('ReactDOMFizzServer', () => {
298 </Suspense>
299 </Suspense>
300 </div>,
305 - writable,
301 +
302 {
303 onCompleteAll() {
304 isCompleteCalls++;
305 },
306 },
307 );
312 - startWriting();
308 + pipe(writable);
309
310 jest.runAllTimers();
311
packages/react-dom/src/server/ReactDOMFizzServerNode.js
+8 -6
@@ -41,7 +41,7 @@ type Controls = {|
41 // Cancel any pending I/O and put anything remaining into
42 // client rendered mode.
43 abort(): void,
44 - startWriting(): void,
44 + pipe<T: Writable>(destination: T): T,
45 |};
46
47 function createRequestImpl(children: ReactNodeList, options: void | Options) {
@@ -56,22 +56,24 @@ function createRequestImpl(children: ReactNodeList, options: void | Options) {
56 );
57 }
58
59 -function pipeToNodeWritable(
59 +function renderToPipeableStream(
60 children: ReactNodeList,
61 - destination: Writable,
61 options?: Options,
62 ): Controls {
63 const request = createRequestImpl(children, options);
64 let hasStartedFlowing = false;
65 startWork(request);
66 return {
68 - startWriting() {
67 + pipe<T: Writable>(destination: T): T {
68 if (hasStartedFlowing) {
70 - return;
69 + throw new Error(
70 + 'React currently only supports piping to one writable stream.',
71 + );
72 }
73 hasStartedFlowing = true;
74 startFlowing(request, destination);
75 destination.on('drain', createDrainHandler(destination, request));
76 + return destination;
77 },
78 abort() {
79 abort(request);
@@ -79,4 +81,4 @@ function pipeToNodeWritable(
81 };
82 }
83
82 -export {pipeToNodeWritable, ReactVersion as version};
84 +export {renderToPipeableStream, ReactVersion as version};
packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
+21 -6
@@ -25,20 +25,35 @@ type Options = {
25 onError?: (error: mixed) => void,
26 };
27
28 -function pipeToNodeWritable(
28 +type Controls = {|
29 + pipe<T: Writable>(destination: T): T,
30 +|};
31 +
32 +function renderToPipeableStream(
33 model: ReactModel,
30 - destination: Writable,
34 webpackMap: BundlerConfig,
35 options?: Options,
33 -): void {
36 +): Controls {
37 const request = createRequest(
38 model,
39 webpackMap,
40 options ? options.onError : undefined,
41 );
42 + let hasStartedFlowing = false;
43 startWork(request);
40 - startFlowing(request, destination);
41 - destination.on('drain', createDrainHandler(destination, request));
44 + return {
45 + pipe<T: Writable>(destination: T): T {
46 + if (hasStartedFlowing) {
47 + throw new Error(
48 + 'React currently only supports piping to one writable stream.',
49 + );
50 + }
51 + hasStartedFlowing = true;
52 + startFlowing(request, destination);
53 + destination.on('drain', createDrainHandler(destination, request));
54 + return destination;
55 + },
56 + };
57 }
58
44 -export {pipeToNodeWritable};
59 +export {renderToPipeableStream};
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+25 -16
@@ -57,8 +57,8 @@ describe('ReactFlightDOM', () => {
57 },
58 });
59 return {
60 - writable,
60 readable,
61 + writable,
62 };
63 }
64
@@ -113,7 +113,11 @@ describe('ReactFlightDOM', () => {
113 }
114
115 const {writable, readable} = getTestStream();
116 - ReactServerDOMWriter.pipeToNodeWritable(<App />, writable, webpackMap);
116 + const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
117 + <App />,
118 + webpackMap,
119 + );
120 + pipe(writable);
121 const response = ReactServerDOMReader.createFromReadableStream(readable);
122 await waitForSuspense(() => {
123 const model = response.readRoot();
@@ -162,11 +166,11 @@ describe('ReactFlightDOM', () => {
166 }
167
168 const {writable, readable} = getTestStream();
165 - ReactServerDOMWriter.pipeToNodeWritable(
169 + const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
170 <RootModel />,
167 - writable,
171 webpackMap,
172 );
173 + pipe(writable);
174 const response = ReactServerDOMReader.createFromReadableStream(readable);
175
176 const container = document.createElement('div');
@@ -200,11 +204,11 @@ describe('ReactFlightDOM', () => {
204 }
205
206 const {writable, readable} = getTestStream();
203 - ReactServerDOMWriter.pipeToNodeWritable(
207 + const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
208 <RootModel />,
205 - writable,
209 webpackMap,
210 );
211 + pipe(writable);
212 const response = ReactServerDOMReader.createFromReadableStream(readable);
213
214 const container = document.createElement('div');
@@ -236,11 +240,11 @@ describe('ReactFlightDOM', () => {
240 }
241
242 const {writable, readable} = getTestStream();
239 - ReactServerDOMWriter.pipeToNodeWritable(
243 + const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
244 <RootModel />,
241 - writable,
245 webpackMap,
246 );
247 + pipe(writable);
248 const response = ReactServerDOMReader.createFromReadableStream(readable);
249
250 const container = document.createElement('div');
@@ -371,11 +375,16 @@ describe('ReactFlightDOM', () => {
375 }
376
377 const {writable, readable} = getTestStream();
374 - ReactServerDOMWriter.pipeToNodeWritable(model, writable, webpackMap, {
375 - onError(x) {
376 - reportedErrors.push(x);
378 + const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
379 + model,
380 + webpackMap,
381 + {
382 + onError(x) {
383 + reportedErrors.push(x);
384 + },
385 },
378 - });
386 + );
387 + pipe(writable);
388 const response = ReactServerDOMReader.createFromReadableStream(readable);
389
390 const container = document.createElement('div');
@@ -481,11 +490,11 @@ describe('ReactFlightDOM', () => {
490 const root = ReactDOM.createRoot(container);
491
492 const stream1 = getTestStream();
484 - ReactServerDOMWriter.pipeToNodeWritable(
493 + const {pipe} = ReactServerDOMWriter.renderToPipeableStream(
494 <App color="red" />,
486 - stream1.writable,
495 webpackMap,
496 );
497 + pipe(stream1.writable);
498 const response1 = ReactServerDOMReader.createFromReadableStream(
499 stream1.readable,
500 );
@@ -509,11 +518,11 @@ describe('ReactFlightDOM', () => {
518 inputB.value = 'goodbye';
519
520 const stream2 = getTestStream();
512 - ReactServerDOMWriter.pipeToNodeWritable(
521 + const {pipe: pipe2} = ReactServerDOMWriter.renderToPipeableStream(
522 <App color="blue" />,
514 - stream2.writable,
523 webpackMap,
524 );
525 + pipe2(stream2.writable);
526 const response2 = ReactServerDOMReader.createFromReadableStream(
527 stream2.readable,
528 );
scripts/error-codes/codes.json
+2 -1
@@ -401,5 +401,6 @@
401 "413": "Expected finished root and lanes to be set. This is a bug in React.",
402 "414": "Did not expect this call in production. This is a bug in React. Please file an issue.",
403 "415": "Error parsing the data. It's probably an error code or network corruption.",
404 - "416": "This environment don't support binary chunks."
404 + "416": "This environment don't support binary chunks.",
405 + "417": "React currently only supports piping to one writable stream."
406 }