Skip to content

Commit

Permalink
Expose prerender() for SSG in stable (#31298)
Browse files Browse the repository at this point in the history
When we added `renderToReadableStream` we added the `allReady` helper to
make it easier to do SSG rendering but it's kind of awkward to wire up
that way. Since we're also discouraging `renderToString` in React 19 the
cliff is kind of awkward. ([As noted by
Docusaurus.](#24752 (comment)))

The idea of the `react-dom/static` `prerender` API was that this would
be the replacement for SSG rendering. Awkwardly this entry point
actually already exists in stable but it has only `undefined` exports.

Since then we've also added other useful heuristics into the `prerender`
branch that makes this really the favored and easiest to use API for the
prerender (SSG/ISR) use case.

`prerender` is also used for Partial Prerendering but that part is still
experimental.

However, we can expose only the `prerender` API on `react-dom/static`
without it returning the `postponeState`. Instead the stream is on
`prelude`. The naming is a bit awkward if you don't consider resuming
but it's the same thing.

It's really just `renderToReadable` stream with automatic `allReady` and
better heuristics for prerendering.
  • Loading branch information
sebmarkbage authored Oct 20, 2024
1 parent 22b2b1a commit d49123f
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 39 deletions.
16 changes: 7 additions & 9 deletions packages/react-dom/src/__tests__/ReactDOMFizzStatic-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ describe('ReactDOMFizzStatic', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
if (__EXPERIMENTAL__) {
ReactDOMFizzStatic = require('react-dom/static');
}
ReactDOMFizzStatic = require('react-dom/static');
Stream = require('stream');
Suspense = React.Suspense;

Expand Down Expand Up @@ -212,7 +210,6 @@ describe('ReactDOMFizzStatic', () => {
return readText(text);
}

// @gate experimental
it('should render a fully static document, send it and then hydrate it', async () => {
function App() {
return (
Expand All @@ -230,7 +227,11 @@ describe('ReactDOMFizzStatic', () => {

const result = await promise;

expect(result.postponed).toBe(null);
expect(result.postponed).toBe(
gate(flags => flags.enableHalt || flags.enablePostpone)
? null
: undefined,
);

await act(async () => {
result.prelude.pipe(writable);
Expand All @@ -244,7 +245,6 @@ describe('ReactDOMFizzStatic', () => {
expect(getVisibleChildren(container)).toEqual(<div>Hello</div>);
});

// @gate experimental
it('should support importMap option', async () => {
const importMap = {
foo: 'path/to/foo.js',
Expand All @@ -265,7 +265,6 @@ describe('ReactDOMFizzStatic', () => {
]);
});

// @gate experimental
it('supports onHeaders', async () => {
let headers;
function onHeaders(x) {
Expand Down Expand Up @@ -300,7 +299,7 @@ describe('ReactDOMFizzStatic', () => {
expect(getVisibleChildren(container)).toEqual('hello');
});

// @gate experimental && enablePostpone
// @gate enablePostpone
it('includes stylesheet preloads in onHeaders when postponing in the Shell', async () => {
let headers;
function onHeaders(x) {
Expand Down Expand Up @@ -336,7 +335,6 @@ describe('ReactDOMFizzStatic', () => {
expect(getVisibleChildren(container)).toEqual(undefined);
});

// @gate experimental
it('will prerender Suspense fallbacks before children', async () => {
const values = [];
function Indirection({children}) {
Expand Down
24 changes: 6 additions & 18 deletions packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMFizzServer = require('react-dom/server.browser');
if (__EXPERIMENTAL__) {
ReactDOMFizzStatic = require('react-dom/static.browser');
}
ReactDOMFizzStatic = require('react-dom/static.browser');
Suspense = React.Suspense;
container = document.createElement('div');
document.body.appendChild(container);
Expand Down Expand Up @@ -131,7 +129,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
await insertNodesAndExecuteScripts(temp, container, null);
}

// @gate experimental
it('should call prerender', async () => {
const result = await serverAct(() =>
ReactDOMFizzStatic.prerender(<div>hello world</div>),
Expand All @@ -140,7 +137,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(prelude).toMatchInlineSnapshot(`"<div>hello world</div>"`);
});

// @gate experimental
it('should emit DOCTYPE at the root of the document', async () => {
const result = await serverAct(() =>
ReactDOMFizzStatic.prerender(
Expand All @@ -155,7 +151,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
);
});

// @gate experimental
it('should emit bootstrap script src at the end', async () => {
const result = await serverAct(() =>
ReactDOMFizzStatic.prerender(<div>hello world</div>, {
Expand All @@ -170,7 +165,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
);
});

// @gate experimental
it('emits all HTML as one unit', async () => {
let hasLoaded = false;
let resolve;
Expand Down Expand Up @@ -202,7 +196,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(prelude).toMatchInlineSnapshot(`"<div><!--$-->Done<!--/$--></div>"`);
});

// @gate experimental
it('should reject the promise when an error is thrown at the root', async () => {
const reportedErrors = [];
let caughtError = null;
Expand All @@ -226,7 +219,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(reportedErrors).toEqual([theError]);
});

// @gate experimental
it('should reject the promise when an error is thrown inside a fallback', async () => {
const reportedErrors = [];
let caughtError = null;
Expand All @@ -252,7 +244,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(reportedErrors).toEqual([theError]);
});

// @gate experimental
it('should not error the stream when an error is thrown inside suspense boundary', async () => {
const reportedErrors = [];
const result = await serverAct(() =>
Expand All @@ -275,7 +266,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(reportedErrors).toEqual([theError]);
});

// @gate experimental
it('should be able to complete by aborting even if the promise never resolves', async () => {
const errors = [];
const controller = new AbortController();
Expand Down Expand Up @@ -306,7 +296,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(errors).toEqual(['The operation was aborted.']);
});

// @gate experimental
// @gate !enableHalt
it('should reject if aborting before the shell is complete and enableHalt is disabled', async () => {
const errors = [];
Expand Down Expand Up @@ -376,7 +365,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(content).toBe('');
});

// @gate experimental
it('should be able to abort before something suspends', async () => {
const errors = [];
const controller = new AbortController();
Expand Down Expand Up @@ -419,7 +407,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
}
});

// @gate experimental
// @gate !enableHalt
it('should reject if passing an already aborted signal and enableHalt is disabled', async () => {
const errors = [];
Expand Down Expand Up @@ -493,7 +480,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(content).toBe('');
});

// @gate experimental
it('supports custom abort reasons with a string', async () => {
const promise = new Promise(r => {});
function Wait() {
Expand Down Expand Up @@ -536,7 +522,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
expect(errors).toEqual(['foobar', 'foobar']);
});

// @gate experimental
it('supports custom abort reasons with an Error', async () => {
const promise = new Promise(r => {});
function Wait() {
Expand Down Expand Up @@ -1610,7 +1595,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
);
});

// @gate experimental
it('logs an error if onHeaders throws but continues the prerender', async () => {
const errors = [];
function onError(error) {
Expand All @@ -1627,7 +1611,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
onError,
}),
);
expect(prerendered.postponed).toBe(null);
expect(prerendered.postponed).toBe(
gate(flags => flags.enableHalt || flags.enablePostpone)
? null
: undefined,
);
expect(errors).toEqual(['bad onHeaders']);

await readIntoContainer(prerendered.prelude);
Expand Down
15 changes: 11 additions & 4 deletions packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

import {enablePostpone, enableHalt} from 'shared/ReactFeatureFlags';

import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
ensureCorrectIsomorphicReactVersion();

Expand Down Expand Up @@ -85,10 +87,15 @@ function prerender(
{highWaterMark: 0},
);

const result = {
postponed: getPostponedState(request),
prelude: stream,
};
const result: StaticResult =
enablePostpone || enableHalt
? {
postponed: getPostponedState(request),
prelude: stream,
}
: ({
prelude: stream,
}: any);
resolve(result);
}

Expand Down
15 changes: 11 additions & 4 deletions packages/react-dom/src/server/ReactDOMFizzStaticEdge.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

import {enablePostpone, enableHalt} from 'shared/ReactFeatureFlags';

import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
ensureCorrectIsomorphicReactVersion();

Expand Down Expand Up @@ -85,10 +87,15 @@ function prerender(
{highWaterMark: 0},
);

const result = {
postponed: getPostponedState(request),
prelude: stream,
};
const result: StaticResult =
enablePostpone || enableHalt
? {
postponed: getPostponedState(request),
prelude: stream,
}
: ({
prelude: stream,
}: any);
resolve(result);
}

Expand Down
15 changes: 11 additions & 4 deletions packages/react-dom/src/server/ReactDOMFizzStaticNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
createRootFormatContext,
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

import {enablePostpone, enableHalt} from 'shared/ReactFeatureFlags';

import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
ensureCorrectIsomorphicReactVersion();

Expand Down Expand Up @@ -94,10 +96,15 @@ function prerenderToNodeStream(
});
const writable = createFakeWritable(readable);

const result = {
postponed: getPostponedState(request),
prelude: readable,
};
const result: StaticResult =
enablePostpone || enableHalt
? {
postponed: getPostponedState(request),
prelude: readable,
}
: ({
prelude: readable,
}: any);
resolve(result);
}
const resumableState = createResumableState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
*/

export {renderToReadableStream, version} from './ReactDOMFizzServerBrowser.js';
export {prerender} from './ReactDOMFizzStaticBrowser.js';
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
*/

export {renderToReadableStream, version} from './ReactDOMFizzServerEdge.js';
export {prerender} from './ReactDOMFizzStaticEdge.js';
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
*/

export {renderToPipeableStream, version} from './ReactDOMFizzServerNode.js';
export {prerenderToNodeStream} from './ReactDOMFizzStaticNode.js';

0 comments on commit d49123f

Please sign in to comment.