mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Expose prerender() for SSG in stable (#31298)
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.](https://github.com/facebook/react/pull/24752#issuecomment-2178309299)) 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.
This commit is contained in:
parent
22b2b1a05a
commit
d49123f73f
|
|
@ -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;
|
||||
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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);
|
||||
|
|
@ -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',
|
||||
|
|
@ -265,7 +265,6 @@ describe('ReactDOMFizzStatic', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('supports onHeaders', async () => {
|
||||
let headers;
|
||||
function onHeaders(x) {
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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}) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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>),
|
||||
|
|
@ -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(
|
||||
|
|
@ -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>, {
|
||||
|
|
@ -170,7 +165,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
|||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('emits all HTML as one unit', async () => {
|
||||
let hasLoaded = false;
|
||||
let resolve;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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(() =>
|
||||
|
|
@ -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();
|
||||
|
|
@ -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 = [];
|
||||
|
|
@ -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();
|
||||
|
|
@ -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 = [];
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
*/
|
||||
|
||||
export {renderToReadableStream, version} from './ReactDOMFizzServerBrowser.js';
|
||||
export {prerender} from './ReactDOMFizzStaticBrowser.js';
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
*/
|
||||
|
||||
export {renderToReadableStream, version} from './ReactDOMFizzServerEdge.js';
|
||||
export {prerender} from './ReactDOMFizzStaticEdge.js';
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
*/
|
||||
|
||||
export {renderToPipeableStream, version} from './ReactDOMFizzServerNode.js';
|
||||
export {prerenderToNodeStream} from './ReactDOMFizzStaticNode.js';
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user