Files
react/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js
T
Sebastian Markbåge cb1e7b1c6c Move onCompleteAll to .allReady Promise (#24025)
* Move onCompleteAll to .allReady Promise

The onCompleteAll callback can sometimes resolve before the promise that
returns the stream which is tough to coordinate. A more idiomatic API
for a one shot event is a Promise.

That way the way you render for SEO or SSG is:

const stream = await renderToReadableStream(...);
await stream.readyAll;
respondWith(stream);

Ideally this should be a sub-class of ReadableStream but we don't yet
compile these to ES6 and they'd had to be to native class to subclass
a native stream.

I have other ideas for overriding the .tee() method in a subclass anyway.
So this is inline with that strategy.

* Reject the Promise on fatal errors
2022-03-03 12:46:12 -05:00

131 lines
3.0 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import ReactVersion from 'shared/ReactVersion';
import type {ReactNodeList} from 'shared/ReactTypes';
import {
createRequest,
startWork,
startFlowing,
abort,
} from 'react-server/src/ReactFizzServer';
import {
createResponseState,
createRootFormatContext,
} from './ReactDOMServerLegacyFormatConfig';
type ServerOptions = {
identifierPrefix?: string,
};
function onError() {
// Non-fatal errors are ignored.
}
function renderToStringImpl(
children: ReactNodeList,
options: void | ServerOptions,
generateStaticMarkup: boolean,
): string {
let didFatal = false;
let fatalError = null;
let result = '';
const destination = {
push(chunk) {
if (chunk !== null) {
result += chunk;
}
return true;
},
destroy(error) {
didFatal = true;
fatalError = error;
},
};
let readyToStream = false;
function onCompleteShell() {
readyToStream = true;
}
const request = createRequest(
children,
createResponseState(
generateStaticMarkup,
options ? options.identifierPrefix : undefined,
),
createRootFormatContext(),
Infinity,
onError,
undefined,
onCompleteShell,
undefined,
undefined,
);
startWork(request);
// If anything suspended and is still pending, we'll abort it before writing.
// That way we write only client-rendered boundaries from the start.
abort(request);
startFlowing(request, destination);
if (didFatal) {
throw fatalError;
}
if (!readyToStream) {
// Note: This error message is the one we use on the client. It doesn't
// really make sense here. But this is the legacy server renderer, anyway.
// We're going to delete it soon.
throw new Error(
'A component suspended while responding to synchronous input. This ' +
'will cause the UI to be replaced with a loading indicator. To fix, ' +
'updates that suspend should be wrapped with startTransition.',
);
}
return result;
}
function renderToString(
children: ReactNodeList,
options?: ServerOptions,
): string {
return renderToStringImpl(children, options, false);
}
function renderToStaticMarkup(
children: ReactNodeList,
options?: ServerOptions,
): string {
return renderToStringImpl(children, options, true);
}
function renderToNodeStream() {
throw new Error(
'ReactDOMServer.renderToNodeStream(): The streaming API is not available ' +
'in the browser. Use ReactDOMServer.renderToString() instead.',
);
}
function renderToStaticNodeStream() {
throw new Error(
'ReactDOMServer.renderToStaticNodeStream(): The streaming API is not available ' +
'in the browser. Use ReactDOMServer.renderToStaticMarkup() instead.',
);
}
export {
renderToString,
renderToStaticMarkup,
renderToNodeStream,
renderToStaticNodeStream,
ReactVersion as version,
};