[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', ReadableStreamController: 'readonly',
RequestInfo: 'readonly', RequestInfo: 'readonly',
RequestOptions: 'readonly', RequestOptions: 'readonly',
ResponseState: 'readonly',
StoreAsGlobal: 'readonly', StoreAsGlobal: 'readonly',
symbol: 'readonly', symbol: 'readonly',
SyntheticEvent: 'readonly', SyntheticEvent: 'readonly',

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -15,3 +15,6 @@ exports.renderToStaticMarkup = l.renderToStaticMarkup;
exports.renderToNodeStream = l.renderToNodeStream; exports.renderToNodeStream = l.renderToNodeStream;
exports.renderToStaticNodeStream = l.renderToStaticNodeStream; exports.renderToStaticNodeStream = l.renderToStaticNodeStream;
exports.renderToReadableStream = s.renderToReadableStream; 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.version = b.version;
exports.renderToReadableStream = b.renderToReadableStream; exports.renderToReadableStream = b.renderToReadableStream;
if (b.resume) {
exports.resume = b.resume;
}
exports.renderToNodeStream = b.renderToNodeStream; exports.renderToNodeStream = b.renderToNodeStream;
exports.renderToStaticNodeStream = b.renderToStaticNodeStream; exports.renderToStaticNodeStream = b.renderToStaticNodeStream;
exports.renderToString = l.renderToString; exports.renderToString = l.renderToString;

View File

@ -16,3 +16,6 @@ exports.renderToNodeStream = b.renderToNodeStream;
exports.renderToStaticNodeStream = b.renderToStaticNodeStream; exports.renderToStaticNodeStream = b.renderToStaticNodeStream;
exports.renderToString = l.renderToString; exports.renderToString = l.renderToString;
exports.renderToStaticMarkup = l.renderToStaticMarkup; 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.renderToNodeStream = l.renderToNodeStream;
exports.renderToStaticNodeStream = l.renderToStaticNodeStream; exports.renderToStaticNodeStream = l.renderToStaticNodeStream;
exports.renderToPipeableStream = s.renderToPipeableStream; exports.renderToPipeableStream = s.renderToPipeableStream;
if (s.resume) {
exports.resume = s.resume;
}

View File

@ -37,7 +37,14 @@ export function renderToStaticNodeStream() {
} }
export function renderToReadableStream() { 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, this,
arguments, arguments,
); );

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
* @flow * @flow
*/ */
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import type {ReactNodeList} from 'shared/ReactTypes'; import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -20,8 +21,8 @@ import {
} from 'react-server/src/ReactFizzServer'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +40,14 @@ type Options = {
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor, 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. // TODO: Move to sub-classing ReadableStream.
type ReactDOMServerReadableStream = ReadableStream & { type ReactDOMServerReadableStream = ReadableStream & {
allReady: Promise<void>, allReady: Promise<void>,
@ -81,19 +90,18 @@ function renderToReadableStream(
allReady.catch(() => {}); allReady.catch(() => {});
reject(error); 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( const request = createRequest(
children, children,
resources, resumableState,
createResponseState( createRenderState(resumableState, options ? options.nonce : undefined),
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,
),
createRootFormatContext(options ? options.namespaceURI : undefined), createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined, options ? options.progressiveChunkSize : undefined,
options ? options.onError : 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'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -82,19 +82,18 @@ function renderToReadableStream(
allReady.catch(() => {}); allReady.catch(() => {});
reject(error); 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( const request = createRequest(
children, children,
resources, resumableState,
createResponseState( createRenderState(resumableState, options ? options.nonce : undefined),
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,
),
createRootFormatContext(options ? options.namespaceURI : undefined), createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined, options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined, options ? options.onError : undefined,

View File

@ -7,6 +7,7 @@
* @flow * @flow
*/ */
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import type {ReactNodeList} from 'shared/ReactTypes'; import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -20,8 +21,8 @@ import {
} from 'react-server/src/ReactFizzServer'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +40,14 @@ type Options = {
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor, 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. // TODO: Move to sub-classing ReadableStream.
type ReactDOMServerReadableStream = ReadableStream & { type ReactDOMServerReadableStream = ReadableStream & {
allReady: Promise<void>, allReady: Promise<void>,
@ -81,19 +90,18 @@ function renderToReadableStream(
allReady.catch(() => {}); allReady.catch(() => {});
reject(error); 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( const request = createRequest(
children, children,
resources, resumableState,
createResponseState( createRenderState(resumableState, options ? options.nonce : undefined),
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,
),
createRootFormatContext(options ? options.namespaceURI : undefined), createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined, options ? options.progressiveChunkSize : undefined,
options ? options.onError : 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 * @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 {ReactNodeList} from 'shared/ReactTypes';
import type {Writable} from 'stream'; import type {Writable} from 'stream';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -23,8 +23,8 @@ import {
} from 'react-server/src/ReactFizzServer'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -53,6 +53,15 @@ type Options = {
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor, 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 = { type PipeableStream = {
// Cancel any pending I/O and put anything remaining into // Cancel any pending I/O and put anything remaining into
// client rendered mode. // client rendered mode.
@ -61,19 +70,18 @@ type PipeableStream = {
}; };
function createRequestImpl(children: ReactNodeList, options: void | Options) { 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( return createRequest(
children, children,
resources, resumableState,
createResponseState( createRenderState(resumableState, options ? options.nonce : undefined),
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,
),
createRootFormatContext(options ? options.namespaceURI : undefined), createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined, options ? options.progressiveChunkSize : undefined,
options ? options.onError : 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 {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import ReactVersion from 'shared/ReactVersion'; import ReactVersion from 'shared/ReactVersion';
@ -17,11 +18,12 @@ import {
startWork, startWork,
startFlowing, startFlowing,
abort, abort,
getPostponedState,
} from 'react-server/src/ReactFizzServer'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -39,6 +41,7 @@ type Options = {
}; };
type StaticResult = { type StaticResult = {
postponed: null | PostponedState,
prelude: ReadableStream, prelude: ReadableStream,
}; };
@ -62,23 +65,23 @@ function prerender(
); );
const result = { const result = {
postponed: getPostponedState(request),
prelude: stream, prelude: stream,
}; };
resolve(result); 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( const request = createRequest(
children, children,
resources, resources,
createResponseState( createRenderState(resources, undefined),
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
createRootFormatContext(options ? options.namespaceURI : undefined), createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined, options ? options.progressiveChunkSize : undefined,
options ? options.onError : undefined, options ? options.onError : undefined,

View File

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

View File

@ -9,6 +9,7 @@
import type {ReactNodeList} from 'shared/ReactTypes'; import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import type {PostponedState} from 'react-server/src/ReactFizzServer';
import {Writable, Readable} from 'stream'; import {Writable, Readable} from 'stream';
@ -19,11 +20,12 @@ import {
startWork, startWork,
startFlowing, startFlowing,
abort, abort,
getPostponedState,
} from 'react-server/src/ReactFizzServer'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
@ -41,6 +43,7 @@ type Options = {
}; };
type StaticResult = { type StaticResult = {
postponed: null | PostponedState,
prelude: Readable, prelude: Readable,
}; };
@ -60,7 +63,7 @@ function createFakeWritable(readable: any): Writable {
}: any); }: any);
} }
function prerenderToNodeStreams( function prerenderToNodeStream(
children: ReactNodeList, children: ReactNodeList,
options?: Options, options?: Options,
): Promise<StaticResult> { ): Promise<StaticResult> {
@ -76,23 +79,23 @@ function prerenderToNodeStreams(
const writable = createFakeWritable(readable); const writable = createFakeWritable(readable);
const result = { const result = {
postponed: getPostponedState(request),
prelude: readable, prelude: readable,
}; };
resolve(result); 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( const request = createRequest(
children, children,
resources, resumableState,
createResponseState( createRenderState(resumableState, undefined),
resources,
options ? options.identifierPrefix : undefined,
undefined,
options ? options.bootstrapScriptContent : undefined,
options ? options.bootstrapScripts : undefined,
options ? options.bootstrapModules : undefined,
options ? options.unstable_externalRuntimeSrc : undefined,
),
createRootFormatContext(options ? options.namespaceURI : undefined), createRootFormatContext(options ? options.namespaceURI : undefined),
options ? options.progressiveChunkSize : undefined, options ? options.progressiveChunkSize : undefined,
options ? options.onError : 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 ReactVersion from 'shared/ReactVersion';
import type {ReactNodeList} from 'shared/ReactTypes'; import type {ReactNodeList} from 'shared/ReactTypes';
import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
import { import {
createRequest, createRequest,
@ -20,8 +19,8 @@ import {
} from 'react-server/src/ReactFizzServer'; } from 'react-server/src/ReactFizzServer';
import { import {
createResources, createResumableState,
createResponseState, createRenderState,
createRootFormatContext, createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy'; } from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
@ -38,7 +37,6 @@ function renderToStringImpl(
options: void | ServerOptions, options: void | ServerOptions,
generateStaticMarkup: boolean, generateStaticMarkup: boolean,
abortReason: string, abortReason: string,
unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor,
): string { ): string {
let didFatal = false; let didFatal = false;
let fatalError = null; let fatalError = null;
@ -62,16 +60,18 @@ function renderToStringImpl(
function onShellReady() { function onShellReady() {
readyToStream = true; readyToStream = true;
} }
const resources = createResources(); const resumableState = createResumableState(
options ? options.identifierPrefix : undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
const request = createRequest( const request = createRequest(
children, children,
resources, resumableState,
createResponseState( createRenderState(resumableState, undefined, generateStaticMarkup),
resources,
generateStaticMarkup,
options ? options.identifierPrefix : undefined,
unstable_externalRuntimeSrc,
),
createRootFormatContext(), createRootFormatContext(),
Infinity, Infinity,
onError, onError,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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