mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Flight] Implement prerender (#30686)
Prerendering in flight is similar to prerendering in Fizz. Instead of receiving a result (the stream) immediately a promise is returned which resolves to the stream when the prerender is complete. The promise will reject if the flight render fatally errors otherwise it will resolve when the render is completed or is aborted.
This commit is contained in:
parent
50d2197dd5
commit
fa6eab5854
|
|
@ -16,6 +16,13 @@ test('smoke test', async ({page}) => {
|
|||
await expect(page.getByTestId('promise-as-a-child-test')).toHaveText(
|
||||
'Promise as a child hydrates without errors: deferred text'
|
||||
);
|
||||
await expect(page.getByTestId('prerendered')).not.toBeAttached();
|
||||
|
||||
await expect(consoleErrors).toEqual([]);
|
||||
await expect(pageErrors).toEqual([]);
|
||||
|
||||
await page.goto('/prerender');
|
||||
await expect(page.getByTestId('prerendered')).toBeAttached();
|
||||
|
||||
await expect(consoleErrors).toEqual([]);
|
||||
await expect(pageErrors).toEqual([]);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function request(options, body) {
|
|||
});
|
||||
}
|
||||
|
||||
app.all('/', async function (req, res, next) {
|
||||
async function renderApp(req, res, next) {
|
||||
// Proxy the request to the regional server.
|
||||
const proxiedHeaders = {
|
||||
'X-Forwarded-Host': req.hostname,
|
||||
|
|
@ -102,12 +102,14 @@ app.all('/', async function (req, res, next) {
|
|||
proxiedHeaders['Content-type'] = req.get('Content-type');
|
||||
}
|
||||
|
||||
const requestsPrerender = req.path === '/prerender';
|
||||
|
||||
const promiseForData = request(
|
||||
{
|
||||
host: '127.0.0.1',
|
||||
port: 3001,
|
||||
method: req.method,
|
||||
path: '/',
|
||||
path: requestsPrerender ? '/?prerender=1' : '/',
|
||||
headers: proxiedHeaders,
|
||||
},
|
||||
req
|
||||
|
|
@ -210,7 +212,10 @@ app.all('/', async function (req, res, next) {
|
|||
res.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.all('/', renderApp);
|
||||
app.all('/prerender', renderApp);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
app.use(express.static('public'));
|
||||
|
|
|
|||
|
|
@ -105,8 +105,67 @@ async function renderApp(res, returnValue, formState) {
|
|||
pipe(res);
|
||||
}
|
||||
|
||||
async function prerenderApp(res, returnValue, formState) {
|
||||
const {prerenderToNodeStream} = await import(
|
||||
'react-server-dom-webpack/static'
|
||||
);
|
||||
// const m = require('../src/App.js');
|
||||
const m = await import('../src/App.js');
|
||||
|
||||
let moduleMap;
|
||||
let mainCSSChunks;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Read the module map from the HMR server in development.
|
||||
moduleMap = await (
|
||||
await fetch('http://localhost:3000/react-client-manifest.json')
|
||||
).json();
|
||||
mainCSSChunks = (
|
||||
await (
|
||||
await fetch('http://localhost:3000/entrypoint-manifest.json')
|
||||
).json()
|
||||
).main.css;
|
||||
} else {
|
||||
// Read the module map from the static build in production.
|
||||
moduleMap = JSON.parse(
|
||||
await readFile(
|
||||
path.resolve(__dirname, `../build/react-client-manifest.json`),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
mainCSSChunks = JSON.parse(
|
||||
await readFile(
|
||||
path.resolve(__dirname, `../build/entrypoint-manifest.json`),
|
||||
'utf8'
|
||||
)
|
||||
).main.css;
|
||||
}
|
||||
const App = m.default.default || m.default;
|
||||
const root = React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
// Prepend the App's tree with stylesheets required for this entrypoint.
|
||||
mainCSSChunks.map(filename =>
|
||||
React.createElement('link', {
|
||||
rel: 'stylesheet',
|
||||
href: filename,
|
||||
precedence: 'default',
|
||||
key: filename,
|
||||
})
|
||||
),
|
||||
React.createElement(App, {prerender: true})
|
||||
);
|
||||
// For client-invoked server actions we refresh the tree and return a return value.
|
||||
const payload = {root, returnValue, formState};
|
||||
const {prelude} = await prerenderToNodeStream(payload, moduleMap);
|
||||
prelude.pipe(res);
|
||||
}
|
||||
|
||||
app.get('/', async function (req, res) {
|
||||
await renderApp(res, null, null);
|
||||
if ('prerender' in req.query) {
|
||||
await prerenderApp(res, null, null);
|
||||
} else {
|
||||
await renderApp(res, null, null);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/', bodyParser.text(), async function (req, res) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ const promisedText = new Promise(resolve =>
|
|||
setTimeout(() => resolve('deferred text'), 100)
|
||||
);
|
||||
|
||||
export default async function App() {
|
||||
export default async function App({prerender}) {
|
||||
const res = await fetch('http://localhost:3001/todos');
|
||||
const todos = await res.json();
|
||||
return (
|
||||
|
|
@ -35,6 +35,11 @@ export default async function App() {
|
|||
</head>
|
||||
<body>
|
||||
<Container>
|
||||
{prerender ? (
|
||||
<meta data-testid="prerendered" name="prerendered" content="true" />
|
||||
) : (
|
||||
<meta content="when not prerendering we render this meta tag. When prerendering you will expect to see this tag and the one with data-testid=prerendered because we SSR one and hydrate the other" />
|
||||
)}
|
||||
<h1>{getServerState()}</h1>
|
||||
<React.Suspense fallback={null}>
|
||||
<div data-testid="promise-as-a-child-test">
|
||||
|
|
|
|||
6
packages/react-server-dom-esm/npm/static.js
vendored
Normal file
6
packages/react-server-dom-esm/npm/static.js
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
throw new Error(
|
||||
'The React Server Writer cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.'
|
||||
);
|
||||
12
packages/react-server-dom-esm/npm/static.node.js
Normal file
12
packages/react-server-dom-esm/npm/static.node.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-esm-server.node.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-esm-server.node.development.js');
|
||||
}
|
||||
|
||||
if (s.prerenderToNodeStream) {
|
||||
exports.prerenderToNodeStream = s.prerenderToNodeStream;
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
"client.node.js",
|
||||
"server.js",
|
||||
"server.node.js",
|
||||
"static.js",
|
||||
"static.node.js",
|
||||
"cjs/",
|
||||
"esm/"
|
||||
],
|
||||
|
|
@ -33,6 +35,11 @@
|
|||
"default": "./server.js"
|
||||
},
|
||||
"./server.node": "./server.node.js",
|
||||
"./static": {
|
||||
"react-server": "./static.node.js",
|
||||
"default": "./static.js"
|
||||
},
|
||||
"./static.node": "./static.node.js",
|
||||
"./node-loader": "./esm/react-server-dom-esm-node-loader.production.js",
|
||||
"./src/*": "./src/*.js",
|
||||
"./package.json": "./package.json"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import type {Busboy} from 'busboy';
|
|||
import type {Writable} from 'stream';
|
||||
import type {Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import {Readable} from 'stream';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
|
|
@ -123,6 +125,80 @@ function renderToPipeableStream(
|
|||
},
|
||||
};
|
||||
}
|
||||
function createFakeWritable(readable: any): Writable {
|
||||
// The current host config expects a Writable so we create
|
||||
// a fake writable for now to push into the Readable.
|
||||
return ({
|
||||
write(chunk) {
|
||||
return readable.push(chunk);
|
||||
},
|
||||
end() {
|
||||
readable.push(null);
|
||||
},
|
||||
destroy(error) {
|
||||
readable.destroy(error);
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
type PrerenderOptions = {
|
||||
environmentName?: string | (() => string),
|
||||
filterStackFrame?: (url: string, functionName: string) => boolean,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
prelude: Readable,
|
||||
};
|
||||
|
||||
function prerenderToNodeStream(
|
||||
model: ReactClientValue,
|
||||
moduleBasePath: ClientManifest,
|
||||
options?: PrerenderOptions,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const readable: Readable = new Readable({
|
||||
read() {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
});
|
||||
const writable = createFakeWritable(readable);
|
||||
resolve({prelude: readable});
|
||||
}
|
||||
|
||||
const request = createRequest(
|
||||
model,
|
||||
moduleBasePath,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReplyFromBusboy<T>(
|
||||
busboyStream: Busboy,
|
||||
|
|
@ -207,6 +283,7 @@ function decodeReply<T>(
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerNode';
|
||||
13
packages/react-server-dom-esm/static.js
vendored
Normal file
13
packages/react-server-dom-esm/static.js
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
throw new Error(
|
||||
'The React Server cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.',
|
||||
);
|
||||
10
packages/react-server-dom-esm/static.node.js
Normal file
10
packages/react-server-dom-esm/static.node.js
Normal 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 {prerenderToNodeStream} from './src/server/react-flight-dom-server.node';
|
||||
12
packages/react-server-dom-turbopack/npm/static.browser.js
Normal file
12
packages/react-server-dom-turbopack/npm/static.browser.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.browser.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.browser.development.js');
|
||||
}
|
||||
|
||||
if (s.prerender) {
|
||||
exports.prerender = s.prerender;
|
||||
}
|
||||
12
packages/react-server-dom-turbopack/npm/static.edge.js
Normal file
12
packages/react-server-dom-turbopack/npm/static.edge.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.edge.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.edge.development.js');
|
||||
}
|
||||
|
||||
if (s.prerender) {
|
||||
exports.prerender = s.prerender;
|
||||
}
|
||||
6
packages/react-server-dom-turbopack/npm/static.js
vendored
Normal file
6
packages/react-server-dom-turbopack/npm/static.js
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
throw new Error(
|
||||
'The React Server Writer cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.'
|
||||
);
|
||||
12
packages/react-server-dom-turbopack/npm/static.node.js
Normal file
12
packages/react-server-dom-turbopack/npm/static.node.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.node.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.node.development.js');
|
||||
}
|
||||
|
||||
if (s.prerenderToNodeStream) {
|
||||
exports.prerenderToNodeStream = s.prerenderToNodeStream;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.node.unbundled.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-turbopack-server.node.unbundled.development.js');
|
||||
}
|
||||
|
||||
if (s.prerenderToNodeStream) {
|
||||
exports.prerenderToNodeStream = s.prerenderToNodeStream;
|
||||
}
|
||||
|
|
@ -22,6 +22,11 @@
|
|||
"server.edge.js",
|
||||
"server.node.js",
|
||||
"server.node.unbundled.js",
|
||||
"static.js",
|
||||
"static.browser.js",
|
||||
"static.edge.js",
|
||||
"static.node.js",
|
||||
"static.node.unbundled.js",
|
||||
"node-register.js",
|
||||
"cjs/",
|
||||
"esm/"
|
||||
|
|
@ -63,6 +68,24 @@
|
|||
"./server.edge": "./server.edge.js",
|
||||
"./server.node": "./server.node.js",
|
||||
"./server.node.unbundled": "./server.node.unbundled.js",
|
||||
"./static": {
|
||||
"react-server": {
|
||||
"workerd": "./static.edge.js",
|
||||
"deno": "./static.browser.js",
|
||||
"node": {
|
||||
"turbopack": "./static.node.js",
|
||||
"webpack": "./static.node.js",
|
||||
"default": "./static.node.unbundled.js"
|
||||
},
|
||||
"edge-light": "./static.edge.js",
|
||||
"browser": "./static.browser.js"
|
||||
},
|
||||
"default": "./static.js"
|
||||
},
|
||||
"./static.browser": "./static.browser.js",
|
||||
"./static.edge": "./static.edge.js",
|
||||
"./static.node": "./static.node.js",
|
||||
"./static.node.unbundled": "./static.node.unbundled.js",
|
||||
"./node-loader": "./esm/react-server-dom-turbopack-node-loader.production.js",
|
||||
"./node-register": "./node-register.js",
|
||||
"./src/*": "./src/*.js",
|
||||
|
|
|
|||
|
|
@ -100,6 +100,65 @@ function renderToReadableStream(
|
|||
return stream;
|
||||
}
|
||||
|
||||
type StaticResult = {
|
||||
prelude: ReadableStream,
|
||||
};
|
||||
|
||||
function prerender(
|
||||
model: ReactClientValue,
|
||||
turbopackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
startWork(request);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowing(request, controller);
|
||||
},
|
||||
cancel: (reason): ?Promise<void> => {
|
||||
stopFlowing(request);
|
||||
abort(request, reason);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
resolve({prelude: stream});
|
||||
}
|
||||
const request = createRequest(
|
||||
model,
|
||||
turbopackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
turbopackMap: ServerManifest,
|
||||
|
|
@ -121,4 +180,10 @@ function decodeReply<T>(
|
|||
return root;
|
||||
}
|
||||
|
||||
export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -100,6 +100,65 @@ function renderToReadableStream(
|
|||
return stream;
|
||||
}
|
||||
|
||||
type StaticResult = {
|
||||
prelude: ReadableStream,
|
||||
};
|
||||
|
||||
function prerender(
|
||||
model: ReactClientValue,
|
||||
turbopackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
startWork(request);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowing(request, controller);
|
||||
},
|
||||
cancel: (reason): ?Promise<void> => {
|
||||
stopFlowing(request);
|
||||
abort(request, reason);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
resolve({prelude: stream});
|
||||
}
|
||||
const request = createRequest(
|
||||
model,
|
||||
turbopackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
turbopackMap: ServerManifest,
|
||||
|
|
@ -121,4 +180,10 @@ function decodeReply<T>(
|
|||
return root;
|
||||
}
|
||||
|
||||
export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import type {Busboy} from 'busboy';
|
|||
import type {Writable} from 'stream';
|
||||
import type {Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import {Readable} from 'stream';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
|
|
@ -125,6 +127,81 @@ function renderToPipeableStream(
|
|||
};
|
||||
}
|
||||
|
||||
function createFakeWritable(readable: any): Writable {
|
||||
// The current host config expects a Writable so we create
|
||||
// a fake writable for now to push into the Readable.
|
||||
return ({
|
||||
write(chunk) {
|
||||
return readable.push(chunk);
|
||||
},
|
||||
end() {
|
||||
readable.push(null);
|
||||
},
|
||||
destroy(error) {
|
||||
readable.destroy(error);
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
type PrerenderOptions = {
|
||||
environmentName?: string | (() => string),
|
||||
filterStackFrame?: (url: string, functionName: string) => boolean,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
prelude: Readable,
|
||||
};
|
||||
|
||||
function prerenderToNodeStream(
|
||||
model: ReactClientValue,
|
||||
turbopackMap: ClientManifest,
|
||||
options?: PrerenderOptions,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const readable: Readable = new Readable({
|
||||
read() {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
});
|
||||
const writable = createFakeWritable(readable);
|
||||
resolve({prelude: readable});
|
||||
}
|
||||
|
||||
const request = createRequest(
|
||||
model,
|
||||
turbopackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReplyFromBusboy<T>(
|
||||
busboyStream: Busboy,
|
||||
turbopackMap: ServerManifest,
|
||||
|
|
@ -208,6 +285,7 @@ function decodeReply<T>(
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerBrowser';
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerEdge';
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerNode';
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerNode';
|
||||
10
packages/react-server-dom-turbopack/static.browser.js
Normal file
10
packages/react-server-dom-turbopack/static.browser.js
Normal 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 {prerender} from './src/server/react-flight-dom-server.browser';
|
||||
10
packages/react-server-dom-turbopack/static.edge.js
Normal file
10
packages/react-server-dom-turbopack/static.edge.js
Normal 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 {prerender} from './src/server/react-flight-dom-server.edge';
|
||||
13
packages/react-server-dom-turbopack/static.js
vendored
Normal file
13
packages/react-server-dom-turbopack/static.js
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
throw new Error(
|
||||
'The React Server cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.',
|
||||
);
|
||||
10
packages/react-server-dom-turbopack/static.node.js
Normal file
10
packages/react-server-dom-turbopack/static.node.js
Normal 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 {prerenderToNodeStream} from './src/server/react-flight-dom-server.node';
|
||||
10
packages/react-server-dom-turbopack/static.node.unbundled.js
Normal file
10
packages/react-server-dom-turbopack/static.node.unbundled.js
Normal 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 {prerenderToNodeStream} from './src/server/react-flight-dom-server.node.unbundled';
|
||||
12
packages/react-server-dom-webpack/npm/static.browser.js
Normal file
12
packages/react-server-dom-webpack/npm/static.browser.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-webpack-server.browser.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-webpack-server.browser.development.js');
|
||||
}
|
||||
|
||||
if (s.prerender) {
|
||||
exports.prerender = s.prerender;
|
||||
}
|
||||
12
packages/react-server-dom-webpack/npm/static.edge.js
Normal file
12
packages/react-server-dom-webpack/npm/static.edge.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-webpack-server.edge.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-webpack-server.edge.development.js');
|
||||
}
|
||||
|
||||
if (s.prerender) {
|
||||
exports.prerender = s.prerender;
|
||||
}
|
||||
6
packages/react-server-dom-webpack/npm/static.js
vendored
Normal file
6
packages/react-server-dom-webpack/npm/static.js
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
throw new Error(
|
||||
'The React Server Writer cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.'
|
||||
);
|
||||
12
packages/react-server-dom-webpack/npm/static.node.js
Normal file
12
packages/react-server-dom-webpack/npm/static.node.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-webpack-server.node.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-webpack-server.node.development.js');
|
||||
}
|
||||
|
||||
if (s.prerenderToNodeStream) {
|
||||
exports.prerenderToNodeStream = s.prerenderToNodeStream;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
var s;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
s = require('./cjs/react-server-dom-webpack-server.node.unbundled.production.js');
|
||||
} else {
|
||||
s = require('./cjs/react-server-dom-webpack-server.node.unbundled.development.js');
|
||||
}
|
||||
|
||||
if (s.prerenderToNodeStream) {
|
||||
exports.prerenderToNodeStream = s.prerenderToNodeStream;
|
||||
}
|
||||
|
|
@ -23,6 +23,11 @@
|
|||
"server.edge.js",
|
||||
"server.node.js",
|
||||
"server.node.unbundled.js",
|
||||
"static.js",
|
||||
"static.browser.js",
|
||||
"static.edge.js",
|
||||
"static.node.js",
|
||||
"static.node.unbundled.js",
|
||||
"node-register.js",
|
||||
"cjs/",
|
||||
"esm/"
|
||||
|
|
@ -63,6 +68,23 @@
|
|||
"./server.edge": "./server.edge.js",
|
||||
"./server.node": "./server.node.js",
|
||||
"./server.node.unbundled": "./server.node.unbundled.js",
|
||||
"./static": {
|
||||
"react-server": {
|
||||
"workerd": "./static.edge.js",
|
||||
"deno": "./static.browser.js",
|
||||
"node": {
|
||||
"webpack": "./static.node.js",
|
||||
"default": "./static.node.unbundled.js"
|
||||
},
|
||||
"edge-light": "./static.edge.js",
|
||||
"browser": "./static.browser.js"
|
||||
},
|
||||
"default": "./static.js"
|
||||
},
|
||||
"./static.browser": "./static.browser.js",
|
||||
"./static.edge": "./static.edge.js",
|
||||
"./static.node": "./static.node.js",
|
||||
"./static.node.unbundled": "./static.node.unbundled.js",
|
||||
"./node-loader": "./esm/react-server-dom-webpack-node-loader.production.js",
|
||||
"./node-register": "./node-register.js",
|
||||
"./src/*": "./src/*.js",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
'use strict';
|
||||
|
||||
import {patchSetImmediate} from '../../../../scripts/jest/patchSetImmediate';
|
||||
import {Readable} from 'stream';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
|
|
@ -28,6 +29,7 @@ let React;
|
|||
let FlightReactDOM;
|
||||
let ReactDOMClient;
|
||||
let ReactServerDOMServer;
|
||||
let ReactServerDOMStaticServer;
|
||||
let ReactServerDOMClient;
|
||||
let ReactDOMFizzServer;
|
||||
let ReactDOMStaticServer;
|
||||
|
|
@ -59,12 +61,20 @@ describe('ReactFlightDOM', () => {
|
|||
jest.mock('react-server-dom-webpack/server', () =>
|
||||
require('react-server-dom-webpack/server.node.unbundled'),
|
||||
);
|
||||
if (__EXPERIMENTAL__) {
|
||||
jest.mock('react-server-dom-webpack/static', () =>
|
||||
require('react-server-dom-webpack/static.node.unbundled'),
|
||||
);
|
||||
}
|
||||
const WebpackMock = require('./utils/WebpackMock');
|
||||
clientExports = WebpackMock.clientExports;
|
||||
clientModuleError = WebpackMock.clientModuleError;
|
||||
webpackMap = WebpackMock.webpackMap;
|
||||
|
||||
ReactServerDOMServer = require('react-server-dom-webpack/server');
|
||||
if (__EXPERIMENTAL__) {
|
||||
ReactServerDOMStaticServer = require('react-server-dom-webpack/static');
|
||||
}
|
||||
|
||||
// This reset is to load modules for the SSR/Browser scope.
|
||||
jest.unmock('react-server-dom-webpack/server');
|
||||
|
|
@ -2650,4 +2660,66 @@ describe('ReactFlightDOM', () => {
|
|||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('can prerender', async () => {
|
||||
let resolveGreeting;
|
||||
const greetingPromise = new Promise(resolve => {
|
||||
resolveGreeting = resolve;
|
||||
});
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Greeting />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function Greeting() {
|
||||
await greetingPromise;
|
||||
return 'hello world';
|
||||
}
|
||||
|
||||
const {pendingResult} = await serverAct(async () => {
|
||||
// destructure trick to avoid the act scope from awaiting the returned value
|
||||
return {
|
||||
pendingResult: ReactServerDOMStaticServer.prerenderToNodeStream(
|
||||
<App />,
|
||||
webpackMap,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
resolveGreeting();
|
||||
const {prelude} = await pendingResult;
|
||||
|
||||
const response = ReactServerDOMClient.createFromReadableStream(
|
||||
Readable.toWeb(prelude),
|
||||
);
|
||||
|
||||
const {writable: fizzWritable, readable: fizzReadable} = getTestStream();
|
||||
|
||||
function ClientApp() {
|
||||
return use(response);
|
||||
}
|
||||
|
||||
const shellErrors = [];
|
||||
await serverAct(async () => {
|
||||
ReactDOMFizzServer.renderToPipeableStream(
|
||||
React.createElement(ClientApp),
|
||||
{
|
||||
onShellError(error) {
|
||||
shellErrors.push(error.message);
|
||||
},
|
||||
},
|
||||
).pipe(fizzWritable);
|
||||
});
|
||||
|
||||
expect(shellErrors).toEqual([]);
|
||||
|
||||
const container = document.createElement('div');
|
||||
await readInto(container, fizzReadable);
|
||||
expect(getMeaningfulChildren(container)).toEqual(<div>hello world</div>);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -100,6 +100,65 @@ function renderToReadableStream(
|
|||
return stream;
|
||||
}
|
||||
|
||||
type StaticResult = {
|
||||
prelude: ReadableStream,
|
||||
};
|
||||
|
||||
function prerender(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
startWork(request);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowing(request, controller);
|
||||
},
|
||||
cancel: (reason): ?Promise<void> => {
|
||||
stopFlowing(request);
|
||||
abort(request, reason);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
resolve({prelude: stream});
|
||||
}
|
||||
const request = createRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
webpackMap: ServerManifest,
|
||||
|
|
@ -121,4 +180,10 @@ function decodeReply<T>(
|
|||
return root;
|
||||
}
|
||||
|
||||
export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -100,6 +100,65 @@ function renderToReadableStream(
|
|||
return stream;
|
||||
}
|
||||
|
||||
type StaticResult = {
|
||||
prelude: ReadableStream,
|
||||
};
|
||||
|
||||
function prerender(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: Options,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const stream = new ReadableStream(
|
||||
{
|
||||
type: 'bytes',
|
||||
start: (controller): ?Promise<void> => {
|
||||
startWork(request);
|
||||
},
|
||||
pull: (controller): ?Promise<void> => {
|
||||
startFlowing(request, controller);
|
||||
},
|
||||
cancel: (reason): ?Promise<void> => {
|
||||
stopFlowing(request);
|
||||
abort(request, reason);
|
||||
},
|
||||
},
|
||||
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
|
||||
{highWaterMark: 0},
|
||||
);
|
||||
resolve({prelude: stream});
|
||||
}
|
||||
const request = createRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReply<T>(
|
||||
body: string | FormData,
|
||||
webpackMap: ServerManifest,
|
||||
|
|
@ -121,4 +180,10 @@ function decodeReply<T>(
|
|||
return root;
|
||||
}
|
||||
|
||||
export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import type {Busboy} from 'busboy';
|
|||
import type {Writable} from 'stream';
|
||||
import type {Thenable} from 'shared/ReactTypes';
|
||||
|
||||
import {Readable} from 'stream';
|
||||
|
||||
import {
|
||||
createRequest,
|
||||
startWork,
|
||||
|
|
@ -125,6 +127,81 @@ function renderToPipeableStream(
|
|||
};
|
||||
}
|
||||
|
||||
function createFakeWritable(readable: any): Writable {
|
||||
// The current host config expects a Writable so we create
|
||||
// a fake writable for now to push into the Readable.
|
||||
return ({
|
||||
write(chunk) {
|
||||
return readable.push(chunk);
|
||||
},
|
||||
end() {
|
||||
readable.push(null);
|
||||
},
|
||||
destroy(error) {
|
||||
readable.destroy(error);
|
||||
},
|
||||
}: any);
|
||||
}
|
||||
|
||||
type PrerenderOptions = {
|
||||
environmentName?: string | (() => string),
|
||||
filterStackFrame?: (url: string, functionName: string) => boolean,
|
||||
onError?: (error: mixed) => void,
|
||||
onPostpone?: (reason: string) => void,
|
||||
identifierPrefix?: string,
|
||||
temporaryReferences?: TemporaryReferenceSet,
|
||||
signal?: AbortSignal,
|
||||
};
|
||||
|
||||
type StaticResult = {
|
||||
prelude: Readable,
|
||||
};
|
||||
|
||||
function prerenderToNodeStream(
|
||||
model: ReactClientValue,
|
||||
webpackMap: ClientManifest,
|
||||
options?: PrerenderOptions,
|
||||
): Promise<StaticResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFatalError = reject;
|
||||
function onAllReady() {
|
||||
const readable: Readable = new Readable({
|
||||
read() {
|
||||
startFlowing(request, writable);
|
||||
},
|
||||
});
|
||||
const writable = createFakeWritable(readable);
|
||||
resolve({prelude: readable});
|
||||
}
|
||||
|
||||
const request = createRequest(
|
||||
model,
|
||||
webpackMap,
|
||||
options ? options.onError : undefined,
|
||||
options ? options.identifierPrefix : undefined,
|
||||
options ? options.onPostpone : undefined,
|
||||
options ? options.temporaryReferences : undefined,
|
||||
__DEV__ && options ? options.environmentName : undefined,
|
||||
__DEV__ && options ? options.filterStackFrame : undefined,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeReplyFromBusboy<T>(
|
||||
busboyStream: Busboy,
|
||||
webpackMap: ServerManifest,
|
||||
|
|
@ -208,6 +285,7 @@ function decodeReply<T>(
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerBrowser';
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToReadableStream,
|
||||
prerender,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerEdge';
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerNode';
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
export {
|
||||
renderToPipeableStream,
|
||||
prerenderToNodeStream,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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,
|
||||
decodeReplyFromBusboy,
|
||||
decodeReply,
|
||||
decodeAction,
|
||||
decodeFormState,
|
||||
registerServerReference,
|
||||
registerClientReference,
|
||||
createClientModuleProxy,
|
||||
createTemporaryReferenceSet,
|
||||
} from './ReactFlightDOMServerNode';
|
||||
10
packages/react-server-dom-webpack/static.browser.js
Normal file
10
packages/react-server-dom-webpack/static.browser.js
Normal 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 {prerender} from './src/server/react-flight-dom-server.browser';
|
||||
10
packages/react-server-dom-webpack/static.edge.js
Normal file
10
packages/react-server-dom-webpack/static.edge.js
Normal 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 {prerender} from './src/server/react-flight-dom-server.edge';
|
||||
13
packages/react-server-dom-webpack/static.js
vendored
Normal file
13
packages/react-server-dom-webpack/static.js
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
throw new Error(
|
||||
'The React Server cannot be used outside a react-server environment. ' +
|
||||
'You must configure Node.js using the `--conditions react-server` flag.',
|
||||
);
|
||||
10
packages/react-server-dom-webpack/static.node.js
Normal file
10
packages/react-server-dom-webpack/static.node.js
Normal 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 {prerenderToNodeStream} from './src/server/react-flight-dom-server.node';
|
||||
10
packages/react-server-dom-webpack/static.node.unbundled.js
Normal file
10
packages/react-server-dom-webpack/static.node.unbundled.js
Normal 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 {prerenderToNodeStream} from './src/server/react-flight-dom-server.node.unbundled';
|
||||
19
packages/react-server/src/ReactFlightServer.js
vendored
19
packages/react-server/src/ReactFlightServer.js
vendored
|
|
@ -376,6 +376,8 @@ export type Request = {
|
|||
taintCleanupQueue: Array<string | bigint>,
|
||||
onError: (error: mixed) => ?string,
|
||||
onPostpone: (reason: string) => void,
|
||||
onAllReady: () => void,
|
||||
onFatalError: mixed => void,
|
||||
// DEV-only
|
||||
environmentName: () => string,
|
||||
filterStackFrame: (url: string, functionName: string) => boolean,
|
||||
|
|
@ -435,6 +437,8 @@ function RequestInstance(
|
|||
temporaryReferences: void | TemporaryReferenceSet,
|
||||
environmentName: void | string | (() => string), // DEV-only
|
||||
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
|
||||
onAllReady: void | (() => void),
|
||||
onFatalError: void | ((error: mixed) => void),
|
||||
) {
|
||||
if (
|
||||
ReactSharedInternals.A !== null &&
|
||||
|
|
@ -486,6 +490,8 @@ function RequestInstance(
|
|||
this.onError = onError === undefined ? defaultErrorHandler : onError;
|
||||
this.onPostpone =
|
||||
onPostpone === undefined ? defaultPostponeHandler : onPostpone;
|
||||
this.onAllReady = onAllReady === undefined ? noop : onAllReady;
|
||||
this.onFatalError = onFatalError === undefined ? noop : onFatalError;
|
||||
|
||||
if (__DEV__) {
|
||||
this.environmentName =
|
||||
|
|
@ -513,6 +519,8 @@ function RequestInstance(
|
|||
pingedTasks.push(rootTask);
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
export function createRequest(
|
||||
model: ReactClientValue,
|
||||
bundlerConfig: ClientManifest,
|
||||
|
|
@ -522,6 +530,8 @@ export function createRequest(
|
|||
temporaryReferences: void | TemporaryReferenceSet,
|
||||
environmentName: void | string | (() => string), // DEV-only
|
||||
filterStackFrame: void | ((url: string, functionName: string) => boolean), // DEV-only
|
||||
onAllReady: void | (() => void),
|
||||
onFatalError: void | (() => void),
|
||||
): Request {
|
||||
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
|
||||
return new RequestInstance(
|
||||
|
|
@ -533,6 +543,8 @@ export function createRequest(
|
|||
temporaryReferences,
|
||||
environmentName,
|
||||
filterStackFrame,
|
||||
onAllReady,
|
||||
onFatalError,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2890,6 +2902,8 @@ function logRecoverableError(
|
|||
}
|
||||
|
||||
function fatalError(request: Request, error: mixed): void {
|
||||
const onFatalError = request.onFatalError;
|
||||
onFatalError(error);
|
||||
if (enableTaint) {
|
||||
cleanupTaintQueue(request);
|
||||
}
|
||||
|
|
@ -3753,6 +3767,11 @@ function performWork(request: Request): void {
|
|||
if (request.destination !== null) {
|
||||
flushCompletedChunks(request, request.destination);
|
||||
}
|
||||
if (request.abortableTasks.size === 0) {
|
||||
// we're done rendering
|
||||
const onAllReady = request.onAllReady;
|
||||
onAllReady();
|
||||
}
|
||||
} catch (error) {
|
||||
logRecoverableError(request, error, null);
|
||||
fatalError(request, error);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ module.exports = [
|
|||
'react-server-dom-webpack/client.node.unbundled',
|
||||
'react-server-dom-webpack/server',
|
||||
'react-server-dom-webpack/server.node.unbundled',
|
||||
'react-server-dom-webpack/static',
|
||||
'react-server-dom-webpack/static.node.unbundled',
|
||||
'react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js',
|
||||
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
|
||||
|
|
@ -82,6 +84,8 @@ module.exports = [
|
|||
'react-server-dom-webpack/client.node',
|
||||
'react-server-dom-webpack/server',
|
||||
'react-server-dom-webpack/server.node',
|
||||
'react-server-dom-webpack/static',
|
||||
'react-server-dom-webpack/static.node',
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js',
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer.js',
|
||||
'react-server-dom-webpack/src/server/react-flight-dom-server.node',
|
||||
|
|
@ -123,6 +127,8 @@ module.exports = [
|
|||
'react-server-dom-turbopack/client.node.unbundled',
|
||||
'react-server-dom-turbopack/server',
|
||||
'react-server-dom-turbopack/server.node.unbundled',
|
||||
'react-server-dom-turbopack/static',
|
||||
'react-server-dom-turbopack/static.node.unbundled',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-turbopack/client.node.unbundled
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerNode.js',
|
||||
'react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled',
|
||||
|
|
@ -164,6 +170,8 @@ module.exports = [
|
|||
'react-server-dom-turbopack/client.node',
|
||||
'react-server-dom-turbopack/server',
|
||||
'react-server-dom-turbopack/server.node',
|
||||
'react-server-dom-turbopack/static',
|
||||
'react-server-dom-turbopack/static.node',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-turbopack/client.node
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer.js',
|
||||
|
|
@ -238,6 +246,7 @@ module.exports = [
|
|||
'react-server-dom-webpack/client',
|
||||
'react-server-dom-webpack/client.browser',
|
||||
'react-server-dom-webpack/server.browser',
|
||||
'react-server-dom-webpack/static.browser',
|
||||
'react-server-dom-webpack/src/client/ReactFlightDOMClientBrowser.js', // react-server-dom-webpack/client.browser
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js',
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackBrowser.js',
|
||||
|
|
@ -299,6 +308,7 @@ module.exports = [
|
|||
'react-server-dom-turbopack/client',
|
||||
'react-server-dom-turbopack/client.browser',
|
||||
'react-server-dom-turbopack/server.browser',
|
||||
'react-server-dom-turbopack/static.browser',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightDOMClientBrowser.js', // react-server-dom-turbopack/client.browser
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackBrowser.js',
|
||||
|
|
@ -339,6 +349,7 @@ module.exports = [
|
|||
'react-server-dom-webpack',
|
||||
'react-server-dom-webpack/client.edge',
|
||||
'react-server-dom-webpack/server.edge',
|
||||
'react-server-dom-webpack/static.edge',
|
||||
'react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-webpack/client.edge
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js',
|
||||
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer.js',
|
||||
|
|
@ -378,6 +389,7 @@ module.exports = [
|
|||
'react-server-dom-turbopack',
|
||||
'react-server-dom-turbopack/client.edge',
|
||||
'react-server-dom-turbopack/server.edge',
|
||||
'react-server-dom-turbopack/static.edge',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-turbopack/client.edge
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopack.js',
|
||||
'react-server-dom-turbopack/src/client/ReactFlightClientConfigBundlerTurbopackServer.js',
|
||||
|
|
@ -419,6 +431,8 @@ module.exports = [
|
|||
'react-server-dom-esm/client.node',
|
||||
'react-server-dom-esm/server',
|
||||
'react-server-dom-esm/server.node',
|
||||
'react-server-dom-esm/static',
|
||||
'react-server-dom-esm/static.node',
|
||||
'react-server-dom-esm/src/client/ReactFlightDOMClientNode.js', // react-server-dom-esm/client.node
|
||||
'react-server-dom-esm/src/server/react-flight-dom-server.node',
|
||||
'react-server-dom-esm/src/server/ReactFlightDOMServerNode.js', // react-server-dom-esm/src/server/react-flight-dom-server.node
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user