[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.
This commit is contained in:
Sebastian Markbåge 2023-08-22 15:21:36 -04:00 committed by GitHub
parent 86198b9231
commit 31034b6de7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1050 additions and 633 deletions

View File

@ -492,7 +492,6 @@ module.exports = {
ReadableStreamController: 'readonly',
RequestInfo: 'readonly',
RequestOptions: 'readonly',
ResponseState: 'readonly',
StoreAsGlobal: 'readonly',
symbol: 'readonly',
SyntheticEvent: 'readonly',

File diff suppressed because it is too large Load Diff

View File

@ -7,16 +7,10 @@
* @flow
*/
import type {
Resources,
BootstrapScriptDescriptor,
ExternalRuntimeScript,
StreamingFormat,
InstructionState,
} from './ReactFizzConfigDOM';
import type {ResumableState, BoundaryResources} from './ReactFizzConfigDOM';
import {
createResponseState as createResponseStateImpl,
createRenderState as createRenderStateImpl,
pushTextInstance as pushTextInstanceImpl,
pushSegmentFinale as pushSegmentFinaleImpl,
writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl,
@ -37,65 +31,44 @@ import {NotPending} from '../shared/ReactDOMFormActions';
export const isPrimaryRenderer = false;
export type ResponseState = {
export type RenderState = {
// Keep this in sync with ReactFizzConfigDOM
bootstrapChunks: Array<Chunk | PrecomputedChunk>,
placeholderPrefix: PrecomputedChunk,
segmentPrefix: PrecomputedChunk,
boundaryPrefix: string,
idPrefix: string,
nextSuspenseID: number,
streamingFormat: StreamingFormat,
startInlineScript: PrecomputedChunk,
instructions: InstructionState,
externalRuntimeScript: null | ExternalRuntimeScript,
htmlChunks: null | Array<Chunk | PrecomputedChunk>,
headChunks: null | Array<Chunk | PrecomputedChunk>,
hasBody: boolean,
charsetChunks: Array<Chunk | PrecomputedChunk>,
preconnectChunks: Array<Chunk | PrecomputedChunk>,
preloadChunks: Array<Chunk | PrecomputedChunk>,
hoistableChunks: Array<Chunk | PrecomputedChunk>,
boundaryResources: ?BoundaryResources,
stylesToHoist: boolean,
// This is an extra field for the legacy renderer
generateStaticMarkup: boolean,
};
export function createResponseState(
resources: Resources,
export function createRenderState(
resumableState: ResumableState,
nonce: string | void,
generateStaticMarkup: boolean,
identifierPrefix: string | void,
externalRuntimeConfig: string | BootstrapScriptDescriptor | void,
): ResponseState {
const responseState = createResponseStateImpl(
resources,
identifierPrefix,
undefined,
undefined,
undefined,
undefined,
externalRuntimeConfig,
);
): RenderState {
const renderState = createRenderStateImpl(resumableState, nonce);
return {
// Keep this in sync with ReactFizzConfigDOM
bootstrapChunks: responseState.bootstrapChunks,
placeholderPrefix: responseState.placeholderPrefix,
segmentPrefix: responseState.segmentPrefix,
boundaryPrefix: responseState.boundaryPrefix,
idPrefix: responseState.idPrefix,
nextSuspenseID: responseState.nextSuspenseID,
streamingFormat: responseState.streamingFormat,
startInlineScript: responseState.startInlineScript,
instructions: responseState.instructions,
externalRuntimeScript: responseState.externalRuntimeScript,
htmlChunks: responseState.htmlChunks,
headChunks: responseState.headChunks,
hasBody: responseState.hasBody,
charsetChunks: responseState.charsetChunks,
preconnectChunks: responseState.preconnectChunks,
preloadChunks: responseState.preloadChunks,
hoistableChunks: responseState.hoistableChunks,
stylesToHoist: responseState.stylesToHoist,
placeholderPrefix: renderState.placeholderPrefix,
segmentPrefix: renderState.segmentPrefix,
boundaryPrefix: renderState.boundaryPrefix,
startInlineScript: renderState.startInlineScript,
htmlChunks: renderState.htmlChunks,
headChunks: renderState.headChunks,
charsetChunks: renderState.charsetChunks,
preconnectChunks: renderState.preconnectChunks,
preloadChunks: renderState.preloadChunks,
hoistableChunks: renderState.hoistableChunks,
boundaryResources: renderState.boundaryResources,
stylesToHoist: renderState.stylesToHoist,
// This is an extra field for the legacy renderer
generateStaticMarkup,
@ -111,7 +84,7 @@ import {
export const doctypeChunk: PrecomputedChunk = stringToPrecomputedChunk('');
export type {
Resources,
ResumableState,
BoundaryResources,
FormatContext,
SuspenseBoundaryID,
@ -137,7 +110,7 @@ export {
writePlaceholder,
writeCompletedRoot,
createRootFormatContext,
createResources,
createResumableState,
createBoundaryResources,
writePreamble,
writeHoistables,
@ -152,29 +125,29 @@ import escapeTextForBrowser from './escapeTextForBrowser';
export function pushTextInstance(
target: Array<Chunk | PrecomputedChunk>,
text: string,
responseState: ResponseState,
renderState: RenderState,
textEmbedded: boolean,
): boolean {
if (responseState.generateStaticMarkup) {
if (renderState.generateStaticMarkup) {
target.push(stringToChunk(escapeTextForBrowser(text)));
return false;
} else {
return pushTextInstanceImpl(target, text, responseState, textEmbedded);
return pushTextInstanceImpl(target, text, renderState, textEmbedded);
}
}
export function pushSegmentFinale(
target: Array<Chunk | PrecomputedChunk>,
responseState: ResponseState,
renderState: RenderState,
lastPushedText: boolean,
textEmbedded: boolean,
): void {
if (responseState.generateStaticMarkup) {
if (renderState.generateStaticMarkup) {
return;
} else {
return pushSegmentFinaleImpl(
target,
responseState,
renderState,
lastPushedText,
textEmbedded,
);
@ -183,31 +156,31 @@ export function pushSegmentFinale(
export function writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
): boolean {
if (responseState.generateStaticMarkup) {
if (renderState.generateStaticMarkup) {
// A completed boundary is done and doesn't need a representation in the HTML
// if we're not going to be hydrating it.
return true;
}
return writeStartCompletedSuspenseBoundaryImpl(destination, responseState);
return writeStartCompletedSuspenseBoundaryImpl(destination, renderState);
}
export function writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
// flushing these error arguments are not currently supported in this legacy streaming format.
errorDigest: ?string,
errorMessage: ?string,
errorComponentStack: ?string,
): boolean {
if (responseState.generateStaticMarkup) {
if (renderState.generateStaticMarkup) {
// A client rendered boundary is done and doesn't need a representation in the HTML
// since we'll never hydrate it. This is arguably an error in static generation.
return true;
}
return writeStartClientRenderedSuspenseBoundaryImpl(
destination,
responseState,
renderState,
errorDigest,
errorMessage,
errorComponentStack,
@ -215,21 +188,21 @@ export function writeStartClientRenderedSuspenseBoundary(
}
export function writeEndCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
): boolean {
if (responseState.generateStaticMarkup) {
if (renderState.generateStaticMarkup) {
return true;
}
return writeEndCompletedSuspenseBoundaryImpl(destination, responseState);
return writeEndCompletedSuspenseBoundaryImpl(destination, renderState);
}
export function writeEndClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
): boolean {
if (responseState.generateStaticMarkup) {
if (renderState.generateStaticMarkup) {
return true;
}
return writeEndClientRenderedSuspenseBoundaryImpl(destination, responseState);
return writeEndClientRenderedSuspenseBoundaryImpl(destination, renderState);
}
export type TransitionStatus = FormStatus;

View File

@ -15,3 +15,6 @@ exports.renderToStaticMarkup = l.renderToStaticMarkup;
exports.renderToNodeStream = l.renderToNodeStream;
exports.renderToStaticNodeStream = l.renderToStaticNodeStream;
exports.renderToReadableStream = s.renderToReadableStream;
if (s.resume) {
exports.resume = s.resume;
}

View File

@ -12,6 +12,9 @@ if (process.env.NODE_ENV === 'production') {
exports.version = b.version;
exports.renderToReadableStream = b.renderToReadableStream;
if (b.resume) {
exports.resume = b.resume;
}
exports.renderToNodeStream = b.renderToNodeStream;
exports.renderToStaticNodeStream = b.renderToStaticNodeStream;
exports.renderToString = l.renderToString;

View File

@ -16,3 +16,6 @@ exports.renderToNodeStream = b.renderToNodeStream;
exports.renderToStaticNodeStream = b.renderToStaticNodeStream;
exports.renderToString = l.renderToString;
exports.renderToStaticMarkup = l.renderToStaticMarkup;
if (b.resume) {
exports.resume = b.resume;
}

View File

@ -15,3 +15,6 @@ exports.renderToStaticMarkup = l.renderToStaticMarkup;
exports.renderToNodeStream = l.renderToNodeStream;
exports.renderToStaticNodeStream = l.renderToStaticNodeStream;
exports.renderToPipeableStream = s.renderToPipeableStream;
if (s.resume) {
exports.resume = s.resume;
}

View File

@ -37,7 +37,14 @@ export function renderToStaticNodeStream() {
}
export function renderToReadableStream() {
return require('./src/server/ReactDOMFizzServerBrowser').renderToReadableStream.apply(
return require('./src/server/react-dom-server.browser').renderToReadableStream.apply(
this,
arguments,
);
}
export function resume() {
return require('./src/server/react-dom-server.browser').resume.apply(
this,
arguments,
);

View File

@ -12,21 +12,21 @@ import ReactVersion from 'shared/ReactVersion';
export {ReactVersion as version};
export function renderToReadableStream() {
return require('./src/server/ReactDOMFizzServerBun').renderToReadableStream.apply(
return require('./src/server/react-dom-server.bun').renderToReadableStream.apply(
this,
arguments,
);
}
export function renderToNodeStream() {
return require('./src/server/ReactDOMFizzServerBun').renderToNodeStream.apply(
return require('./src/server/react-dom-server.bun').renderToNodeStream.apply(
this,
arguments,
);
}
export function renderToStaticNodeStream() {
return require('./src/server/ReactDOMFizzServerBun').renderToStaticNodeStream.apply(
return require('./src/server/react-dom-server.bun').renderToStaticNodeStream.apply(
this,
arguments,
);
@ -45,3 +45,10 @@ export function renderToStaticMarkup() {
arguments,
);
}
export function resume() {
return require('./src/server/react-dom-server.bun').resume.apply(
this,
arguments,
);
}

View File

@ -12,21 +12,21 @@ import ReactVersion from 'shared/ReactVersion';
export {ReactVersion as version};
export function renderToReadableStream() {
return require('./src/server/ReactDOMFizzServerEdge').renderToReadableStream.apply(
return require('./src/server/react-dom-server.edge').renderToReadableStream.apply(
this,
arguments,
);
}
export function renderToNodeStream() {
return require('./src/server/ReactDOMFizzServerEdge').renderToNodeStream.apply(
return require('./src/server/react-dom-server.edge').renderToNodeStream.apply(
this,
arguments,
);
}
export function renderToStaticNodeStream() {
return require('./src/server/ReactDOMFizzServerEdge').renderToStaticNodeStream.apply(
return require('./src/server/react-dom-server.edge').renderToStaticNodeStream.apply(
this,
arguments,
);
@ -45,3 +45,10 @@ export function renderToStaticMarkup() {
arguments,
);
}
export function resume() {
return require('./src/server/react-dom-server.edge').resume.apply(
this,
arguments,
);
}

View File

@ -37,7 +37,14 @@ export function renderToStaticNodeStream() {
}
export function renderToPipeableStream() {
return require('./src/server/ReactDOMFizzServerNode').renderToPipeableStream.apply(
return require('./src/server/react-dom-server.node').renderToPipeableStream.apply(
this,
arguments,
);
}
export function resume() {
return require('./src/server/react-dom-server.node').resume.apply(
this,
arguments,
);

View File

@ -218,12 +218,14 @@ describe('ReactDOMFizzStatic', () => {
);
}
const promise = ReactDOMFizzStatic.prerenderToNodeStreams(<App />);
const promise = ReactDOMFizzStatic.prerenderToNodeStream(<App />);
resolveText('Hello');
const result = await promise;
expect(result.postponed).toBe(null);
await act(async () => {
result.prelude.pipe(writable);
});

View File

@ -47,8 +47,8 @@ describe('ReactDOMFizzStaticNode', () => {
}
// @gate experimental
it('should call prerenderToNodeStreams', async () => {
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
it('should call prerenderToNodeStream', async () => {
const result = await ReactDOMFizzStatic.prerenderToNodeStream(
<div>hello world</div>,
);
const prelude = await readContent(result.prelude);
@ -57,7 +57,7 @@ describe('ReactDOMFizzStaticNode', () => {
// @gate experimental
it('should emit DOCTYPE at the root of the document', async () => {
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
const result = await ReactDOMFizzStatic.prerenderToNodeStream(
<html>
<body>hello world</body>
</html>,
@ -76,7 +76,7 @@ describe('ReactDOMFizzStaticNode', () => {
// @gate experimental
it('should emit bootstrap script src at the end', async () => {
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
const result = await ReactDOMFizzStatic.prerenderToNodeStream(
<div>hello world</div>,
{
bootstrapScriptContent: 'INIT();',
@ -101,7 +101,7 @@ describe('ReactDOMFizzStaticNode', () => {
}
return 'Done';
}
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<Suspense fallback="Loading">
<Wait />
@ -127,7 +127,7 @@ describe('ReactDOMFizzStaticNode', () => {
const reportedErrors = [];
let caughtError = null;
try {
await ReactDOMFizzStatic.prerenderToNodeStreams(
await ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<Throw />
</div>,
@ -149,7 +149,7 @@ describe('ReactDOMFizzStaticNode', () => {
const reportedErrors = [];
let caughtError = null;
try {
await ReactDOMFizzStatic.prerenderToNodeStreams(
await ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<Suspense fallback={<Throw />}>
<InfiniteSuspend />
@ -171,7 +171,7 @@ describe('ReactDOMFizzStaticNode', () => {
// @gate experimental
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
const reportedErrors = [];
const result = await ReactDOMFizzStatic.prerenderToNodeStreams(
const result = await ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<Throw />
@ -193,7 +193,7 @@ describe('ReactDOMFizzStaticNode', () => {
it('should be able to complete by aborting even if the promise never resolves', async () => {
const errors = [];
const controller = new AbortController();
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<InfiniteSuspend />
@ -223,7 +223,7 @@ describe('ReactDOMFizzStaticNode', () => {
it('should reject if aborting before the shell is complete', async () => {
const errors = [];
const controller = new AbortController();
const promise = ReactDOMFizzStatic.prerenderToNodeStreams(
const promise = ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<InfiniteSuspend />
</div>,
@ -262,7 +262,7 @@ describe('ReactDOMFizzStaticNode', () => {
</Suspense>
);
}
const streamPromise = ReactDOMFizzStatic.prerenderToNodeStreams(
const streamPromise = ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<App />
</div>,
@ -291,7 +291,7 @@ describe('ReactDOMFizzStaticNode', () => {
const theReason = new Error('aborted for reasons');
controller.abort(theReason);
const promise = ReactDOMFizzStatic.prerenderToNodeStreams(
const promise = ReactDOMFizzStatic.prerenderToNodeStream(
<div>
<Suspense fallback={<div>Loading</div>}>
<InfiniteSuspend />
@ -342,7 +342,7 @@ describe('ReactDOMFizzStaticNode', () => {
const errors = [];
const controller = new AbortController();
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(<App />, {
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStream(<App />, {
signal: controller.signal,
onError(x) {
errors.push(x);
@ -384,7 +384,7 @@ describe('ReactDOMFizzStaticNode', () => {
const errors = [];
const controller = new AbortController();
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStreams(<App />, {
const resultPromise = ReactDOMFizzStatic.prerenderToNodeStream(<App />, {
signal: controller.signal,
onError(x) {
errors.push(x.message);

View File

@ -7,6 +7,7 @@
* @flow
*/
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -20,8 +21,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +40,14 @@ type Options = {
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
type ResumeOptions = {
nonce?: string,
signal?: AbortSignal,
onError?: (error: mixed) => ?string,
onPostpone?: (reason: string) => void,
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
// TODO: Move to sub-classing ReadableStream.
type ReactDOMServerReadableStream = ReadableStream & {
allReady: Promise<void>,
@ -81,19 +90,18 @@ function renderToReadableStream(
allReady.catch(() => {});
reject(error);
}
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
resumableState,
createRenderState(resumableState, options ? options.nonce : undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
@ -119,4 +127,74 @@ function renderToReadableStream(
});
}
export {renderToReadableStream, ReactVersion as version};
function resume(
children: ReactNodeList,
postponedState: PostponedState,
options?: ResumeOptions,
): Promise<ReactDOMServerReadableStream> {
return new Promise((resolve, reject) => {
let onFatalError;
let onAllReady;
const allReady = new Promise<void>((res, rej) => {
onAllReady = res;
onFatalError = rej;
});
function onShellReady() {
const stream: ReactDOMServerReadableStream = (new ReadableStream(
{
type: 'bytes',
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
abort(request);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
): any);
// TODO: Move to sub-classing ReadableStream.
stream.allReady = allReady;
resolve(stream);
}
function onShellError(error: mixed) {
// If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`.
// However, `allReady` will be rejected by `onFatalError` as well.
// So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`.
allReady.catch(() => {});
reject(error);
}
const request = createRequest(
children,
postponedState.resumableState,
createRenderState(
postponedState.resumableState,
options ? options.nonce : undefined,
),
postponedState.rootFormatContext,
postponedState.progressiveChunkSize,
options ? options.onError : undefined,
onAllReady,
onShellReady,
onShellError,
onFatalError,
options ? options.onPostpone : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal: any).reason);
} else {
const listener = () => {
abort(request, (signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
export {renderToReadableStream, resume, ReactVersion as version};

View File

@ -20,8 +20,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -82,19 +82,18 @@ function renderToReadableStream(
allReady.catch(() => {});
reject(error);
}
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
resumableState,
createRenderState(resumableState, options ? options.nonce : undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,

View File

@ -7,6 +7,7 @@
* @flow
*/
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -20,8 +21,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +40,14 @@ type Options = {
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
type ResumeOptions = {
nonce?: string,
signal?: AbortSignal,
onError?: (error: mixed) => ?string,
onPostpone?: (reason: string) => void,
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
// TODO: Move to sub-classing ReadableStream.
type ReactDOMServerReadableStream = ReadableStream & {
allReady: Promise<void>,
@ -81,19 +90,18 @@ function renderToReadableStream(
allReady.catch(() => {});
reject(error);
}
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
resumableState,
createRenderState(resumableState, options ? options.nonce : undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
@ -119,4 +127,74 @@ function renderToReadableStream(
});
}
export {renderToReadableStream, ReactVersion as version};
function resume(
children: ReactNodeList,
postponedState: PostponedState,
options?: ResumeOptions,
): Promise<ReactDOMServerReadableStream> {
return new Promise((resolve, reject) => {
let onFatalError;
let onAllReady;
const allReady = new Promise<void>((res, rej) => {
onAllReady = res;
onFatalError = rej;
});
function onShellReady() {
const stream: ReactDOMServerReadableStream = (new ReadableStream(
{
type: 'bytes',
pull: (controller): ?Promise<void> => {
startFlowing(request, controller);
},
cancel: (reason): ?Promise<void> => {
abort(request);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
): any);
// TODO: Move to sub-classing ReadableStream.
stream.allReady = allReady;
resolve(stream);
}
function onShellError(error: mixed) {
// If the shell errors the caller of `renderToReadableStream` won't have access to `allReady`.
// However, `allReady` will be rejected by `onFatalError` as well.
// So we need to catch the duplicate, uncatchable fatal error in `allReady` to prevent a `UnhandledPromiseRejection`.
allReady.catch(() => {});
reject(error);
}
const request = createRequest(
children,
postponedState.resumableState,
createRenderState(
postponedState.resumableState,
options ? options.nonce : undefined,
),
postponedState.rootFormatContext,
postponedState.progressiveChunkSize,
options ? options.onError : undefined,
onAllReady,
onShellReady,
onShellError,
onFatalError,
options ? options.onPostpone : undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal: any).reason);
} else {
const listener = () => {
abort(request, (signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
export {renderToReadableStream, resume, ReactVersion as version};

View File

@ -7,7 +7,7 @@
* @flow
*/
import type {Request} from 'react-server/src/ReactFizzServer';
import type {Request, PostponedState} from 'react-server/src/ReactFizzServer';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {Writable} from 'stream';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -23,8 +23,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -53,6 +53,15 @@ type Options = {
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
};
type ResumeOptions = {
nonce?: string,
onShellReady?: () => void,
onShellError?: (error: mixed) => void,
onAllReady?: () => void,
onError?: (error: mixed) => ?string,
onPostpone?: (reason: string) => void,
};
type PipeableStream = {
// Cancel any pending I/O and put anything remaining into
// client rendered mode.
@ -61,19 +70,18 @@ type PipeableStream = {
};
function createRequestImpl(children: ReactNodeList, options: void | Options) {
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
return createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
options ? options.nonce : undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
resumableState,
createRenderState(resumableState, options ? options.nonce : undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
@ -121,4 +129,68 @@ function renderToPipeableStream(
};
}
export {renderToPipeableStream, ReactVersion as version};
function resumeRequestImpl(
children: ReactNodeList,
postponedState: PostponedState,
options: void | ResumeOptions,
) {
return createRequest(
children,
postponedState.resumableState,
createRenderState(
postponedState.resumableState,
options ? options.nonce : undefined,
),
postponedState.rootFormatContext,
postponedState.progressiveChunkSize,
options ? options.onError : undefined,
options ? options.onAllReady : undefined,
options ? options.onShellReady : undefined,
options ? options.onShellError : undefined,
undefined,
options ? options.onPostpone : undefined,
);
}
function resumeToPipeableStream(
children: ReactNodeList,
postponedState: PostponedState,
options?: ResumeOptions,
): PipeableStream {
const request = resumeRequestImpl(children, postponedState, options);
let hasStartedFlowing = false;
startWork(request);
return {
pipe<T: Writable>(destination: T): T {
if (hasStartedFlowing) {
throw new Error(
'React currently only supports piping to one writable stream.',
);
}
hasStartedFlowing = true;
startFlowing(request, destination);
destination.on('drain', createDrainHandler(destination, request));
destination.on(
'error',
createAbortHandler(
request,
'The destination stream errored while writing data.',
),
);
destination.on(
'close',
createAbortHandler(request, 'The destination stream closed early.'),
);
return destination;
},
abort(reason: mixed) {
abort(request, reason);
},
};
}
export {
renderToPipeableStream,
resumeToPipeableStream,
ReactVersion as version,
};

View File

@ -9,6 +9,7 @@
import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import ReactVersion from 'shared/ReactVersion';
@ -17,11 +18,12 @@ import {
startWork,
startFlowing,
abort,
getPostponedState,
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +41,7 @@ type Options = {
};
type StaticResult = {
postponed: null | PostponedState,
prelude: ReadableStream,
};
@ -62,23 +65,23 @@ function prerender(
);
const result = {
postponed: getPostponedState(request),
prelude: stream,
};
resolve(result);
}
const resources = createResources();
const resources = createResumableState(
options ? options.identifierPrefix : undefined,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
createRenderState(resources, undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,

View File

@ -9,6 +9,7 @@
import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import ReactVersion from 'shared/ReactVersion';
@ -17,11 +18,12 @@ import {
startWork,
startFlowing,
abort,
getPostponedState,
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +41,7 @@ type Options = {
};
type StaticResult = {
postponed: null | PostponedState,
prelude: ReadableStream,
};
@ -62,23 +65,23 @@ function prerender(
);
const result = {
postponed: getPostponedState(request),
prelude: stream,
};
resolve(result);
}
const resources = createResources();
const resources = createResumableState(
options ? options.identifierPrefix : undefined,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
createRenderState(resources, undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,

View File

@ -9,6 +9,7 @@
import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import {Writable, Readable} from 'stream';
@ -19,11 +20,12 @@ import {
startWork,
startFlowing,
abort,
getPostponedState,
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -41,6 +43,7 @@ type Options = {
};
type StaticResult = {
postponed: null | PostponedState,
prelude: Readable,
};
@ -60,7 +63,7 @@ function createFakeWritable(readable: any): Writable {
}: any);
}
function prerenderToNodeStreams(
function prerenderToNodeStream(
children: ReactNodeList,
options?: Options,
): Promise<StaticResult> {
@ -76,23 +79,23 @@ function prerenderToNodeStreams(
const writable = createFakeWritable(readable);
const result = {
postponed: getPostponedState(request),
prelude: readable,
};
resolve(result);
}
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
undefined, // nonce is not compatible with prerendered bootstrap scripts
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
resumableState,
createRenderState(resumableState, undefined),
createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined,
@ -118,4 +121,4 @@ function prerenderToNodeStreams(
});
}
export {prerenderToNodeStreams, ReactVersion as version};
export {prerenderToNodeStream, ReactVersion as version};

View File

@ -10,7 +10,6 @@
import ReactVersion from 'shared/ReactVersion';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import {
createRequest,
@ -20,8 +19,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
@ -38,7 +37,6 @@ function renderToStringImpl(
options: void | ServerOptions,
generateStaticMarkup: boolean,
abortReason: string,
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
): string {
let didFatal = false;
let fatalError = null;
@ -62,16 +60,18 @@ function renderToStringImpl(
function onShellReady() {
readyToStream = true;
}
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
generateStaticMarkup,
options ? options.identifierPrefix : undefined,
unstable_externalRuntimeSrc,
),
resumableState,
createRenderState(resumableState, undefined, generateStaticMarkup),
createRootFormatContext(),
Infinity,
onError,

View File

@ -19,8 +19,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
@ -71,15 +71,18 @@ function renderToNodeStreamImpl(
startFlowing(request, destination);
}
const destination = new ReactMarkupReadableStream();
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
false,
options ? options.identifierPrefix : undefined,
),
resumableState,
createRenderState(resumableState, undefined, false),
createRootFormatContext(),
Infinity,
onError,

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './ReactDOMFizzServerBrowser.js';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export {renderToReadableStream, version} from './ReactDOMFizzServerBrowser.js';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './ReactDOMFizzServerBun.js';

View File

@ -0,0 +1,15 @@
/**
* 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
*/
export {
renderToReadableStream,
renderToNodeStream,
renderToStaticNodeStream,
version,
} from './ReactDOMFizzServerBun.js';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './ReactDOMFizzServerEdge.js';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export {renderToReadableStream, version} from './ReactDOMFizzServerEdge.js';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export * from './ReactDOMFizzServerNode.js';

View File

@ -0,0 +1,10 @@
/**
* 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
*/
export {renderToPipeableStream, version} from './ReactDOMFizzServerNode.js';

View File

@ -8,6 +8,6 @@
*/
export {
prerenderToNodeStreams,
prerenderToNodeStream,
version,
} from './src/server/ReactDOMFizzStaticNode';

View File

@ -51,7 +51,7 @@ type Destination = {
stack: Array<Segment | Instance | SuspenseInstance>,
};
type Resources = null;
type RenderState = null;
type BoundaryResources = null;
const POP = Buffer.from('/', 'utf8');
@ -104,7 +104,7 @@ const ReactNoopServer = ReactFizzServer({
pushTextInstance(
target: Array<Uint8Array>,
text: string,
responseState: ResponseState,
renderState: RenderState,
textEmbedded: boolean,
): boolean {
const textInstance: TextInstance = {
@ -140,21 +140,21 @@ const ReactNoopServer = ReactFizzServer({
// This is a noop in ReactNoop
pushSegmentFinale(
target: Array<Uint8Array>,
responseState: ResponseState,
renderState: RenderState,
lastPushedText: boolean,
textEmbedded: boolean,
): void {},
writeCompletedRoot(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
): boolean {
return true;
},
writePlaceholder(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
id: number,
): boolean {
const parent = destination.stack[destination.stack.length - 1];
@ -166,7 +166,7 @@ const ReactNoopServer = ReactFizzServer({
writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
suspenseInstance: SuspenseInstance,
): boolean {
suspenseInstance.state = 'complete';
@ -176,7 +176,7 @@ const ReactNoopServer = ReactFizzServer({
},
writeStartPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
suspenseInstance: SuspenseInstance,
): boolean {
suspenseInstance.state = 'pending';
@ -186,7 +186,7 @@ const ReactNoopServer = ReactFizzServer({
},
writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
suspenseInstance: SuspenseInstance,
): boolean {
suspenseInstance.state = 'client-render';
@ -206,7 +206,7 @@ const ReactNoopServer = ReactFizzServer({
writeStartSegment(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
formatContext: null,
id: number,
): boolean {
@ -225,7 +225,7 @@ const ReactNoopServer = ReactFizzServer({
writeCompletedSegmentInstruction(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
contentSegmentID: number,
): boolean {
const segment = destination.segments.get(contentSegmentID);
@ -245,7 +245,7 @@ const ReactNoopServer = ReactFizzServer({
writeCompletedBoundaryInstruction(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
boundary: SuspenseInstance,
contentSegmentID: number,
): boolean {
@ -259,7 +259,7 @@ const ReactNoopServer = ReactFizzServer({
writeClientRenderBoundaryInstruction(
destination: Destination,
responseState: ResponseState,
renderState: RenderState,
boundary: SuspenseInstance,
): boolean {
boundary.status = 'client-render';
@ -269,10 +269,6 @@ const ReactNoopServer = ReactFizzServer({
writeHoistables() {},
writePostamble() {},
createResources(): Resources {
return null;
},
createBoundaryResources(): BoundaryResources {
return null;
},

View File

@ -23,8 +23,8 @@ import {
} from 'react-server/src/ReactFizzServer';
import {
createResources,
createResponseState,
createResumableState,
createRenderState,
createRootFormatContext,
} from 'react-server/src/ReactFizzConfig';
@ -50,19 +50,18 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
fatal: false,
error: null,
};
const resources = createResources();
const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
);
const request = createRequest(
children,
resources,
createResponseState(
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
resumableState,
createRenderState(resumableState, undefined),
createRootFormatContext(undefined),
options ? options.progressiveChunkSize : undefined,
options.onError,

View File

@ -16,7 +16,7 @@ import type {
Usable,
} from 'shared/ReactTypes';
import type {ResponseState} from './ReactFizzConfig';
import type {ResumableState} from './ReactFizzConfig';
import type {Task} from './ReactFizzServer';
import type {ThenableState} from './ReactFizzThenable';
import type {TransitionStatus} from './ReactFizzConfig';
@ -554,15 +554,15 @@ function useId(): string {
const task: Task = (currentlyRenderingTask: any);
const treeId = getTreeId(task.treeContext);
const responseState = currentResponseState;
if (responseState === null) {
const resumableState = currentResumableState;
if (resumableState === null) {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component.',
);
}
const localId = localIdCounter++;
return makeId(responseState, treeId, localId);
return makeId(resumableState, treeId, localId);
}
function use<T>(usable: Usable<T>): T {
@ -652,9 +652,9 @@ if (enableAsyncActions) {
HooksDispatcher.useOptimistic = useOptimistic;
}
export let currentResponseState: null | ResponseState = (null: any);
export function setCurrentResponseState(
responseState: null | ResponseState,
export let currentResumableState: null | ResumableState = (null: any);
export function setCurrentResumableState(
resumableState: null | ResumableState,
): void {
currentResponseState = responseState;
currentResumableState = resumableState;
}

View File

@ -23,9 +23,9 @@ import type {
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
import type {
SuspenseBoundaryID,
ResponseState,
RenderState,
ResumableState,
FormatContext,
Resources,
BoundaryResources,
} from './ReactFizzConfig';
import type {ContextSnapshot} from './ReactFizzNewContext';
@ -100,8 +100,8 @@ import {
checkDidRenderIdHook,
resetHooksState,
HooksDispatcher,
currentResponseState,
setCurrentResponseState,
currentResumableState,
setCurrentResumableState,
getThenableStateAfterSuspending,
unwrapThenable,
} from './ReactFizzHooks';
@ -222,14 +222,14 @@ const CLOSED = 2;
export opaque type Request = {
destination: null | Destination,
flushScheduled: boolean,
+responseState: ResponseState,
+resumableState: ResumableState,
+renderState: RenderState,
+progressiveChunkSize: number,
status: 0 | 1 | 2,
fatalError: mixed,
nextSegmentId: number,
allPendingTasks: number, // when it reaches zero, we can close the connection.
pendingRootTasks: number, // when this reaches zero, we've finished at least the root boundary.
resources: Resources,
completedRootSegment: null | Segment, // Completed but not yet flushed root segments.
abortableTasks: Set<Task>,
pingedTasks: Array<Task>, // High priority tasks that should be worked on first.
@ -283,8 +283,8 @@ function noop(): void {}
export function createRequest(
children: ReactNodeList,
resources: Resources,
responseState: ResponseState,
resumableState: ResumableState,
renderState: RenderState,
rootFormatContext: FormatContext,
progressiveChunkSize: void | number,
onError: void | ((error: mixed) => ?string),
@ -300,7 +300,8 @@ export function createRequest(
const request: Request = {
destination: null,
flushScheduled: false,
responseState,
resumableState,
renderState,
progressiveChunkSize:
progressiveChunkSize === undefined
? DEFAULT_PROGRESSIVE_CHUNK_SIZE
@ -310,7 +311,6 @@ export function createRequest(
nextSegmentId: 0,
allPendingTasks: 0,
pendingRootTasks: 0,
resources,
completedRootSegment: null,
abortableTasks: abortSet,
pingedTasks: pingedTasks,
@ -622,7 +622,7 @@ function renderSuspenseBoundary(
task.blockedSegment = contentRootSegment;
if (enableFloat) {
setCurrentlyRenderingBoundaryResourcesTarget(
request.resources,
request.renderState,
newBoundary.resources,
);
}
@ -631,7 +631,7 @@ function renderSuspenseBoundary(
renderNode(request, task, content, 0);
pushSegmentFinale(
contentRootSegment.chunks,
request.responseState,
request.renderState,
contentRootSegment.lastPushedText,
contentRootSegment.textEmbedded,
);
@ -672,7 +672,7 @@ function renderSuspenseBoundary(
} finally {
if (enableFloat) {
setCurrentlyRenderingBoundaryResourcesTarget(
request.resources,
request.renderState,
parentBoundary ? parentBoundary.resources : null,
);
}
@ -734,8 +734,8 @@ function renderHostElement(
segment.chunks,
type,
props,
request.resources,
request.responseState,
request.resumableState,
request.renderState,
segment.formatContext,
segment.lastPushedText,
);
@ -754,7 +754,7 @@ function renderHostElement(
segment.chunks,
type,
props,
request.responseState,
request.resumableState,
prevContext,
);
segment.lastPushedText = false;
@ -1568,7 +1568,7 @@ function renderNodeDestructiveImpl(
segment.lastPushedText = pushTextInstance(
task.blockedSegment.chunks,
node,
request.responseState,
request.renderState,
segment.lastPushedText,
);
return;
@ -1579,7 +1579,7 @@ function renderNodeDestructiveImpl(
segment.lastPushedText = pushTextInstance(
task.blockedSegment.chunks,
'' + node,
request.responseState,
request.renderState,
segment.lastPushedText,
);
return;
@ -1975,7 +1975,7 @@ function retryTask(request: Request, task: Task): void {
if (enableFloat) {
const blockedBoundary = task.blockedBoundary;
setCurrentlyRenderingBoundaryResourcesTarget(
request.resources,
request.renderState,
blockedBoundary ? blockedBoundary.resources : null,
);
}
@ -2009,7 +2009,7 @@ function retryTask(request: Request, task: Task): void {
renderNodeDestructive(request, task, prevThenableState, task.node, 0);
pushSegmentFinale(
segment.chunks,
request.responseState,
request.renderState,
segment.lastPushedText,
segment.textEmbedded,
);
@ -2047,7 +2047,7 @@ function retryTask(request: Request, task: Task): void {
}
} finally {
if (enableFloat) {
setCurrentlyRenderingBoundaryResourcesTarget(request.resources, null);
setCurrentlyRenderingBoundaryResourcesTarget(request.renderState, null);
}
if (__DEV__) {
currentTaskInDEV = prevTaskInDEV;
@ -2076,8 +2076,8 @@ export function performWork(request: Request): void {
prevGetCurrentStackImpl = ReactDebugCurrentFrame.getCurrentStack;
ReactDebugCurrentFrame.getCurrentStack = getCurrentStackInDEV;
}
const prevResponseState = currentResponseState;
setCurrentResponseState(request.responseState);
const prevResumableState = currentResumableState;
setCurrentResumableState(request.resumableState);
try {
const pingedTasks = request.pingedTasks;
let i;
@ -2093,7 +2093,7 @@ export function performWork(request: Request): void {
logRecoverableError(request, error);
fatalError(request, error);
} finally {
setCurrentResponseState(prevResponseState);
setCurrentResumableState(prevResumableState);
ReactCurrentDispatcher.current = prevDispatcher;
if (enableCache) {
ReactCurrentCache.current = prevCacheDispatcher;
@ -2130,7 +2130,7 @@ function flushSubtree(
// When this segment finally completes it won't be embedded in text since it will flush separately
segment.lastPushedText = false;
segment.textEmbedded = false;
return writePlaceholder(destination, request.responseState, segmentID);
return writePlaceholder(destination, request.renderState, segmentID);
}
case COMPLETED: {
segment.status = FLUSHED;
@ -2183,7 +2183,7 @@ function flushSegment(
writeStartClientRenderedSuspenseBoundary(
destination,
request.responseState,
request.renderState,
boundary.errorDigest,
boundary.errorMessage,
boundary.errorComponentStack,
@ -2193,7 +2193,7 @@ function flushSegment(
return writeEndClientRenderedSuspenseBoundary(
destination,
request.responseState,
request.renderState,
);
} else if (boundary.pendingTasks > 0) {
// This boundary is still loading. Emit a pending suspense boundary wrapper.
@ -2206,14 +2206,17 @@ function flushSegment(
}
/// This is the first time we should have referenced this ID.
const id = (boundary.id = assignSuspenseBoundaryID(request.responseState));
const id = (boundary.id = assignSuspenseBoundaryID(
request.renderState,
request.resumableState,
));
writeStartPendingSuspenseBoundary(destination, request.responseState, id);
writeStartPendingSuspenseBoundary(destination, request.renderState, id);
// Flush the fallback.
flushSubtree(request, destination, segment);
return writeEndPendingSuspenseBoundary(destination, request.responseState);
return writeEndPendingSuspenseBoundary(destination, request.renderState);
} else if (boundary.byteSize > request.progressiveChunkSize) {
// This boundary is large and will be emitted separately so that we can progressively show
// other content. We add it to the queue during the flush because we have to ensure that
@ -2228,20 +2231,20 @@ function flushSegment(
// Emit a pending rendered suspense boundary wrapper.
writeStartPendingSuspenseBoundary(
destination,
request.responseState,
request.renderState,
boundary.id,
);
// Flush the fallback.
flushSubtree(request, destination, segment);
return writeEndPendingSuspenseBoundary(destination, request.responseState);
return writeEndPendingSuspenseBoundary(destination, request.renderState);
} else {
if (enableFloat) {
hoistResources(request.resources, boundary.resources);
hoistResources(request.renderState, boundary.resources);
}
// We can inline this boundary's content as a complete boundary.
writeStartCompletedSuspenseBoundary(destination, request.responseState);
writeStartCompletedSuspenseBoundary(destination, request.renderState);
const completedSegments = boundary.completedSegments;
@ -2254,10 +2257,7 @@ function flushSegment(
const contentSegment = completedSegments[0];
flushSegment(request, destination, contentSegment);
return writeEndCompletedSuspenseBoundary(
destination,
request.responseState,
);
return writeEndCompletedSuspenseBoundary(destination, request.renderState);
}
}
@ -2268,7 +2268,8 @@ function flushClientRenderedBoundary(
): boolean {
return writeClientRenderBoundaryInstruction(
destination,
request.responseState,
request.resumableState,
request.renderState,
boundary.id,
boundary.errorDigest,
boundary.errorMessage,
@ -2283,7 +2284,7 @@ function flushSegmentContainer(
): boolean {
writeStartSegment(
destination,
request.responseState,
request.renderState,
segment.formatContext,
segment.id,
);
@ -2298,7 +2299,7 @@ function flushCompletedBoundary(
): boolean {
if (enableFloat) {
setCurrentlyRenderingBoundaryResourcesTarget(
request.resources,
request.renderState,
boundary.resources,
);
}
@ -2314,13 +2315,14 @@ function flushCompletedBoundary(
writeResourcesForBoundary(
destination,
boundary.resources,
request.responseState,
request.renderState,
);
}
return writeCompletedBoundaryInstruction(
destination,
request.responseState,
request.resumableState,
request.renderState,
boundary.id,
boundary.rootSegmentID,
boundary.resources,
@ -2334,7 +2336,7 @@ function flushPartialBoundary(
): boolean {
if (enableFloat) {
setCurrentlyRenderingBoundaryResourcesTarget(
request.resources,
request.renderState,
boundary.resources,
);
}
@ -2362,7 +2364,7 @@ function flushPartialBoundary(
return writeResourcesForBoundary(
destination,
boundary.resources,
request.responseState,
request.renderState,
);
} else {
return true;
@ -2397,7 +2399,8 @@ function flushPartiallyCompletedSegment(
flushSegmentContainer(request, destination, segment);
return writeCompletedSegmentInstruction(
destination,
request.responseState,
request.resumableState,
request.renderState,
segmentID,
);
}
@ -2421,15 +2424,15 @@ function flushCompletedQueues(
if (enableFloat) {
writePreamble(
destination,
request.resources,
request.responseState,
request.resumableState,
request.renderState,
request.allPendingTasks === 0,
);
}
flushSegment(request, destination, completedRootSegment);
request.completedRootSegment = null;
writeCompletedRoot(destination, request.responseState);
writeCompletedRoot(destination, request.resumableState);
} else {
// We haven't flushed the root yet so we don't need to check any other branches further down
return;
@ -2440,7 +2443,7 @@ function flushCompletedQueues(
}
if (enableFloat) {
writeHoistables(destination, request.resources, request.responseState);
writeHoistables(destination, request.resumableState, request.renderState);
}
// We emit client rendering instructions for already emitted boundaries first.
@ -2519,7 +2522,7 @@ function flushCompletedQueues(
) {
request.flushScheduled = false;
if (enableFloat) {
writePostamble(destination, request.responseState);
writePostamble(destination, request.resumableState);
}
completeWriting(destination);
flushBuffered(destination);
@ -2610,6 +2613,18 @@ export function flushResources(request: Request): void {
enqueueFlush(request);
}
export function getResources(request: Request): Resources {
return request.resources;
export function getResumableState(request: Request): ResumableState {
return request.resumableState;
}
export type PostponedState = {
nextSegmentId: number,
rootFormatContext: FormatContext,
progressiveChunkSize: number,
resumableState: ResumableState,
};
// Returns the state of a postponed request or null if nothing was postponed.
export function getPostponedState(request: Request): null | PostponedState {
return null;
}

View File

@ -28,8 +28,8 @@ import type {TransitionStatus} from 'react-reconciler/src/ReactFiberConfig';
declare var $$$config: any;
export opaque type Destination = mixed; // eslint-disable-line no-undef
export opaque type ResponseState = mixed;
export opaque type Resources = mixed;
export opaque type RenderState = mixed;
export opaque type ResumableState = mixed;
export opaque type BoundaryResources = mixed;
export opaque type FormatContext = mixed;
export opaque type SuspenseBoundaryID = mixed;

View File

@ -233,7 +233,7 @@ const bundles = [
{
bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
moduleType: RENDERER,
entry: 'react-dom/src/server/ReactDOMFizzServerBrowser.js',
entry: 'react-dom/src/server/react-dom-server.browser.js',
name: 'react-dom-server.browser',
global: 'ReactDOMServer',
minifyWithProdErrorCodes: true,
@ -243,7 +243,7 @@ const bundles = [
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-dom/src/server/ReactDOMFizzServerNode.js',
entry: 'react-dom/src/server/react-dom-server.node.js',
name: 'react-dom-server.node',
global: 'ReactDOMServer',
minifyWithProdErrorCodes: false,
@ -264,7 +264,7 @@ const bundles = [
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-dom/src/server/ReactDOMFizzServerEdge.js',
entry: 'react-dom/src/server/react-dom-server.edge.js',
name: 'react-dom-server.edge', // 'node_modules/react/*.js',
global: 'ReactDOMServer',
@ -277,7 +277,7 @@ const bundles = [
{
bundleTypes: [BUN_DEV, BUN_PROD],
moduleType: RENDERER,
entry: 'react-dom/src/server/ReactDOMFizzServerBun.js',
entry: 'react-dom/src/server/react-dom-server.bun.js',
name: 'react-dom-server.bun', // 'node_modules/react/*.js',
global: 'ReactDOMServer',

View File

@ -12,7 +12,7 @@ module.exports = [
entryPoints: [
'react-dom',
'react-dom/unstable_testing',
'react-dom/src/server/ReactDOMFizzServerNode.js',
'react-dom/src/server/react-dom-server.node.js',
'react-dom/static.node',
'react-dom/server-rendering-stub',
'react-dom/unstable_server-external-runtime',
@ -27,6 +27,7 @@ module.exports = [
'react-dom/server.node',
'react-dom/static',
'react-dom/static.node',
'react-dom/src/server/react-dom-server.node',
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
'react-dom/src/server/ReactDOMFizzStaticNode.js',
'react-server-dom-webpack',
@ -46,10 +47,11 @@ module.exports = [
},
{
shortName: 'dom-bun',
entryPoints: ['react-dom', 'react-dom/src/server/ReactDOMFizzServerBun.js'],
entryPoints: ['react-dom', 'react-dom/src/server/react-dom-server.bun.js'],
paths: [
'react-dom',
'react-dom/server.bun',
'react-dom/src/server/react-dom-server.bun',
'react-dom/src/server/ReactDOMFizzServerBun.js',
'react-dom-bindings',
'shared/ReactDOMSharedInternals',
@ -62,7 +64,7 @@ module.exports = [
entryPoints: [
'react-dom',
'react-dom/unstable_testing',
'react-dom/src/server/ReactDOMFizzServerBrowser.js',
'react-dom/src/server/react-dom-server.browser.js',
'react-dom/static.browser',
'react-dom/server-rendering-stub',
'react-dom/unstable_server-external-runtime',
@ -76,6 +78,7 @@ module.exports = [
'react-dom/server.browser',
'react-dom/static.browser',
'react-dom/unstable_testing',
'react-dom/src/server/react-dom-server.browser',
'react-dom/src/server/ReactDOMFizzServerBrowser.js', // react-dom/server.browser
'react-dom/src/server/ReactDOMFizzStaticBrowser.js',
'react-server-dom-webpack',
@ -118,7 +121,7 @@ module.exports = [
{
shortName: 'dom-edge-webpack',
entryPoints: [
'react-dom/src/server/ReactDOMFizzServerEdge.js',
'react-dom/src/server/react-dom-server.edge.js',
'react-dom/static.edge',
'react-server-dom-webpack/server.edge',
'react-server-dom-webpack/client.edge',
@ -130,6 +133,7 @@ module.exports = [
'react-dom/server.edge',
'react-dom/static.edge',
'react-dom/unstable_testing',
'react-dom/src/server/react-dom-server.edge',
'react-dom/src/server/ReactDOMFizzServerEdge.js', // react-dom/server.edge
'react-dom/src/server/ReactDOMFizzStaticEdge.js',
'react-server-dom-webpack',
@ -160,6 +164,7 @@ module.exports = [
'react-dom/server.node',
'react-dom/static',
'react-dom/static.node',
'react-dom/src/server/react-dom-server.node',
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
'react-dom/src/server/ReactDOMFizzStaticNode.js',
'react-server-dom-webpack',
@ -193,6 +198,7 @@ module.exports = [
'react-dom/server.node',
'react-dom/static',
'react-dom/static.node',
'react-dom/src/server/react-dom-server.node',
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
'react-dom/src/server/ReactDOMFizzStaticNode.js',
'react-server-dom-esm',