Files
react/packages/react-dom/src/server/ReactDOMLegacyServerImpl.js
T
Sebastian Markbåge 31034b6de7 [Fizz] Split ResponseState/Resources into RenderState/ResumableState (#27268)
This exposes a `resume()` API to go with the `prerender()` (only in
experimental). It doesn't work yet since we don't yet emit the postponed
state so not yet tested.

The main thing this does is rename ResponseState->RenderState and
Resources->ResumableState. We separated out resources into a separate
concept preemptively since it seemed like separate enough but probably
doesn't warrant being a separate concept. The result is that we have a
per RenderState in the Config which is really just temporary state and
things that must be flushed completely in the prerender. Most things
should be ResumableState.

Most options are specified in the `prerender()` and transferred into the
`resume()` but certain options that are unique per request can't be.
Notably `nonce` is special. This means that bootstrap scripts and
external runtime can't use `nonce` in this mode. They need to have a CSP
configured to deal with external scripts, but not inline.

We need to be able to restore state of things that we've already emitted
in the prerender. We could have separate snapshot/restore methods that
does this work when it happens but that means we have to explicitly do
that work. This design is trying to keep to the principle that we just
work with resumable data structures instead so that we're designing for
it with every feature. It also makes restoring faster since it's just
straight into the data structure.

This is not yet a serializable format. That can be done in a follow up.

We also need to vet that each step makes sense. Notably stylesToHoist is
a bit unclear how it'll work.
2023-08-22 15:21:36 -04:00

108 lines
2.5 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and 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 {
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
type ServerOptions = {
identifierPrefix?: string,
};
function onError() {
// Non-fatal errors are ignored.
}
function renderToStringImpl(
children: ReactNodeList,
options: void | ServerOptions,
generateStaticMarkup: boolean,
abortReason: string,
): string {
let didFatal = false;
let fatalError = null;
let result = '';
const destination = {
// $FlowFixMe[missing-local-annot]
push(chunk) {
if (chunk !== null) {
result += chunk;
}
return true;
},
// $FlowFixMe[missing-local-annot]
destroy(error) {
didFatal = true;
fatalError = error;
},
};
let readyToStream = false;
function onShellReady() {
readyToStream = true;
}
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
const request = createRequest(
children,
resumableState,
createRenderState(resumableState, undefined, generateStaticMarkup),
createRootFormatContext(),
Infinity,
onError,
undefined,
onShellReady,
undefined,
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, abortReason);
startFlowing(request, destination);
if (didFatal && fatalError !== abortReason) {
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;
}
export {renderToStringImpl, ReactVersion as version};