@samitouri / QOS-React-2 / commits / 154b85213a

[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