[Fizz] Expose a method to explicitly start writing to a Node stream (#21028)
* Expose an explicit point when to start writing in the Node API * Add a previously failing test
Sebastian Markbåge committed
Mar 18, 2021 at 15:43 UTC
154b85213a65377a67ac4f7a4a39116024bc3028
5 files changed
+51
-10
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+8
-2
@@ -196,7 +196,7 @@ describe('ReactDOMFizzServer', () => {
196
// @gate experimental
197
it('should asynchronously load the suspense boundary', async () => {
198
await act(async () => {
199
- ReactDOMFizzServer.pipeToNodeWritable(
199
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
200
<div>
201
<Suspense fallback={<Text text="Loading..." />}>
202
<AsyncText text="Hello World" />
@@ -204,6 +204,7 @@ describe('ReactDOMFizzServer', () => {
204
</div>,
205
writable,
206
);
207
+ startWriting();
208
});
209
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
210
await act(async () => {
@@ -229,7 +230,11 @@ describe('ReactDOMFizzServer', () => {
230
}
231
232
await act(async () => {
232
- ReactDOMFizzServer.pipeToNodeWritable(<App />, writable);
233
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
234
+ <App />,
235
+ writable,
236
+ );
237
+ startWriting();
238
});
239
240
// We're still showing a fallback.
@@ -281,6 +286,7 @@ describe('ReactDOMFizzServer', () => {
286
let controls;
287
await act(async () => {
288
controls = ReactDOMFizzServer.pipeToNodeWritable(<App />, writable);
289
+ controls.startWriting();
290
});
291
292
// We're still showing a fallback.
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
+33
-5
@@ -59,11 +59,33 @@ describe('ReactDOMFizzServer', () => {
59
// @gate experimental
60
it('should call pipeToNodeWritable', () => {
61
const {writable, output} = getTestWritable();
62
- ReactDOMFizzServer.pipeToNodeWritable(<div>hello world</div>, writable);
62
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
63
+ <div>hello world</div>,
64
+ writable,
65
+ );
66
+ startWriting();
67
jest.runAllTimers();
68
expect(output.result).toBe('<div>hello world</div>');
69
});
70
71
+ // @gate experimental
72
+ it('should start writing after startWriting', () => {
73
+ const {writable, output} = getTestWritable();
74
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
75
+ <div>hello world</div>,
76
+ writable,
77
+ );
78
+ jest.runAllTimers();
79
+ // First we write our header.
80
+ output.result +=
81
+ '<!doctype html><html><head><title>test</title><head><body>';
82
+ // Then React starts writing.
83
+ startWriting();
84
+ expect(output.result).toBe(
85
+ '<!doctype html><html><head><title>test</title><head><body><div>hello world</div>',
86
+ );
87
+ });
88
+
89
// @gate experimental
90
it('should error the stream when an error is thrown at the root', async () => {
91
const {writable, output, completed} = getTestWritable();
@@ -74,6 +96,8 @@ describe('ReactDOMFizzServer', () => {
96
writable,
97
);
98
99
+ // The stream is errored even if we haven't started writing.
100
+
101
await completed;
102
103
expect(output.error).toBe(theError);
@@ -83,7 +107,7 @@ describe('ReactDOMFizzServer', () => {
107
// @gate experimental
108
it('should error the stream when an error is thrown inside a fallback', async () => {
109
const {writable, output, completed} = getTestWritable();
86
- ReactDOMFizzServer.pipeToNodeWritable(
110
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
111
<div>
112
<Suspense fallback={<Throw />}>
113
<InfiniteSuspend />
@@ -91,6 +115,7 @@ describe('ReactDOMFizzServer', () => {
115
</div>,
116
writable,
117
);
118
+ startWriting();
119
120
await completed;
121
@@ -101,7 +126,7 @@ describe('ReactDOMFizzServer', () => {
126
// @gate experimental
127
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
128
const {writable, output, completed} = getTestWritable();
104
- ReactDOMFizzServer.pipeToNodeWritable(
129
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
130
<div>
131
<Suspense fallback={<div>Loading</div>}>
132
<Throw />
@@ -109,6 +134,7 @@ describe('ReactDOMFizzServer', () => {
134
</div>,
135
writable,
136
);
137
+ startWriting();
138
139
await completed;
140
@@ -128,12 +154,13 @@ describe('ReactDOMFizzServer', () => {
154
function Content() {
155
return 'Hi';
156
}
131
- ReactDOMFizzServer.pipeToNodeWritable(
157
+ const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
158
<Suspense fallback={<Fallback />}>
159
<Content />
160
</Suspense>,
161
writable,
162
);
163
+ startWriting();
164
165
await completed;
166
@@ -145,7 +172,7 @@ describe('ReactDOMFizzServer', () => {
172
// @gate experimental
173
it('should be able to complete by aborting even if the promise never resolves', async () => {
174
const {writable, output, completed} = getTestWritable();
148
- const {abort} = ReactDOMFizzServer.pipeToNodeWritable(
175
+ const {startWriting, abort} = ReactDOMFizzServer.pipeToNodeWritable(
176
<div>
177
<Suspense fallback={<div>Loading</div>}>
178
<InfiniteSuspend />
@@ -153,6 +180,7 @@ describe('ReactDOMFizzServer', () => {
180
</div>,
181
writable,
182
);
183
+ startWriting();
184
185
jest.runAllTimers();
186
packages/react-dom/src/server/ReactDOMFizzServerNode.js
+9
-1
@@ -32,9 +32,17 @@ function pipeToNodeWritable(
32
destination: Writable,
33
): Controls {
34
const request = createRequest(children, destination);
35
- destination.on('drain', createDrainHandler(destination, request));
35
+ let hasStartedFlowing = false;
36
startWork(request);
37
return {
38
+ startWriting() {
39
+ if (hasStartedFlowing) {
40
+ return;
41
+ }
42
+ hasStartedFlowing = true;
43
+ startFlowing(request);
44
+ destination.on('drain', createDrainHandler(destination, request));
45
+ },
46
abort() {
47
abort(request);
48
},
packages/react-noop-renderer/src/ReactNoopServer.js
+1
@@ -222,6 +222,7 @@ function render(children: React$Element<any>): Destination {
222
};
223
const request = ReactNoopServer.createRequest(children, destination);
224
ReactNoopServer.startWork(request);
225
+ ReactNoopServer.startFlowing(request);
226
return destination;
227
}
228
packages/react-server/src/ReactFizzServer.js
-2
@@ -894,8 +894,6 @@ function flushCompletedQueues(request: Request): void {
894
// This would put all waiting boundaries into client-only mode.
895
896
export function startWork(request: Request): void {
897
- // TODO: Don't automatically start flowing. Expose an explicit signal. Auto-start once everything is done.
898
- request.status = FLOWING;
897
scheduleWork(() => performWork(request));
898
}
899