From f77261bc6881e410d77bdad96fe1d4d07308e275 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Wed, 14 Aug 2024 15:51:24 -0700 Subject: [PATCH] [Flight] Implement prerender 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. --- packages/react-server-dom-esm/npm/static.js | 6 ++ .../react-server-dom-esm/npm/static.node.js | 12 ++++ .../server/react-flight-dom-server.node.js | 1 + .../react-flight-dom-server.node.stable.js | 19 +++++ packages/react-server-dom-esm/static.js | 13 ++++ packages/react-server-dom-esm/static.node.js | 10 +++ .../npm/static.browser.js | 12 ++++ .../npm/static.edge.js | 12 ++++ .../react-server-dom-turbopack/npm/static.js | 6 ++ .../npm/static.node.js | 12 ++++ .../npm/static.node.unbundled.js | 12 ++++ .../server/react-flight-dom-server.browser.js | 1 + .../react-flight-dom-server.browser.stable.js | 19 +++++ .../server/react-flight-dom-server.edge.js | 1 + .../react-flight-dom-server.edge.stable.js | 19 +++++ .../server/react-flight-dom-server.node.js | 1 + .../react-flight-dom-server.node.stable.js | 20 ++++++ .../react-flight-dom-server.node.unbundled.js | 1 + ...flight-dom-server.node.unbundled.stable.js | 20 ++++++ .../static.browser.js | 10 +++ .../react-server-dom-turbopack/static.edge.js | 10 +++ packages/react-server-dom-turbopack/static.js | 13 ++++ .../react-server-dom-turbopack/static.node.js | 10 +++ .../static.node.unbundled.js | 10 +++ .../npm/static.browser.js | 12 ++++ .../npm/static.edge.js | 12 ++++ .../react-server-dom-webpack/npm/static.js | 6 ++ .../npm/static.node.js | 12 ++++ .../npm/static.node.unbundled.js | 12 ++++ .../src/__tests__/ReactFlightDOM-test.js | 72 +++++++++++++++++++ .../server/react-flight-dom-server.browser.js | 1 + .../react-flight-dom-server.browser.stable.js | 19 +++++ .../server/react-flight-dom-server.edge.js | 1 + .../react-flight-dom-server.edge.stable.js | 19 +++++ .../server/react-flight-dom-server.node.js | 1 + .../react-flight-dom-server.node.stable.js | 20 ++++++ .../react-flight-dom-server.node.unbundled.js | 1 + ...flight-dom-server.node.unbundled.stable.js | 20 ++++++ .../static.browser.js | 10 +++ .../react-server-dom-webpack/static.edge.js | 10 +++ packages/react-server-dom-webpack/static.js | 13 ++++ .../react-server-dom-webpack/static.node.js | 10 +++ .../static.node.unbundled.js | 10 +++ .../react-server/src/ReactFlightServer.js | 19 +++++ scripts/shared/inlinedHostConfigs.js | 14 ++++ 45 files changed, 544 insertions(+) create mode 100644 packages/react-server-dom-esm/npm/static.js create mode 100644 packages/react-server-dom-esm/npm/static.node.js create mode 100644 packages/react-server-dom-esm/src/server/react-flight-dom-server.node.stable.js create mode 100644 packages/react-server-dom-esm/static.js create mode 100644 packages/react-server-dom-esm/static.node.js create mode 100644 packages/react-server-dom-turbopack/npm/static.browser.js create mode 100644 packages/react-server-dom-turbopack/npm/static.edge.js create mode 100644 packages/react-server-dom-turbopack/npm/static.js create mode 100644 packages/react-server-dom-turbopack/npm/static.node.js create mode 100644 packages/react-server-dom-turbopack/npm/static.node.unbundled.js create mode 100644 packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.stable.js create mode 100644 packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.stable.js create mode 100644 packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.stable.js create mode 100644 packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.stable.js create mode 100644 packages/react-server-dom-turbopack/static.browser.js create mode 100644 packages/react-server-dom-turbopack/static.edge.js create mode 100644 packages/react-server-dom-turbopack/static.js create mode 100644 packages/react-server-dom-turbopack/static.node.js create mode 100644 packages/react-server-dom-turbopack/static.node.unbundled.js create mode 100644 packages/react-server-dom-webpack/npm/static.browser.js create mode 100644 packages/react-server-dom-webpack/npm/static.edge.js create mode 100644 packages/react-server-dom-webpack/npm/static.js create mode 100644 packages/react-server-dom-webpack/npm/static.node.js create mode 100644 packages/react-server-dom-webpack/npm/static.node.unbundled.js create mode 100644 packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.stable.js create mode 100644 packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.stable.js create mode 100644 packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.stable.js create mode 100644 packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.stable.js create mode 100644 packages/react-server-dom-webpack/static.browser.js create mode 100644 packages/react-server-dom-webpack/static.edge.js create mode 100644 packages/react-server-dom-webpack/static.js create mode 100644 packages/react-server-dom-webpack/static.node.js create mode 100644 packages/react-server-dom-webpack/static.node.unbundled.js diff --git a/packages/react-server-dom-esm/npm/static.js b/packages/react-server-dom-esm/npm/static.js new file mode 100644 index 0000000000000..13a632e641179 --- /dev/null +++ b/packages/react-server-dom-esm/npm/static.js @@ -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.' +); diff --git a/packages/react-server-dom-esm/npm/static.node.js b/packages/react-server-dom-esm/npm/static.node.js new file mode 100644 index 0000000000000..ff0b9b2a42f2f --- /dev/null +++ b/packages/react-server-dom-esm/npm/static.node.js @@ -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; +} diff --git a/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.js b/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.js index d14d2b8ed362a..f24946fcae8bb 100644 --- a/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.js +++ b/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.js @@ -9,6 +9,7 @@ export { renderToPipeableStream, + prerenderToNodeStream, decodeReplyFromBusboy, decodeReply, decodeAction, diff --git a/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.stable.js b/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.stable.js new file mode 100644 index 0000000000000..d14d2b8ed362a --- /dev/null +++ b/packages/react-server-dom-esm/src/server/react-flight-dom-server.node.stable.js @@ -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'; diff --git a/packages/react-server-dom-esm/static.js b/packages/react-server-dom-esm/static.js new file mode 100644 index 0000000000000..83d8b8a017ff2 --- /dev/null +++ b/packages/react-server-dom-esm/static.js @@ -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.', +); diff --git a/packages/react-server-dom-esm/static.node.js b/packages/react-server-dom-esm/static.node.js new file mode 100644 index 0000000000000..d15eddc6f9b0e --- /dev/null +++ b/packages/react-server-dom-esm/static.node.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/npm/static.browser.js b/packages/react-server-dom-turbopack/npm/static.browser.js new file mode 100644 index 0000000000000..edc104a459383 --- /dev/null +++ b/packages/react-server-dom-turbopack/npm/static.browser.js @@ -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; +} diff --git a/packages/react-server-dom-turbopack/npm/static.edge.js b/packages/react-server-dom-turbopack/npm/static.edge.js new file mode 100644 index 0000000000000..c074f8ffe7ee4 --- /dev/null +++ b/packages/react-server-dom-turbopack/npm/static.edge.js @@ -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; +} diff --git a/packages/react-server-dom-turbopack/npm/static.js b/packages/react-server-dom-turbopack/npm/static.js new file mode 100644 index 0000000000000..13a632e641179 --- /dev/null +++ b/packages/react-server-dom-turbopack/npm/static.js @@ -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.' +); diff --git a/packages/react-server-dom-turbopack/npm/static.node.js b/packages/react-server-dom-turbopack/npm/static.node.js new file mode 100644 index 0000000000000..84083a965189b --- /dev/null +++ b/packages/react-server-dom-turbopack/npm/static.node.js @@ -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; +} diff --git a/packages/react-server-dom-turbopack/npm/static.node.unbundled.js b/packages/react-server-dom-turbopack/npm/static.node.unbundled.js new file mode 100644 index 0000000000000..e77863bf36a60 --- /dev/null +++ b/packages/react-server-dom-turbopack/npm/static.node.unbundled.js @@ -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; +} diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.js index 0100b65554aec..d8373ec551bc0 100644 --- a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.js +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.js @@ -9,6 +9,7 @@ export { renderToReadableStream, + prerender, decodeReply, decodeAction, decodeFormState, diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.stable.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.stable.js new file mode 100644 index 0000000000000..0100b65554aec --- /dev/null +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.browser.stable.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js index eb887b73a8ae8..9521ba6b68841 100644 --- a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.js @@ -9,6 +9,7 @@ export { renderToReadableStream, + prerender, decodeReply, decodeAction, decodeFormState, diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.stable.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.stable.js new file mode 100644 index 0000000000000..eb887b73a8ae8 --- /dev/null +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.edge.stable.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.js index 0d159704067ea..badc2ed50b691 100644 --- a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.js +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.js @@ -9,6 +9,7 @@ export { renderToPipeableStream, + prerenderToNodeStream, decodeReplyFromBusboy, decodeReply, decodeAction, diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.stable.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.stable.js new file mode 100644 index 0000000000000..0d159704067ea --- /dev/null +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.stable.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.js index 0d159704067ea..badc2ed50b691 100644 --- a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.js +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.js @@ -9,6 +9,7 @@ export { renderToPipeableStream, + prerenderToNodeStream, decodeReplyFromBusboy, decodeReply, decodeAction, diff --git a/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.stable.js b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.stable.js new file mode 100644 index 0000000000000..0d159704067ea --- /dev/null +++ b/packages/react-server-dom-turbopack/src/server/react-flight-dom-server.node.unbundled.stable.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/static.browser.js b/packages/react-server-dom-turbopack/static.browser.js new file mode 100644 index 0000000000000..2589789163206 --- /dev/null +++ b/packages/react-server-dom-turbopack/static.browser.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/static.edge.js b/packages/react-server-dom-turbopack/static.edge.js new file mode 100644 index 0000000000000..a39d54c73f579 --- /dev/null +++ b/packages/react-server-dom-turbopack/static.edge.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/static.js b/packages/react-server-dom-turbopack/static.js new file mode 100644 index 0000000000000..83d8b8a017ff2 --- /dev/null +++ b/packages/react-server-dom-turbopack/static.js @@ -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.', +); diff --git a/packages/react-server-dom-turbopack/static.node.js b/packages/react-server-dom-turbopack/static.node.js new file mode 100644 index 0000000000000..d15eddc6f9b0e --- /dev/null +++ b/packages/react-server-dom-turbopack/static.node.js @@ -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'; diff --git a/packages/react-server-dom-turbopack/static.node.unbundled.js b/packages/react-server-dom-turbopack/static.node.unbundled.js new file mode 100644 index 0000000000000..b2134459afc7a --- /dev/null +++ b/packages/react-server-dom-turbopack/static.node.unbundled.js @@ -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'; diff --git a/packages/react-server-dom-webpack/npm/static.browser.js b/packages/react-server-dom-webpack/npm/static.browser.js new file mode 100644 index 0000000000000..7d514abd6bf71 --- /dev/null +++ b/packages/react-server-dom-webpack/npm/static.browser.js @@ -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; +} diff --git a/packages/react-server-dom-webpack/npm/static.edge.js b/packages/react-server-dom-webpack/npm/static.edge.js new file mode 100644 index 0000000000000..a4ae48f55eb1b --- /dev/null +++ b/packages/react-server-dom-webpack/npm/static.edge.js @@ -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; +} diff --git a/packages/react-server-dom-webpack/npm/static.js b/packages/react-server-dom-webpack/npm/static.js new file mode 100644 index 0000000000000..13a632e641179 --- /dev/null +++ b/packages/react-server-dom-webpack/npm/static.js @@ -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.' +); diff --git a/packages/react-server-dom-webpack/npm/static.node.js b/packages/react-server-dom-webpack/npm/static.node.js new file mode 100644 index 0000000000000..dbc4179d3e788 --- /dev/null +++ b/packages/react-server-dom-webpack/npm/static.node.js @@ -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; +} diff --git a/packages/react-server-dom-webpack/npm/static.node.unbundled.js b/packages/react-server-dom-webpack/npm/static.node.unbundled.js new file mode 100644 index 0000000000000..73c8a3b86e9c7 --- /dev/null +++ b/packages/react-server-dom-webpack/npm/static.node.unbundled.js @@ -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; +} diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 718ddf6c5716c..faaf8aef01b0d 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.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', () => { , ); }); + + // @gate experimental + it('can prerender', async () => { + let resolveGreeting; + const greetingPromise = new Promise(resolve => { + resolveGreeting = resolve; + }); + + function App() { + return ( +
+ +
+ ); + } + + 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( + , + 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(
hello world
); + }); }); diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.js index 0100b65554aec..d8373ec551bc0 100644 --- a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.js +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.js @@ -9,6 +9,7 @@ export { renderToReadableStream, + prerender, decodeReply, decodeAction, decodeFormState, diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.stable.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.stable.js new file mode 100644 index 0000000000000..0100b65554aec --- /dev/null +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.browser.stable.js @@ -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'; diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js index eb887b73a8ae8..9521ba6b68841 100644 --- a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.js @@ -9,6 +9,7 @@ export { renderToReadableStream, + prerender, decodeReply, decodeAction, decodeFormState, diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.stable.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.stable.js new file mode 100644 index 0000000000000..eb887b73a8ae8 --- /dev/null +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.edge.stable.js @@ -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'; diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.js index 0d159704067ea..badc2ed50b691 100644 --- a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.js +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.js @@ -9,6 +9,7 @@ export { renderToPipeableStream, + prerenderToNodeStream, decodeReplyFromBusboy, decodeReply, decodeAction, diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.stable.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.stable.js new file mode 100644 index 0000000000000..0d159704067ea --- /dev/null +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.stable.js @@ -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'; diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.js index 0d159704067ea..badc2ed50b691 100644 --- a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.js +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.js @@ -9,6 +9,7 @@ export { renderToPipeableStream, + prerenderToNodeStream, decodeReplyFromBusboy, decodeReply, decodeAction, diff --git a/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.stable.js b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.stable.js new file mode 100644 index 0000000000000..0d159704067ea --- /dev/null +++ b/packages/react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled.stable.js @@ -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'; diff --git a/packages/react-server-dom-webpack/static.browser.js b/packages/react-server-dom-webpack/static.browser.js new file mode 100644 index 0000000000000..2589789163206 --- /dev/null +++ b/packages/react-server-dom-webpack/static.browser.js @@ -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'; diff --git a/packages/react-server-dom-webpack/static.edge.js b/packages/react-server-dom-webpack/static.edge.js new file mode 100644 index 0000000000000..a39d54c73f579 --- /dev/null +++ b/packages/react-server-dom-webpack/static.edge.js @@ -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'; diff --git a/packages/react-server-dom-webpack/static.js b/packages/react-server-dom-webpack/static.js new file mode 100644 index 0000000000000..83d8b8a017ff2 --- /dev/null +++ b/packages/react-server-dom-webpack/static.js @@ -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.', +); diff --git a/packages/react-server-dom-webpack/static.node.js b/packages/react-server-dom-webpack/static.node.js new file mode 100644 index 0000000000000..d15eddc6f9b0e --- /dev/null +++ b/packages/react-server-dom-webpack/static.node.js @@ -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'; diff --git a/packages/react-server-dom-webpack/static.node.unbundled.js b/packages/react-server-dom-webpack/static.node.unbundled.js new file mode 100644 index 0000000000000..b2134459afc7a --- /dev/null +++ b/packages/react-server-dom-webpack/static.node.unbundled.js @@ -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'; diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 6c9536d95acf7..5920cd0a2fbaf 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -376,6 +376,8 @@ export type Request = { taintCleanupQueue: Array, 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, ); } @@ -2886,6 +2898,8 @@ function logRecoverableError( } function fatalError(request: Request, error: mixed): void { + const onFatalError = request.onFatalError; + onFatalError(error); if (enableTaint) { cleanupTaintQueue(request); } @@ -3752,6 +3766,11 @@ function performWork(request: Request): void { logRecoverableError(request, error, null); fatalError(request, error); } finally { + if (request.abortableTasks.size === 0) { + // we're done rendering + const onAllReady = request.onAllReady; + onAllReady(); + } ReactSharedInternals.H = prevDispatcher; resetHooksForRequest(); currentRequest = prevRequest; diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index ae6a1e6e9478e..2177fc7adf368 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -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