Skip to content

Commit 154b852

Browse files
authored
[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
1 parent cf485e6 commit 154b852

File tree

5 files changed

+51
-10
lines changed

5 files changed

+51
-10
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,15 @@ describe('ReactDOMFizzServer', () => {
196196
// @gate experimental
197197
it('should asynchronously load the suspense boundary', async () => {
198198
await act(async () => {
199-
ReactDOMFizzServer.pipeToNodeWritable(
199+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
200200
<div>
201201
<Suspense fallback={<Text text="Loading..." />}>
202202
<AsyncText text="Hello World" />
203203
</Suspense>
204204
</div>,
205205
writable,
206206
);
207+
startWriting();
207208
});
208209
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
209210
await act(async () => {
@@ -229,7 +230,11 @@ describe('ReactDOMFizzServer', () => {
229230
}
230231

231232
await act(async () => {
232-
ReactDOMFizzServer.pipeToNodeWritable(<App />, writable);
233+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
234+
<App />,
235+
writable,
236+
);
237+
startWriting();
233238
});
234239

235240
// We're still showing a fallback.
@@ -281,6 +286,7 @@ describe('ReactDOMFizzServer', () => {
281286
let controls;
282287
await act(async () => {
283288
controls = ReactDOMFizzServer.pipeToNodeWritable(<App />, writable);
289+
controls.startWriting();
284290
});
285291

286292
// We're still showing a fallback.

packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,33 @@ describe('ReactDOMFizzServer', () => {
5959
// @gate experimental
6060
it('should call pipeToNodeWritable', () => {
6161
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();
6367
jest.runAllTimers();
6468
expect(output.result).toBe('<div>hello world</div>');
6569
});
6670

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+
6789
// @gate experimental
6890
it('should error the stream when an error is thrown at the root', async () => {
6991
const {writable, output, completed} = getTestWritable();
@@ -74,6 +96,8 @@ describe('ReactDOMFizzServer', () => {
7496
writable,
7597
);
7698

99+
// The stream is errored even if we haven't started writing.
100+
77101
await completed;
78102

79103
expect(output.error).toBe(theError);
@@ -83,14 +107,15 @@ describe('ReactDOMFizzServer', () => {
83107
// @gate experimental
84108
it('should error the stream when an error is thrown inside a fallback', async () => {
85109
const {writable, output, completed} = getTestWritable();
86-
ReactDOMFizzServer.pipeToNodeWritable(
110+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
87111
<div>
88112
<Suspense fallback={<Throw />}>
89113
<InfiniteSuspend />
90114
</Suspense>
91115
</div>,
92116
writable,
93117
);
118+
startWriting();
94119

95120
await completed;
96121

@@ -101,14 +126,15 @@ describe('ReactDOMFizzServer', () => {
101126
// @gate experimental
102127
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
103128
const {writable, output, completed} = getTestWritable();
104-
ReactDOMFizzServer.pipeToNodeWritable(
129+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
105130
<div>
106131
<Suspense fallback={<div>Loading</div>}>
107132
<Throw />
108133
</Suspense>
109134
</div>,
110135
writable,
111136
);
137+
startWriting();
112138

113139
await completed;
114140

@@ -128,12 +154,13 @@ describe('ReactDOMFizzServer', () => {
128154
function Content() {
129155
return 'Hi';
130156
}
131-
ReactDOMFizzServer.pipeToNodeWritable(
157+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
132158
<Suspense fallback={<Fallback />}>
133159
<Content />
134160
</Suspense>,
135161
writable,
136162
);
163+
startWriting();
137164

138165
await completed;
139166

@@ -145,14 +172,15 @@ describe('ReactDOMFizzServer', () => {
145172
// @gate experimental
146173
it('should be able to complete by aborting even if the promise never resolves', async () => {
147174
const {writable, output, completed} = getTestWritable();
148-
const {abort} = ReactDOMFizzServer.pipeToNodeWritable(
175+
const {startWriting, abort} = ReactDOMFizzServer.pipeToNodeWritable(
149176
<div>
150177
<Suspense fallback={<div>Loading</div>}>
151178
<InfiniteSuspend />
152179
</Suspense>
153180
</div>,
154181
writable,
155182
);
183+
startWriting();
156184

157185
jest.runAllTimers();
158186

packages/react-dom/src/server/ReactDOMFizzServerNode.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,17 @@ function pipeToNodeWritable(
3232
destination: Writable,
3333
): Controls {
3434
const request = createRequest(children, destination);
35-
destination.on('drain', createDrainHandler(destination, request));
35+
let hasStartedFlowing = false;
3636
startWork(request);
3737
return {
38+
startWriting() {
39+
if (hasStartedFlowing) {
40+
return;
41+
}
42+
hasStartedFlowing = true;
43+
startFlowing(request);
44+
destination.on('drain', createDrainHandler(destination, request));
45+
},
3846
abort() {
3947
abort(request);
4048
},

packages/react-noop-renderer/src/ReactNoopServer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ function render(children: React$Element<any>): Destination {
222222
};
223223
const request = ReactNoopServer.createRequest(children, destination);
224224
ReactNoopServer.startWork(request);
225+
ReactNoopServer.startFlowing(request);
225226
return destination;
226227
}
227228

packages/react-server/src/ReactFizzServer.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,6 @@ function flushCompletedQueues(request: Request): void {
894894
// This would put all waiting boundaries into client-only mode.
895895

896896
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;
899897
scheduleWork(() => performWork(request));
900898
}
901899

0 commit comments

Comments
 (0)