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/package.json b/packages/react-server-dom-esm/package.json
index bd9e9c394962e..a1f8f17f45014 100644
--- a/packages/react-server-dom-esm/package.json
+++ b/packages/react-server-dom-esm/package.json
@@ -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"
diff --git a/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js
index c724d89c2b435..bb65ef4b659a7 100644
--- a/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js
+++ b/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js
@@ -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 {
+ 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(
busboyStream: Busboy,
@@ -207,6 +283,7 @@ function decodeReply(
export {
renderToPipeableStream,
+ prerenderToNodeStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
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/package.json b/packages/react-server-dom-turbopack/package.json
index 93e694b3a3e1b..93cd7d37a04ae 100644
--- a/packages/react-server-dom-turbopack/package.json
+++ b/packages/react-server-dom-turbopack/package.json
@@ -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",
diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js
index 58a87992d6c6e..4e5d5171efb19 100644
--- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js
+++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerBrowser.js
@@ -100,6 +100,64 @@ function renderToReadableStream(
return stream;
}
+type StaticResult = {
+ prelude: ReadableStream,
+};
+
+function prerender(
+ model: ReactClientValue,
+ turbopackMap: ClientManifest,
+ options?: Options,
+): Promise {
+ return new Promise((resolve, reject) => {
+ const onFatalError = reject;
+ function onAllReady() {
+ const stream = new ReadableStream(
+ {
+ type: 'bytes',
+ start: (controller): ?Promise => {
+ startWork(request);
+ },
+ pull: (controller): ?Promise => {
+ startFlowing(request, controller);
+ },
+ cancel: (reason): ?Promise => {
+ 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);
+ }
+ }
+ });
+}
+
function decodeReply(
body: string | FormData,
turbopackMap: ServerManifest,
@@ -121,4 +179,10 @@ function decodeReply(
return root;
}
-export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
+export {
+ renderToReadableStream,
+ prerender,
+ decodeReply,
+ decodeAction,
+ decodeFormState,
+};
diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js
index 58a87992d6c6e..4e5d5171efb19 100644
--- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js
+++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerEdge.js
@@ -100,6 +100,64 @@ function renderToReadableStream(
return stream;
}
+type StaticResult = {
+ prelude: ReadableStream,
+};
+
+function prerender(
+ model: ReactClientValue,
+ turbopackMap: ClientManifest,
+ options?: Options,
+): Promise {
+ return new Promise((resolve, reject) => {
+ const onFatalError = reject;
+ function onAllReady() {
+ const stream = new ReadableStream(
+ {
+ type: 'bytes',
+ start: (controller): ?Promise => {
+ startWork(request);
+ },
+ pull: (controller): ?Promise => {
+ startFlowing(request, controller);
+ },
+ cancel: (reason): ?Promise => {
+ 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);
+ }
+ }
+ });
+}
+
function decodeReply(
body: string | FormData,
turbopackMap: ServerManifest,
@@ -121,4 +179,10 @@ function decodeReply(
return root;
}
-export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
+export {
+ renderToReadableStream,
+ prerender,
+ decodeReply,
+ decodeAction,
+ decodeFormState,
+};
diff --git a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js
index d76bcb5759b0e..e484d4b7e77d5 100644
--- a/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js
+++ b/packages/react-server-dom-turbopack/src/server/ReactFlightDOMServerNode.js
@@ -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 {
+ 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(
busboyStream: Busboy,
turbopackMap: ServerManifest,
@@ -208,6 +285,7 @@ function decodeReply(
export {
renderToPipeableStream,
+ prerenderToNodeStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
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/package.json b/packages/react-server-dom-webpack/package.json
index b8e2ccf92e3e3..7a1fe29d4d4a9 100644
--- a/packages/react-server-dom-webpack/package.json
+++ b/packages/react-server-dom-webpack/package.json
@@ -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",
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/ReactFlightDOMServerBrowser.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js
index 6a6f2936f846b..daf05b3283229 100644
--- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js
+++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerBrowser.js
@@ -100,6 +100,64 @@ function renderToReadableStream(
return stream;
}
+type StaticResult = {
+ prelude: ReadableStream,
+};
+
+function prerender(
+ model: ReactClientValue,
+ webpackMap: ClientManifest,
+ options?: Options,
+): Promise {
+ return new Promise((resolve, reject) => {
+ const onFatalError = reject;
+ function onAllReady() {
+ const stream = new ReadableStream(
+ {
+ type: 'bytes',
+ start: (controller): ?Promise => {
+ startWork(request);
+ },
+ pull: (controller): ?Promise => {
+ startFlowing(request, controller);
+ },
+ cancel: (reason): ?Promise => {
+ 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);
+ }
+ }
+ });
+}
+
function decodeReply(
body: string | FormData,
webpackMap: ServerManifest,
@@ -121,4 +179,10 @@ function decodeReply(
return root;
}
-export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
+export {
+ renderToReadableStream,
+ prerender,
+ decodeReply,
+ decodeAction,
+ decodeFormState,
+};
diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js
index 6a6f2936f846b..daf05b3283229 100644
--- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js
+++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerEdge.js
@@ -100,6 +100,64 @@ function renderToReadableStream(
return stream;
}
+type StaticResult = {
+ prelude: ReadableStream,
+};
+
+function prerender(
+ model: ReactClientValue,
+ webpackMap: ClientManifest,
+ options?: Options,
+): Promise {
+ return new Promise((resolve, reject) => {
+ const onFatalError = reject;
+ function onAllReady() {
+ const stream = new ReadableStream(
+ {
+ type: 'bytes',
+ start: (controller): ?Promise => {
+ startWork(request);
+ },
+ pull: (controller): ?Promise => {
+ startFlowing(request, controller);
+ },
+ cancel: (reason): ?Promise => {
+ 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);
+ }
+ }
+ });
+}
+
function decodeReply(
body: string | FormData,
webpackMap: ServerManifest,
@@ -121,4 +179,10 @@ function decodeReply(
return root;
}
-export {renderToReadableStream, decodeReply, decodeAction, decodeFormState};
+export {
+ renderToReadableStream,
+ prerender,
+ decodeReply,
+ decodeAction,
+ decodeFormState,
+};
diff --git a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js
index 73479bdf3ef04..1506259476703 100644
--- a/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js
+++ b/packages/react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js
@@ -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 {
+ 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(
busboyStream: Busboy,
webpackMap: ServerManifest,
@@ -208,6 +285,7 @@ function decodeReply(
export {
renderToPipeableStream,
+ prerenderToNodeStream,
decodeReplyFromBusboy,
decodeReply,
decodeAction,
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 4a07e036530a2..be5706c927c7c 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