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