Skip to content

Commit d49123f

Browse files
authored
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.](#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.
1 parent 22b2b1a commit d49123f

8 files changed

+49
-39
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzStatic-test.js

+7-9
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ describe('ReactDOMFizzStatic', () => {
3232
React = require('react');
3333
ReactDOM = require('react-dom');
3434
ReactDOMClient = require('react-dom/client');
35-
if (__EXPERIMENTAL__) {
36-
ReactDOMFizzStatic = require('react-dom/static');
37-
}
35+
ReactDOMFizzStatic = require('react-dom/static');
3836
Stream = require('stream');
3937
Suspense = React.Suspense;
4038

@@ -212,7 +210,6 @@ describe('ReactDOMFizzStatic', () => {
212210
return readText(text);
213211
}
214212

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

231228
const result = await promise;
232229

233-
expect(result.postponed).toBe(null);
230+
expect(result.postponed).toBe(
231+
gate(flags => flags.enableHalt || flags.enablePostpone)
232+
? null
233+
: undefined,
234+
);
234235

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

247-
// @gate experimental
248248
it('should support importMap option', async () => {
249249
const importMap = {
250250
foo: 'path/to/foo.js',
@@ -265,7 +265,6 @@ describe('ReactDOMFizzStatic', () => {
265265
]);
266266
});
267267

268-
// @gate experimental
269268
it('supports onHeaders', async () => {
270269
let headers;
271270
function onHeaders(x) {
@@ -300,7 +299,7 @@ describe('ReactDOMFizzStatic', () => {
300299
expect(getVisibleChildren(container)).toEqual('hello');
301300
});
302301

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

339-
// @gate experimental
340338
it('will prerender Suspense fallbacks before children', async () => {
341339
const values = [];
342340
function Indirection({children}) {

packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js

+6-18
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
4242
React = require('react');
4343
ReactDOM = require('react-dom');
4444
ReactDOMFizzServer = require('react-dom/server.browser');
45-
if (__EXPERIMENTAL__) {
46-
ReactDOMFizzStatic = require('react-dom/static.browser');
47-
}
45+
ReactDOMFizzStatic = require('react-dom/static.browser');
4846
Suspense = React.Suspense;
4947
container = document.createElement('div');
5048
document.body.appendChild(container);
@@ -131,7 +129,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
131129
await insertNodesAndExecuteScripts(temp, container, null);
132130
}
133131

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

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

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

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

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

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

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

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

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

379-
// @gate experimental
380368
it('should be able to abort before something suspends', async () => {
381369
const errors = [];
382370
const controller = new AbortController();
@@ -419,7 +407,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
419407
}
420408
});
421409

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

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

539-
// @gate experimental
540525
it('supports custom abort reasons with an Error', async () => {
541526
const promise = new Promise(r => {});
542527
function Wait() {
@@ -1610,7 +1595,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
16101595
);
16111596
});
16121597

1613-
// @gate experimental
16141598
it('logs an error if onHeaders throws but continues the prerender', async () => {
16151599
const errors = [];
16161600
function onError(error) {
@@ -1627,7 +1611,11 @@ describe('ReactDOMFizzStaticBrowser', () => {
16271611
onError,
16281612
}),
16291613
);
1630-
expect(prerendered.postponed).toBe(null);
1614+
expect(prerendered.postponed).toBe(
1615+
gate(flags => flags.enableHalt || flags.enablePostpone)
1616+
? null
1617+
: undefined,
1618+
);
16311619
expect(errors).toEqual(['bad onHeaders']);
16321620

16331621
await readIntoContainer(prerendered.prelude);

packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {
3838
createRootFormatContext,
3939
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
4040

41+
import {enablePostpone, enableHalt} from 'shared/ReactFeatureFlags';
42+
4143
import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
4244
ensureCorrectIsomorphicReactVersion();
4345

@@ -85,10 +87,15 @@ function prerender(
8587
{highWaterMark: 0},
8688
);
8789

88-
const result = {
89-
postponed: getPostponedState(request),
90-
prelude: stream,
91-
};
90+
const result: StaticResult =
91+
enablePostpone || enableHalt
92+
? {
93+
postponed: getPostponedState(request),
94+
prelude: stream,
95+
}
96+
: ({
97+
prelude: stream,
98+
}: any);
9299
resolve(result);
93100
}
94101

packages/react-dom/src/server/ReactDOMFizzStaticEdge.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import {
3838
createRootFormatContext,
3939
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
4040

41+
import {enablePostpone, enableHalt} from 'shared/ReactFeatureFlags';
42+
4143
import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
4244
ensureCorrectIsomorphicReactVersion();
4345

@@ -85,10 +87,15 @@ function prerender(
8587
{highWaterMark: 0},
8688
);
8789

88-
const result = {
89-
postponed: getPostponedState(request),
90-
prelude: stream,
91-
};
90+
const result: StaticResult =
91+
enablePostpone || enableHalt
92+
? {
93+
postponed: getPostponedState(request),
94+
prelude: stream,
95+
}
96+
: ({
97+
prelude: stream,
98+
}: any);
9299
resolve(result);
93100
}
94101

packages/react-dom/src/server/ReactDOMFizzStaticNode.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939
createRootFormatContext,
4040
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
4141

42+
import {enablePostpone, enableHalt} from 'shared/ReactFeatureFlags';
43+
4244
import {ensureCorrectIsomorphicReactVersion} from '../shared/ensureCorrectIsomorphicReactVersion';
4345
ensureCorrectIsomorphicReactVersion();
4446

@@ -94,10 +96,15 @@ function prerenderToNodeStream(
9496
});
9597
const writable = createFakeWritable(readable);
9698

97-
const result = {
98-
postponed: getPostponedState(request),
99-
prelude: readable,
100-
};
99+
const result: StaticResult =
100+
enablePostpone || enableHalt
101+
? {
102+
postponed: getPostponedState(request),
103+
prelude: readable,
104+
}
105+
: ({
106+
prelude: readable,
107+
}: any);
101108
resolve(result);
102109
}
103110
const resumableState = createResumableState(

packages/react-dom/src/server/react-dom-server.browser.stable.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
*/
99

1010
export {renderToReadableStream, version} from './ReactDOMFizzServerBrowser.js';
11+
export {prerender} from './ReactDOMFizzStaticBrowser.js';

packages/react-dom/src/server/react-dom-server.edge.stable.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
*/
99

1010
export {renderToReadableStream, version} from './ReactDOMFizzServerEdge.js';
11+
export {prerender} from './ReactDOMFizzStaticEdge.js';

packages/react-dom/src/server/react-dom-server.node.stable.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
*/
99

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

0 commit comments

Comments
 (0)