Skip to content

Fix streaming SSR in react-dom/server.browser #22889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions fixtures/fizz-ssr-browser-streaming/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8">
<title>Fizz Streaming Example</title>
</head>
<body>
<h1>Fizz Example</h1>
<div id="container">
<p>
To install React, follow the instructions on
<a href="https://github.com/facebook/react/">GitHub</a>.
</p>
<p>
If you can see this, React is <strong>not</strong> working right.
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
</p>
</div>
<script src="../../build/node_modules/react/umd/react.development.js"></script>
<script src="../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
<script type="text/babel">
let controller = new AbortController();
let serverData = data();
let stream = ReactDOMServer.renderToReadableStream(
<html>
<body>
<p>This is successful if you see at least one "Success" below:</p>

{/**
* With a single Suspense boundary, you'll probably see this error in the browser console:
* "Failed to execute 'close' on 'ReadableStreamDefaultController': Cannot close a readable stream that has already been requested to be closed"
*/}
<React.Suspense fallback="Loading...">
<SuspendedData />
</React.Suspense>
{/**
* If you uncomment the following to add another Suspense boundary, you'll probably see this error in the browser console:
* "Aborted, errored or already flushed boundaries should not be flushed again. This is a bug in React."
*/}
{/**<React.Suspense fallback="Loading more...">
<SuspendedData />
</React.Suspense>*/}

</body>
</html>,
{
signal: controller.signal,
}
);
let response = new Response(stream, {
headers: {'Content-Type': 'text/html'},
});
display(response);

function SuspendedData() {
const message = useData();

return <p>{message}</p>;
}

function useData() {
serverData.read();

return 'Success';
}

function data() {
let done = false;
let promise = null;
return {
read() {
if (done) {
return;
}
if (promise) {
throw promise;
}
promise = new Promise((resolve) => {
setTimeout(() => {
done = true;
promise = null;
resolve();
}, 1000);
});
throw promise;
},
};
}

async function display(responseToDisplay) {
let iframe = document.createElement('iframe');
let container = document.getElementById('container');
container.innerHTML = '';
container.appendChild(iframe);

let reader = responseToDisplay.body.getReader();
let done = false;
let decoder = new TextDecoder();
let debugOutput = '';

try {
while (!done) {
const chunk = await reader.read();
done = chunk.done;

let output = decoder.decode(chunk.value);
iframe.contentWindow.document.write(output);
debugOutput += output;
}
} catch (e) {
//
} finally {
iframe.contentWindow.document.close();

console.log(
`DEBUG OUTPUT:\n\n` +
`👀 Notice the invalid HTML chunk '<div hidden id="<div' 👀 \n\n` +
debugOutput
);
}
}
</script>
</body>
</html>
9 changes: 8 additions & 1 deletion packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const COMPLETED = 1;
const FLUSHED = 2;
const ABORTED = 3;
const ERRORED = 4;
const FLUSHING = 5;

type Root = null;

Expand All @@ -178,7 +179,7 @@ export opaque type Request = {
destination: null | Destination,
+responseState: ResponseState,
+progressiveChunkSize: number,
status: 0 | 1 | 2,
status: 0 | 1 | 2 | 5,
fatalError: mixed,
nextSegmentId: number,
allPendingTasks: number, // when it reaches zero, we can close the connection.
Expand Down Expand Up @@ -1820,6 +1821,11 @@ function flushCompletedQueues(
request: Request,
destination: Destination,
): void {
if (request.status === FLUSHING) {
return;
}
request.status = FLUSHING;

beginWriting(destination);
try {
// The structure of this is to go through each queue one by one and write
Expand Down Expand Up @@ -1907,6 +1913,7 @@ function flushCompletedQueues(
} finally {
completeWriting(destination);
flushBuffered(destination);
request.status = OPEN;
if (
request.allPendingTasks === 0 &&
request.pingedTasks.length === 0 &&
Expand Down