@samitouri / QOS-React-1 / commits / 378973b387

[Flight] Move `react-server-dom-webpack/*.unbundled` to private `react-server-dom-unbundled` (#35290)

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>

Sebastian "Sebbie" Silbermann committed Dec 5, 2025 at 03:59 UTC 378973b387b6a6f287e451dd0356099180684c3c
47 files changed +2908 -137
.eslintrc.js
+1
@@ -331,6 +331,7 @@ module.exports = {
331 'packages/react-server-dom-turbopack/**/*.js',
332 'packages/react-server-dom-parcel/**/*.js',
333 'packages/react-server-dom-fb/**/*.js',
334 + 'packages/react-server-dom-unbundled/**/*.js',
335 'packages/react-test-renderer/**/*.js',
336 'packages/react-debug-tools/**/*.js',
337 'packages/react-devtools-extensions/**/*.js',
.github/workflows/runtime_build_and_test.yml
+30 -54
@@ -41,7 +41,7 @@ jobs:
41 with:
42 path: |
43 **/node_modules
44 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
44 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
45 lookup-only: true
46 - uses: actions/setup-node@v4
47 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -55,10 +55,8 @@ jobs:
55 with:
56 path: |
57 **/node_modules
58 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
59 - restore-keys: |
60 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
61 - runtime-node_modules-v6-
58 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
59 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
60 - run: yarn install --frozen-lockfile
61 if: steps.node_modules.outputs.cache-hit != 'true'
62 - name: Save cache
@@ -67,7 +65,7 @@ jobs:
65 with:
66 path: |
67 **/node_modules
70 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
68 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
69
70 runtime_compiler_node_modules_cache:
71 name: Cache Runtime, Compiler node_modules
@@ -82,7 +80,7 @@ jobs:
80 with:
81 path: |
82 **/node_modules
85 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
83 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
84 lookup-only: true
85 - uses: actions/setup-node@v4
86 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -98,10 +96,8 @@ jobs:
96 with:
97 path: |
98 **/node_modules
101 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
102 - restore-keys: |
103 - runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
104 - runtime-and-compiler-node_modules-v6-
99 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
100 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
101 - run: yarn install --frozen-lockfile
102 if: steps.node_modules.outputs.cache-hit != 'true'
103 - run: yarn --cwd compiler install --frozen-lockfile
@@ -112,7 +108,7 @@ jobs:
108 with:
109 path: |
110 **/node_modules
115 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
111 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
112
113 # ----- FLOW -----
114 discover_flow_inline_configs:
@@ -154,10 +150,8 @@ jobs:
150 with:
151 path: |
152 **/node_modules
157 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
158 - restore-keys: |
159 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
160 - runtime-node_modules-v6-
153 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
154 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
155 - name: Ensure clean build directory
156 run: rm -rf build
157 - run: yarn install --frozen-lockfile
@@ -184,10 +178,8 @@ jobs:
178 with:
179 path: |
180 **/node_modules
187 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
188 - restore-keys: |
189 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
190 - runtime-node_modules-v6-
181 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
182 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
183 - name: Ensure clean build directory
184 run: rm -rf build
185 - run: yarn install --frozen-lockfile
@@ -216,7 +208,7 @@ jobs:
208 with:
209 path: |
210 **/node_modules
219 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
211 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
212 - name: Ensure clean build directory
213 run: rm -rf build
214 - run: yarn install --frozen-lockfile
@@ -274,10 +266,8 @@ jobs:
266 with:
267 path: |
268 **/node_modules
277 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
278 - restore-keys: |
279 - runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
280 - runtime-and-compiler-node_modules-v6-
269 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
270 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
271 - name: Ensure clean build directory
272 run: rm -rf build
273 - run: yarn install --frozen-lockfile
@@ -306,7 +296,7 @@ jobs:
296 with:
297 path: |
298 **/node_modules
309 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
299 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
300 - name: Install runtime dependencies
301 run: yarn install --frozen-lockfile
302 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -349,10 +339,8 @@ jobs:
339 with:
340 path: |
341 **/node_modules
352 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
353 - restore-keys: |
354 - runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
355 - runtime-and-compiler-node_modules-v6-
342 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
343 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
344 - name: Ensure clean build directory
345 run: rm -rf build
346 - run: yarn install --frozen-lockfile
@@ -440,10 +428,8 @@ jobs:
428 with:
429 path: |
430 **/node_modules
443 - key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
444 - restore-keys: |
445 - runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
446 - runtime-and-compiler-node_modules-v6-
431 + key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
432 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
433 - name: Ensure clean build directory
434 run: rm -rf build
435 - run: yarn install --frozen-lockfile
@@ -483,10 +469,8 @@ jobs:
469 with:
470 path: |
471 **/node_modules
486 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
487 - restore-keys: |
488 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
489 - runtime-node_modules-v6-
472 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
473 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
474 - name: Ensure clean build directory
475 run: rm -rf build
476 - run: yarn install --frozen-lockfile
@@ -548,10 +532,8 @@ jobs:
532 with:
533 path: |
534 **/node_modules
551 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
552 - restore-keys: |
553 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
554 - runtime-node_modules-v6-
535 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
536 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
537 - name: Ensure clean build directory
538 run: rm -rf build
539 - run: yarn install --frozen-lockfile
@@ -588,10 +570,8 @@ jobs:
570 with:
571 path: |
572 **/node_modules
591 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
592 - restore-keys: |
593 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
594 - runtime-node_modules-v6-
573 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
574 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
575 - name: Ensure clean build directory
576 run: rm -rf build
577 - run: yarn install --frozen-lockfile
@@ -740,10 +720,8 @@ jobs:
720 with:
721 path: |
722 **/node_modules
743 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
744 - restore-keys: |
745 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
746 - runtime-node_modules-v6-
723 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
724 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
725 - name: Ensure clean build directory
726 run: rm -rf build
727 - run: yarn install --frozen-lockfile
@@ -802,10 +780,8 @@ jobs:
780 with:
781 path: |
782 **/node_modules
805 - key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
806 - restore-keys: |
807 - runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
808 - runtime-node_modules-v6-
783 + key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
784 + # Don't use restore-keys here. Otherwise the cache grows indefinitely.
785 - name: Ensure clean build directory
786 run: rm -rf build
787 - run: yarn install --frozen-lockfile
fixtures/flight/loader/region.js
+1 -1
@@ -3,7 +3,7 @@ import {
3 load as reactLoad,
4 getSource as getSourceImpl,
5 transformSource as reactTransformSource,
6 -} from 'react-server-dom-webpack/node-loader';
6 +} from 'react-server-dom-unbundled/node-loader';
7
8 export {resolve};
9
fixtures/flight/server/global.js
+1 -1
@@ -36,7 +36,7 @@ const http = require('http');
36 const React = require('react');
37
38 const {renderToPipeableStream} = require('react-dom/server');
39 -const {createFromNodeStream} = require('react-server-dom-webpack/client');
39 +const {createFromNodeStream} = require('react-server-dom-unbundled/client');
40 const {PassThrough} = require('stream');
41
42 const app = express();
fixtures/flight/server/region.js
+5 -4
@@ -5,7 +5,8 @@
5 const path = require('path');
6 const url = require('url');
7
8 -const register = require('react-server-dom-webpack/node-register');
8 +const register = require('react-server-dom-unbundled/node-register');
9 +// TODO: This seems to have no effect anymore. Remove?
10 register();
11
12 const babelRegister = require('@babel/register');
@@ -76,7 +77,7 @@ function getDebugChannel(req) {
77
78 async function renderApp(res, returnValue, formState, noCache, debugChannel) {
79 const {renderToPipeableStream} = await import(
79 - 'react-server-dom-webpack/server'
80 + 'react-server-dom-unbundled/server'
81 );
82 // const m = require('../src/App.js');
83 const m = await import('../src/App.js');
@@ -134,7 +135,7 @@ async function renderApp(res, returnValue, formState, noCache, debugChannel) {
135
136 async function prerenderApp(res, returnValue, formState, noCache) {
137 const {prerenderToNodeStream} = await import(
137 - 'react-server-dom-webpack/static'
138 + 'react-server-dom-unbundled/static'
139 );
140 // const m = require('../src/App.js');
141 const m = await import('../src/App.js');
@@ -202,7 +203,7 @@ app.get('/', async function (req, res) {
203 app.post('/', bodyParser.text(), async function (req, res) {
204 const noCache = req.headers['cache-control'] === 'no-cache';
205 const {decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState} =
205 - await import('react-server-dom-webpack/server');
206 + await import('react-server-dom-unbundled/server');
207 const serverReference = req.get('rsc-action');
208 if (serverReference) {
209 // This is the client-side case
fixtures/flight/src/App.js
+1 -1
@@ -1,5 +1,5 @@
1 import * as React from 'react';
2 -import {renderToReadableStream} from 'react-server-dom-webpack/server';
2 +import {renderToReadableStream} from 'react-server-dom-unbundled/server';
3 import {createFromReadableStream} from 'react-server-dom-webpack/client';
4 import {PassThrough, Readable} from 'stream';
5
package.json
+1 -1
@@ -126,7 +126,7 @@
126 "build-for-devtools": "cross-env yarn build react/index,react/jsx,react/compiler-runtime,react-dom/index,react-dom/client,react-dom/unstable_testing,react-dom/test-utils,react-is,react-debug-tools,scheduler,react-test-renderer,react-refresh,react-art --type=NODE --release-channel=experimental",
127 "build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
128 "build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
129 - "build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",
129 + "build-for-flight-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react.react-server,react-dom/index,react-dom/client,react-dom/server,react-dom.react-server,react-dom-server.node,react-dom-server-legacy.node,scheduler,react-server-dom-webpack/,react-server-dom-unbundled/ --type=NODE_DEV,ESM_PROD,NODE_ES2015 && mv ./build/node_modules ./build/oss-experimental",
130 "build-for-vt-dev": "cross-env RELEASE_CHANNEL=experimental node ./scripts/rollup/build.js react/index,react/jsx,react-dom/index,react-dom/client,react-dom/server,react-dom-server.node,react-dom-server-legacy.node,scheduler --type=NODE_DEV && mv ./build/node_modules ./build/oss-experimental",
131 "flow-typed-install": "yarn flow-typed install --skip --skipFlowRestart --ignore-deps=dev",
132 "linc": "node ./scripts/tasks/linc.js",
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-unbundled.js renamed
+4 -4
@@ -6,13 +6,13 @@
6 *
7 * @flow
8 */
9 +
10 export {default as rendererVersion} from 'shared/ReactVersion';
10 -export const rendererPackageName = 'react-server-dom-webpack';
11 +export const rendererPackageName = 'react-server-dom-unbundled';
12
13 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
14 export * from 'react-client/src/ReactClientConsoleConfigServer';
14 -export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
15 -export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
16 -export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
15 +export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode';
16 +export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer';
17 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
18 export const usedWithSSR = true;
packages/react-client/src/forks/ReactFlightClientConfig.dom-node.js
+2 -2
@@ -6,13 +6,13 @@
6 *
7 * @flow
8 */
9 -
9 export {default as rendererVersion} from 'shared/ReactVersion';
10 export const rendererPackageName = 'react-server-dom-webpack';
11
12 export * from 'react-client/src/ReactFlightClientStreamConfigNode';
13 export * from 'react-client/src/ReactClientConsoleConfigServer';
15 -export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode';
14 +export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
15 +export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
16 export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
17 export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
18 export const usedWithSSR = true;
packages/react-server-dom-unbundled/README.md new
+5
@@ -0,0 +1,5 @@
1 +# react-server-dom-unbundled
2 +
3 +Test-only React Flight bindings for DOM using Node.js.
4 +
5 +This only exists for internal testing.
packages/react-server-dom-unbundled/client.js renamed
+1 -1
@@ -7,4 +7,4 @@
7 * @flow
8 */
9
10 -export * from './src/client/react-flight-dom-client.node.unbundled';
10 +export * from './src/client/react-flight-dom-client.node';
packages/react-server-dom-unbundled/esm/package.json new
+3
@@ -0,0 +1,3 @@
1 +{
2 + "type": "module"
3 +}
packages/react-server-dom-unbundled/esm/react-server-dom-unbundled-node-loader.production.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export * from '../src/ReactFlightUnbundledNodeLoader.js';
packages/react-server-dom-unbundled/index.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +throw new Error('Use react-server-dom-webpack/client instead.');
packages/react-server-dom-unbundled/node-register.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +module.exports = require('./src/ReactFlightUnbundledNodeRegister');
packages/react-server-dom-unbundled/npm/client.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-server-dom-unbundled-client.node.production.js');
5 +} else {
6 + module.exports = require('./cjs/react-server-dom-unbundled-client.node.development.js');
7 +}
packages/react-server-dom-unbundled/npm/esm/package.json new
+3
@@ -0,0 +1,3 @@
1 +{
2 + "type": "module"
3 +}
packages/react-server-dom-unbundled/npm/index.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +'use strict';
11 +
12 +throw new Error('Use react-server-dom-unbundled/client instead.');
packages/react-server-dom-unbundled/npm/node-register.js new
+3
@@ -0,0 +1,3 @@
1 +'use strict';
2 +
3 +module.exports = require('./cjs/react-server-dom-unbundled-node-register.js');
packages/react-server-dom-unbundled/npm/server.js new
+6
@@ -0,0 +1,6 @@
1 +'use strict';
2 +
3 +throw new Error(
4 + 'The React Server Writer cannot be used outside a react-server environment. ' +
5 + 'You must configure Node.js using the `--conditions react-server` flag.'
6 +);
packages/react-server-dom-unbundled/npm/server.node.js new
+20
@@ -0,0 +1,20 @@
1 +'use strict';
2 +
3 +var s;
4 +if (process.env.NODE_ENV === 'production') {
5 + s = require('./cjs/react-server-dom-unbundled-server.node.production.js');
6 +} else {
7 + s = require('./cjs/react-server-dom-unbundled-server.node.development.js');
8 +}
9 +
10 +exports.renderToReadableStream = s.renderToReadableStream;
11 +exports.renderToPipeableStream = s.renderToPipeableStream;
12 +exports.decodeReply = s.decodeReply;
13 +exports.decodeReplyFromBusboy = s.decodeReplyFromBusboy;
14 +exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
15 +exports.decodeAction = s.decodeAction;
16 +exports.decodeFormState = s.decodeFormState;
17 +exports.registerServerReference = s.registerServerReference;
18 +exports.registerClientReference = s.registerClientReference;
19 +exports.createClientModuleProxy = s.createClientModuleProxy;
20 +exports.createTemporaryReferenceSet = s.createTemporaryReferenceSet;
packages/react-server-dom-unbundled/npm/static.js new
+6
@@ -0,0 +1,6 @@
1 +'use strict';
2 +
3 +throw new Error(
4 + 'The React Server Writer cannot be used outside a react-server environment. ' +
5 + 'You must configure Node.js using the `--conditions react-server` flag.'
6 +);
packages/react-server-dom-unbundled/npm/static.node.js new
+11
@@ -0,0 +1,11 @@
1 +'use strict';
2 +
3 +var s;
4 +if (process.env.NODE_ENV === 'production') {
5 + s = require('./cjs/react-server-dom-unbundled-server.node.production.js');
6 +} else {
7 + s = require('./cjs/react-server-dom-unbundled-server.node.development.js');
8 +}
9 +
10 +exports.prerender = s.prerender;
11 +exports.prerenderToNodeStream = s.prerenderToNodeStream;
packages/react-server-dom-unbundled/package.json new
+46
@@ -0,0 +1,46 @@
1 +{
2 + "name": "react-server-dom-unbundled",
3 + "description": "React Server Components bindings for DOM using Node.js. This only exists for internal testing.",
4 + "version": "0.0.0",
5 + "private": true,
6 + "files": [
7 + "LICENSE",
8 + "README.md",
9 + "index.js",
10 + "client.js",
11 + "server.js",
12 + "server.node.js",
13 + "static.js",
14 + "static.node.js",
15 + "node-register.js",
16 + "cjs/",
17 + "esm/"
18 + ],
19 + "exports": {
20 + ".": "./index.js",
21 + "./client": "./client.js",
22 + "./server": {
23 + "react-server": "./server.node.js",
24 + "default": "./server.js"
25 + },
26 + "./server.node": "./server.node.js",
27 + "./static": {
28 + "react-server": "./static.node.js",
29 + "default": "./static.js"
30 + },
31 + "./static.node": "./static.node.js",
32 + "./node-loader": "./esm/react-server-dom-unbundled-node-loader.production.js",
33 + "./node-register": "./node-register.js",
34 + "./src/*": "./src/*.js",
35 + "./package.json": "./package.json"
36 + },
37 + "main": "index.js",
38 + "peerDependencies": {
39 + "react": "^19.0.0",
40 + "react-dom": "^19.0.0"
41 + },
42 + "dependencies": {
43 + "acorn-loose": "^8.3.0",
44 + "webpack-sources": "^3.2.0"
45 + }
46 +}
packages/react-server-dom-unbundled/server.js new
+13
@@ -0,0 +1,13 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +throw new Error(
11 + 'The React Server cannot be used outside a react-server environment. ' +
12 + 'You must configure Node.js using the `--conditions react-server` flag.',
13 +);
packages/react-server-dom-unbundled/server.node.js renamed
+1 -1
@@ -19,4 +19,4 @@ export {
19 registerClientReference,
20 createClientModuleProxy,
21 createTemporaryReferenceSet,
22 -} from './src/server/react-flight-dom-server.node.unbundled';
22 +} from './src/server/react-flight-dom-server.node';
packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeLoader.js new
+804
@@ -0,0 +1,804 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import * as acorn from 'acorn-loose';
11 +
12 +import readMappings from 'webpack-sources/lib/helpers/readMappings.js';
13 +import createMappingsSerializer from 'webpack-sources/lib/helpers/createMappingsSerializer.js';
14 +
15 +type ResolveContext = {
16 + conditions: Array<string>,
17 + parentURL: string | void,
18 +};
19 +
20 +type ResolveFunction = (
21 + string,
22 + ResolveContext,
23 + ResolveFunction,
24 +) => {url: string} | Promise<{url: string}>;
25 +
26 +type GetSourceContext = {
27 + format: string,
28 +};
29 +
30 +type GetSourceFunction = (
31 + string,
32 + GetSourceContext,
33 + GetSourceFunction,
34 +) => Promise<{source: Source}>;
35 +
36 +type TransformSourceContext = {
37 + format: string,
38 + url: string,
39 +};
40 +
41 +type TransformSourceFunction = (
42 + Source,
43 + TransformSourceContext,
44 + TransformSourceFunction,
45 +) => Promise<{source: Source}>;
46 +
47 +type LoadContext = {
48 + conditions: Array<string>,
49 + format: string | null | void,
50 + importAssertions: Object,
51 +};
52 +
53 +type LoadFunction = (
54 + string,
55 + LoadContext,
56 + LoadFunction,
57 +) => Promise<{format: string, shortCircuit?: boolean, source: Source}>;
58 +
59 +type Source = string | ArrayBuffer | Uint8Array;
60 +
61 +let warnedAboutConditionsFlag = false;
62 +
63 +let stashedGetSource: null | GetSourceFunction = null;
64 +let stashedResolve: null | ResolveFunction = null;
65 +
66 +export async function resolve(
67 + specifier: string,
68 + context: ResolveContext,
69 + defaultResolve: ResolveFunction,
70 +): Promise<{url: string}> {
71 + // We stash this in case we end up needing to resolve export * statements later.
72 + stashedResolve = defaultResolve;
73 +
74 + if (!context.conditions.includes('react-server')) {
75 + context = {
76 + ...context,
77 + conditions: [...context.conditions, 'react-server'],
78 + };
79 + if (!warnedAboutConditionsFlag) {
80 + warnedAboutConditionsFlag = true;
81 + // eslint-disable-next-line react-internal/no-production-logging
82 + console.warn(
83 + 'You did not run Node.js with the `--conditions react-server` flag. ' +
84 + 'Any "react-server" override will only work with ESM imports.',
85 + );
86 + }
87 + }
88 + return await defaultResolve(specifier, context, defaultResolve);
89 +}
90 +
91 +export async function getSource(
92 + url: string,
93 + context: GetSourceContext,
94 + defaultGetSource: GetSourceFunction,
95 +): Promise<{source: Source}> {
96 + // We stash this in case we end up needing to resolve export * statements later.
97 + stashedGetSource = defaultGetSource;
98 + return defaultGetSource(url, context, defaultGetSource);
99 +}
100 +
101 +type ExportedEntry = {
102 + localName: string,
103 + exportedName: string,
104 + type: null | string,
105 + loc: {
106 + start: {line: number, column: number},
107 + end: {line: number, column: number},
108 + },
109 + originalLine: number,
110 + originalColumn: number,
111 + originalSource: number,
112 + nameIndex: number,
113 +};
114 +
115 +function addExportedEntry(
116 + exportedEntries: Array<ExportedEntry>,
117 + localNames: Set<string>,
118 + localName: string,
119 + exportedName: string,
120 + type: null | 'function',
121 + loc: {
122 + start: {line: number, column: number},
123 + end: {line: number, column: number},
124 + },
125 +) {
126 + if (localNames.has(localName)) {
127 + // If the same local name is exported more than once, we only need one of the names.
128 + return;
129 + }
130 + exportedEntries.push({
131 + localName,
132 + exportedName,
133 + type,
134 + loc,
135 + originalLine: -1,
136 + originalColumn: -1,
137 + originalSource: -1,
138 + nameIndex: -1,
139 + });
140 +}
141 +
142 +function addLocalExportedNames(
143 + exportedEntries: Array<ExportedEntry>,
144 + localNames: Set<string>,
145 + node: any,
146 +) {
147 + switch (node.type) {
148 + case 'Identifier':
149 + addExportedEntry(
150 + exportedEntries,
151 + localNames,
152 + node.name,
153 + node.name,
154 + null,
155 + node.loc,
156 + );
157 + return;
158 + case 'ObjectPattern':
159 + for (let i = 0; i < node.properties.length; i++)
160 + addLocalExportedNames(exportedEntries, localNames, node.properties[i]);
161 + return;
162 + case 'ArrayPattern':
163 + for (let i = 0; i < node.elements.length; i++) {
164 + const element = node.elements[i];
165 + if (element)
166 + addLocalExportedNames(exportedEntries, localNames, element);
167 + }
168 + return;
169 + case 'Property':
170 + addLocalExportedNames(exportedEntries, localNames, node.value);
171 + return;
172 + case 'AssignmentPattern':
173 + addLocalExportedNames(exportedEntries, localNames, node.left);
174 + return;
175 + case 'RestElement':
176 + addLocalExportedNames(exportedEntries, localNames, node.argument);
177 + return;
178 + case 'ParenthesizedExpression':
179 + addLocalExportedNames(exportedEntries, localNames, node.expression);
180 + return;
181 + }
182 +}
183 +
184 +function transformServerModule(
185 + source: string,
186 + program: any,
187 + url: string,
188 + sourceMap: any,
189 + loader: LoadFunction,
190 +): string {
191 + const body = program.body;
192 +
193 + // This entry list needs to be in source location order.
194 + const exportedEntries: Array<ExportedEntry> = [];
195 + // Dedupe set.
196 + const localNames: Set<string> = new Set();
197 +
198 + for (let i = 0; i < body.length; i++) {
199 + const node = body[i];
200 + switch (node.type) {
201 + case 'ExportAllDeclaration':
202 + // If export * is used, the other file needs to explicitly opt into "use server" too.
203 + break;
204 + case 'ExportDefaultDeclaration':
205 + if (node.declaration.type === 'Identifier') {
206 + addExportedEntry(
207 + exportedEntries,
208 + localNames,
209 + node.declaration.name,
210 + 'default',
211 + null,
212 + node.declaration.loc,
213 + );
214 + } else if (node.declaration.type === 'FunctionDeclaration') {
215 + if (node.declaration.id) {
216 + addExportedEntry(
217 + exportedEntries,
218 + localNames,
219 + node.declaration.id.name,
220 + 'default',
221 + 'function',
222 + node.declaration.id.loc,
223 + );
224 + } else {
225 + // TODO: This needs to be rewritten inline because it doesn't have a local name.
226 + }
227 + }
228 + continue;
229 + case 'ExportNamedDeclaration':
230 + if (node.declaration) {
231 + if (node.declaration.type === 'VariableDeclaration') {
232 + const declarations = node.declaration.declarations;
233 + for (let j = 0; j < declarations.length; j++) {
234 + addLocalExportedNames(
235 + exportedEntries,
236 + localNames,
237 + declarations[j].id,
238 + );
239 + }
240 + } else {
241 + const name = node.declaration.id.name;
242 + addExportedEntry(
243 + exportedEntries,
244 + localNames,
245 + name,
246 + name,
247 +
248 + node.declaration.type === 'FunctionDeclaration'
249 + ? 'function'
250 + : null,
251 + node.declaration.id.loc,
252 + );
253 + }
254 + }
255 + if (node.specifiers) {
256 + const specifiers = node.specifiers;
257 + for (let j = 0; j < specifiers.length; j++) {
258 + const specifier = specifiers[j];
259 + addExportedEntry(
260 + exportedEntries,
261 + localNames,
262 + specifier.local.name,
263 + specifier.exported.name,
264 + null,
265 + specifier.local.loc,
266 + );
267 + }
268 + }
269 + continue;
270 + }
271 + }
272 +
273 + let mappings =
274 + sourceMap && typeof sourceMap.mappings === 'string'
275 + ? sourceMap.mappings
276 + : '';
277 + let newSrc = source;
278 +
279 + if (exportedEntries.length > 0) {
280 + let lastSourceIndex = 0;
281 + let lastOriginalLine = 0;
282 + let lastOriginalColumn = 0;
283 + let lastNameIndex = 0;
284 + let sourceLineCount = 0;
285 + let lastMappedLine = 0;
286 +
287 + if (sourceMap) {
288 + // We iterate source mapping entries and our matched exports in parallel to source map
289 + // them to their original location.
290 + let nextEntryIdx = 0;
291 + let nextEntryLine = exportedEntries[nextEntryIdx].loc.start.line;
292 + let nextEntryColumn = exportedEntries[nextEntryIdx].loc.start.column;
293 + readMappings(
294 + mappings,
295 + (
296 + generatedLine: number,
297 + generatedColumn: number,
298 + sourceIndex: number,
299 + originalLine: number,
300 + originalColumn: number,
301 + nameIndex: number,
302 + ) => {
303 + if (
304 + generatedLine > nextEntryLine ||
305 + (generatedLine === nextEntryLine &&
306 + generatedColumn > nextEntryColumn)
307 + ) {
308 + // We're past the entry which means that the best match we have is the previous entry.
309 + if (lastMappedLine === nextEntryLine) {
310 + // Match
311 + exportedEntries[nextEntryIdx].originalLine = lastOriginalLine;
312 + exportedEntries[nextEntryIdx].originalColumn = lastOriginalColumn;
313 + exportedEntries[nextEntryIdx].originalSource = lastSourceIndex;
314 + exportedEntries[nextEntryIdx].nameIndex = lastNameIndex;
315 + } else {
316 + // Skip if we didn't have any mappings on the exported line.
317 + }
318 + nextEntryIdx++;
319 + if (nextEntryIdx < exportedEntries.length) {
320 + nextEntryLine = exportedEntries[nextEntryIdx].loc.start.line;
321 + nextEntryColumn = exportedEntries[nextEntryIdx].loc.start.column;
322 + } else {
323 + nextEntryLine = -1;
324 + nextEntryColumn = -1;
325 + }
326 + }
327 + lastMappedLine = generatedLine;
328 + if (sourceIndex > -1) {
329 + lastSourceIndex = sourceIndex;
330 + }
331 + if (originalLine > -1) {
332 + lastOriginalLine = originalLine;
333 + }
334 + if (originalColumn > -1) {
335 + lastOriginalColumn = originalColumn;
336 + }
337 + if (nameIndex > -1) {
338 + lastNameIndex = nameIndex;
339 + }
340 + },
341 + );
342 + if (nextEntryIdx < exportedEntries.length) {
343 + if (lastMappedLine === nextEntryLine) {
344 + // Match
345 + exportedEntries[nextEntryIdx].originalLine = lastOriginalLine;
346 + exportedEntries[nextEntryIdx].originalColumn = lastOriginalColumn;
347 + exportedEntries[nextEntryIdx].originalSource = lastSourceIndex;
348 + exportedEntries[nextEntryIdx].nameIndex = lastNameIndex;
349 + }
350 + }
351 +
352 + for (
353 + let lastIdx = mappings.length - 1;
354 + lastIdx >= 0 && mappings[lastIdx] === ';';
355 + lastIdx--
356 + ) {
357 + // If the last mapped lines don't contain any segments, we don't get a callback from readMappings
358 + // so we need to pad the number of mapped lines, with one for each empty line.
359 + lastMappedLine++;
360 + }
361 +
362 + sourceLineCount = program.loc.end.line;
363 + if (sourceLineCount < lastMappedLine) {
364 + throw new Error(
365 + 'The source map has more mappings than there are lines.',
366 + );
367 + }
368 + // If the original source string had more lines than there are mappings in the source map.
369 + // Add some extra padding of unmapped lines so that any lines that we add line up.
370 + for (
371 + let extraLines = sourceLineCount - lastMappedLine;
372 + extraLines > 0;
373 + extraLines--
374 + ) {
375 + mappings += ';';
376 + }
377 + } else {
378 + // If a file doesn't have a source map then we generate a blank source map that just
379 + // contains the original content and segments pointing to the original lines.
380 + sourceLineCount = 1;
381 + let idx = -1;
382 + while ((idx = source.indexOf('\n', idx + 1)) !== -1) {
383 + sourceLineCount++;
384 + }
385 + mappings = 'AAAA' + ';AACA'.repeat(sourceLineCount - 1);
386 + sourceMap = {
387 + version: 3,
388 + sources: [url],
389 + sourcesContent: [source],
390 + mappings: mappings,
391 + sourceRoot: '',
392 + };
393 + lastSourceIndex = 0;
394 + lastOriginalLine = sourceLineCount;
395 + lastOriginalColumn = 0;
396 + lastNameIndex = -1;
397 + lastMappedLine = sourceLineCount;
398 +
399 + for (let i = 0; i < exportedEntries.length; i++) {
400 + // Point each entry to original location.
401 + const entry = exportedEntries[i];
402 + entry.originalSource = 0;
403 + entry.originalLine = entry.loc.start.line;
404 + // We use column zero since we do the short-hand line-only source maps above.
405 + entry.originalColumn = 0; // entry.loc.start.column;
406 + }
407 + }
408 +
409 + newSrc += '\n\n;';
410 + newSrc +=
411 + 'import {registerServerReference} from "react-server-dom-webpack/server";\n';
412 + if (mappings) {
413 + mappings += ';;';
414 + }
415 +
416 + const createMapping = createMappingsSerializer();
417 +
418 + // Create an empty mapping pointing to where we last left off to reset the counters.
419 + let generatedLine = 1;
420 + createMapping(
421 + generatedLine,
422 + 0,
423 + lastSourceIndex,
424 + lastOriginalLine,
425 + lastOriginalColumn,
426 + lastNameIndex,
427 + );
428 + for (let i = 0; i < exportedEntries.length; i++) {
429 + const entry = exportedEntries[i];
430 + generatedLine++;
431 + if (entry.type !== 'function') {
432 + // We first check if the export is a function and if so annotate it.
433 + newSrc += 'if (typeof ' + entry.localName + ' === "function") ';
434 + }
435 + newSrc += 'registerServerReference(' + entry.localName + ',';
436 + newSrc += JSON.stringify(url) + ',';
437 + newSrc += JSON.stringify(entry.exportedName) + ');\n';
438 +
439 + mappings += createMapping(
440 + generatedLine,
441 + 0,
442 + entry.originalSource,
443 + entry.originalLine,
444 + entry.originalColumn,
445 + entry.nameIndex,
446 + );
447 + }
448 + }
449 +
450 + if (sourceMap) {
451 + // Override with an new mappings and serialize an inline source map.
452 + sourceMap.mappings = mappings;
453 + newSrc +=
454 + '//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
455 + Buffer.from(JSON.stringify(sourceMap)).toString('base64');
456 + }
457 +
458 + return newSrc;
459 +}
460 +
461 +function addExportNames(names: Array<string>, node: any) {
462 + switch (node.type) {
463 + case 'Identifier':
464 + names.push(node.name);
465 + return;
466 + case 'ObjectPattern':
467 + for (let i = 0; i < node.properties.length; i++)
468 + addExportNames(names, node.properties[i]);
469 + return;
470 + case 'ArrayPattern':
471 + for (let i = 0; i < node.elements.length; i++) {
472 + const element = node.elements[i];
473 + if (element) addExportNames(names, element);
474 + }
475 + return;
476 + case 'Property':
477 + addExportNames(names, node.value);
478 + return;
479 + case 'AssignmentPattern':
480 + addExportNames(names, node.left);
481 + return;
482 + case 'RestElement':
483 + addExportNames(names, node.argument);
484 + return;
485 + case 'ParenthesizedExpression':
486 + addExportNames(names, node.expression);
487 + return;
488 + }
489 +}
490 +
491 +function resolveClientImport(
492 + specifier: string,
493 + parentURL: string,
494 +): {url: string} | Promise<{url: string}> {
495 + // Resolve an import specifier as if it was loaded by the client. This doesn't use
496 + // the overrides that this loader does but instead reverts to the default.
497 + // This resolution algorithm will not necessarily have the same configuration
498 + // as the actual client loader. It should mostly work and if it doesn't you can
499 + // always convert to explicit exported names instead.
500 + const conditions = ['node', 'import'];
501 + if (stashedResolve === null) {
502 + throw new Error(
503 + 'Expected resolve to have been called before transformSource',
504 + );
505 + }
506 + return stashedResolve(specifier, {conditions, parentURL}, stashedResolve);
507 +}
508 +
509 +async function parseExportNamesInto(
510 + body: any,
511 + names: Array<string>,
512 + parentURL: string,
513 + loader: LoadFunction,
514 +): Promise<void> {
515 + for (let i = 0; i < body.length; i++) {
516 + const node = body[i];
517 + switch (node.type) {
518 + case 'ExportAllDeclaration':
519 + if (node.exported) {
520 + addExportNames(names, node.exported);
521 + continue;
522 + } else {
523 + const {url} = await resolveClientImport(node.source.value, parentURL);
524 + const {source} = await loader(
525 + url,
526 + {format: 'module', conditions: [], importAssertions: {}},
527 + loader,
528 + );
529 + if (typeof source !== 'string') {
530 + throw new Error('Expected the transformed source to be a string.');
531 + }
532 + let childBody;
533 + try {
534 + childBody = acorn.parse(source, {
535 + ecmaVersion: '2024',
536 + sourceType: 'module',
537 + }).body;
538 + } catch (x) {
539 + // eslint-disable-next-line react-internal/no-production-logging
540 + console.error('Error parsing %s %s', url, x.message);
541 + continue;
542 + }
543 + await parseExportNamesInto(childBody, names, url, loader);
544 + continue;
545 + }
546 + case 'ExportDefaultDeclaration':
547 + names.push('default');
548 + continue;
549 + case 'ExportNamedDeclaration':
550 + if (node.declaration) {
551 + if (node.declaration.type === 'VariableDeclaration') {
552 + const declarations = node.declaration.declarations;
553 + for (let j = 0; j < declarations.length; j++) {
554 + addExportNames(names, declarations[j].id);
555 + }
556 + } else {
557 + addExportNames(names, node.declaration.id);
558 + }
559 + }
560 + if (node.specifiers) {
561 + const specifiers = node.specifiers;
562 + for (let j = 0; j < specifiers.length; j++) {
563 + addExportNames(names, specifiers[j].exported);
564 + }
565 + }
566 + continue;
567 + }
568 + }
569 +}
570 +
571 +async function transformClientModule(
572 + program: any,
573 + url: string,
574 + sourceMap: any,
575 + loader: LoadFunction,
576 +): Promise<string> {
577 + const body = program.body;
578 +
579 + const names: Array<string> = [];
580 +
581 + await parseExportNamesInto(body, names, url, loader);
582 +
583 + if (names.length === 0) {
584 + return '';
585 + }
586 +
587 + let newSrc =
588 + 'import {registerClientReference} from "react-server-dom-webpack/server";\n';
589 + for (let i = 0; i < names.length; i++) {
590 + const name = names[i];
591 + if (name === 'default') {
592 + newSrc += 'export default ';
593 + newSrc += 'registerClientReference(function() {';
594 + newSrc +=
595 + 'throw new Error(' +
596 + JSON.stringify(
597 + `Attempted to call the default export of ${url} from the server ` +
598 + `but it's on the client. It's not possible to invoke a client function from ` +
599 + `the server, it can only be rendered as a Component or passed to props of a ` +
600 + `Client Component.`,
601 + ) +
602 + ');';
603 + } else {
604 + newSrc += 'export const ' + name + ' = ';
605 + newSrc += 'registerClientReference(function() {';
606 + newSrc +=
607 + 'throw new Error(' +
608 + JSON.stringify(
609 + `Attempted to call ${name}() from the server but ${name} is on the client. ` +
610 + `It's not possible to invoke a client function from the server, it can ` +
611 + `only be rendered as a Component or passed to props of a Client Component.`,
612 + ) +
613 + ');';
614 + }
615 + newSrc += '},';
616 + newSrc += JSON.stringify(url) + ',';
617 + newSrc += JSON.stringify(name) + ');\n';
618 + }
619 +
620 + // TODO: Generate source maps for Client Reference functions so they can point to their
621 + // original locations.
622 + return newSrc;
623 +}
624 +
625 +async function loadClientImport(
626 + url: string,
627 + defaultTransformSource: TransformSourceFunction,
628 +): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
629 + if (stashedGetSource === null) {
630 + throw new Error(
631 + 'Expected getSource to have been called before transformSource',
632 + );
633 + }
634 + // TODO: Validate that this is another module by calling getFormat.
635 + const {source} = await stashedGetSource(
636 + url,
637 + {format: 'module'},
638 + stashedGetSource,
639 + );
640 + const result = await defaultTransformSource(
641 + source,
642 + {format: 'module', url},
643 + defaultTransformSource,
644 + );
645 + return {format: 'module', source: result.source};
646 +}
647 +
648 +async function transformModuleIfNeeded(
649 + source: string,
650 + url: string,
651 + loader: LoadFunction,
652 +): Promise<string> {
653 + // Do a quick check for the exact string. If it doesn't exist, don't
654 + // bother parsing.
655 + if (
656 + source.indexOf('use client') === -1 &&
657 + source.indexOf('use server') === -1
658 + ) {
659 + return source;
660 + }
661 +
662 + let sourceMappingURL = null;
663 + let sourceMappingStart = 0;
664 + let sourceMappingEnd = 0;
665 + let sourceMappingLines = 0;
666 +
667 + let program;
668 + try {
669 + program = acorn.parse(source, {
670 + ecmaVersion: '2024',
671 + sourceType: 'module',
672 + locations: true,
673 + onComment(
674 + block: boolean,
675 + text: string,
676 + start: number,
677 + end: number,
678 + startLoc: {line: number, column: number},
679 + endLoc: {line: number, column: number},
680 + ) {
681 + if (
682 + text.startsWith('# sourceMappingURL=') ||
683 + text.startsWith('@ sourceMappingURL=')
684 + ) {
685 + sourceMappingURL = text.slice(19);
686 + sourceMappingStart = start;
687 + sourceMappingEnd = end;
688 + sourceMappingLines = endLoc.line - startLoc.line;
689 + }
690 + },
691 + });
692 + } catch (x) {
693 + // eslint-disable-next-line react-internal/no-production-logging
694 + console.error('Error parsing %s %s', url, x.message);
695 + return source;
696 + }
697 +
698 + let useClient = false;
699 + let useServer = false;
700 +
701 + const body = program.body;
702 + for (let i = 0; i < body.length; i++) {
703 + const node = body[i];
704 + if (node.type !== 'ExpressionStatement' || !node.directive) {
705 + break;
706 + }
707 + if (node.directive === 'use client') {
708 + useClient = true;
709 + }
710 + if (node.directive === 'use server') {
711 + useServer = true;
712 + }
713 + }
714 +
715 + if (!useClient && !useServer) {
716 + return source;
717 + }
718 +
719 + if (useClient && useServer) {
720 + throw new Error(
721 + 'Cannot have both "use client" and "use server" directives in the same file.',
722 + );
723 + }
724 +
725 + let sourceMap = null;
726 + if (sourceMappingURL) {
727 + const sourceMapResult = await loader(
728 + sourceMappingURL,
729 + // $FlowFixMe
730 + {
731 + format: 'json',
732 + conditions: [],
733 + importAssertions: {type: 'json'},
734 + importAttributes: {type: 'json'},
735 + },
736 + loader,
737 + );
738 + const sourceMapString =
739 + typeof sourceMapResult.source === 'string'
740 + ? sourceMapResult.source
741 + : // $FlowFixMe
742 + sourceMapResult.source.toString('utf8');
743 + sourceMap = JSON.parse(sourceMapString);
744 +
745 + // Strip the source mapping comment. We'll re-add it below if needed.
746 + source =
747 + source.slice(0, sourceMappingStart) +
748 + '\n'.repeat(sourceMappingLines) +
749 + source.slice(sourceMappingEnd);
750 + }
751 +
752 + if (useClient) {
753 + return transformClientModule(program, url, sourceMap, loader);
754 + }
755 +
756 + return transformServerModule(source, program, url, sourceMap, loader);
757 +}
758 +
759 +export async function transformSource(
760 + source: Source,
761 + context: TransformSourceContext,
762 + defaultTransformSource: TransformSourceFunction,
763 +): Promise<{source: Source}> {
764 + const transformed = await defaultTransformSource(
765 + source,
766 + context,
767 + defaultTransformSource,
768 + );
769 + if (context.format === 'module') {
770 + const transformedSource = transformed.source;
771 + if (typeof transformedSource !== 'string') {
772 + throw new Error('Expected source to have been transformed to a string.');
773 + }
774 + const newSrc = await transformModuleIfNeeded(
775 + transformedSource,
776 + context.url,
777 + (url: string, ctx: LoadContext, defaultLoad: LoadFunction) => {
778 + return loadClientImport(url, defaultTransformSource);
779 + },
780 + );
781 + return {source: newSrc};
782 + }
783 + return transformed;
784 +}
785 +
786 +export async function load(
787 + url: string,
788 + context: LoadContext,
789 + defaultLoad: LoadFunction,
790 +): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
791 + const result = await defaultLoad(url, context, defaultLoad);
792 + if (result.format === 'module') {
793 + if (typeof result.source !== 'string') {
794 + throw new Error('Expected source to have been loaded into a string.');
795 + }
796 + const newSrc = await transformModuleIfNeeded(
797 + result.source,
798 + url,
799 + defaultLoad,
800 + );
801 + return {format: 'module', source: newSrc};
802 + }
803 + return result;
804 +}
packages/react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister.js new
+109
@@ -0,0 +1,109 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +const acorn = require('acorn-loose');
11 +
12 +const url = require('url');
13 +
14 +const Module = require('module');
15 +
16 +module.exports = function register() {
17 + const Server: any = require('react-server-dom-unbundled/server');
18 + const registerServerReference = Server.registerServerReference;
19 + const createClientModuleProxy = Server.createClientModuleProxy;
20 +
21 + // $FlowFixMe[prop-missing] found when upgrading Flow
22 + const originalCompile = Module.prototype._compile;
23 +
24 + // $FlowFixMe[prop-missing] found when upgrading Flow
25 + Module.prototype._compile = function (
26 + this: any,
27 + content: string,
28 + filename: string,
29 + ): void {
30 + // Do a quick check for the exact string. If it doesn't exist, don't
31 + // bother parsing.
32 + if (
33 + content.indexOf('use client') === -1 &&
34 + content.indexOf('use server') === -1
35 + ) {
36 + return originalCompile.apply(this, arguments);
37 + }
38 +
39 + let body;
40 + try {
41 + body = acorn.parse(content, {
42 + ecmaVersion: '2024',
43 + sourceType: 'source',
44 + }).body;
45 + } catch (x) {
46 + console['error']('Error parsing %s %s', url, x.message);
47 + return originalCompile.apply(this, arguments);
48 + }
49 +
50 + let useClient = false;
51 + let useServer = false;
52 + for (let i = 0; i < body.length; i++) {
53 + const node = body[i];
54 + if (node.type !== 'ExpressionStatement' || !node.directive) {
55 + break;
56 + }
57 + if (node.directive === 'use client') {
58 + useClient = true;
59 + }
60 + if (node.directive === 'use server') {
61 + useServer = true;
62 + }
63 + }
64 +
65 + if (!useClient && !useServer) {
66 + return originalCompile.apply(this, arguments);
67 + }
68 +
69 + if (useClient && useServer) {
70 + throw new Error(
71 + 'Cannot have both "use client" and "use server" directives in the same file.',
72 + );
73 + }
74 +
75 + if (useClient) {
76 + const moduleId: string = (url.pathToFileURL(filename).href: any);
77 + this.exports = createClientModuleProxy(moduleId);
78 + }
79 +
80 + if (useServer) {
81 + originalCompile.apply(this, arguments);
82 +
83 + const moduleId: string = (url.pathToFileURL(filename).href: any);
84 +
85 + const exports = this.exports;
86 +
87 + // This module is imported server to server, but opts in to exposing functions by
88 + // reference. If there are any functions in the export.
89 + if (typeof exports === 'function') {
90 + // The module exports a function directly,
91 + registerServerReference(
92 + (exports: any),
93 + moduleId,
94 + // Represents the whole Module object instead of a particular import.
95 + null,
96 + );
97 + } else {
98 + const keys = Object.keys(exports);
99 + for (let i = 0; i < keys.length; i++) {
100 + const key = keys[i];
101 + const value = exports[keys[i]];
102 + if (typeof value === 'function') {
103 + registerServerReference((value: any), moduleId, key);
104 + }
105 + }
106 + }
107 + }
108 + };
109 +};
packages/react-server-dom-unbundled/src/ReactFlightUnbundledReferences.js new
+352
@@ -0,0 +1,352 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
11 +
12 +export type ServerReference<T: Function> = T & {
13 + $$typeof: symbol,
14 + $$id: string,
15 + $$bound: null | Array<ReactClientValue>,
16 + $$location?: Error,
17 +};
18 +
19 +// eslint-disable-next-line no-unused-vars
20 +export type ClientReference<T> = {
21 + $$typeof: symbol,
22 + $$id: string,
23 + $$async: boolean,
24 +};
25 +
26 +const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
27 +const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
28 +
29 +export function isClientReference(reference: Object): boolean {
30 + return reference.$$typeof === CLIENT_REFERENCE_TAG;
31 +}
32 +
33 +export function isServerReference(reference: Object): boolean {
34 + return reference.$$typeof === SERVER_REFERENCE_TAG;
35 +}
36 +
37 +export function registerClientReference<T>(
38 + proxyImplementation: any,
39 + id: string,
40 + exportName: string,
41 +): ClientReference<T> {
42 + return registerClientReferenceImpl(
43 + proxyImplementation,
44 + id + '#' + exportName,
45 + false,
46 + );
47 +}
48 +
49 +function registerClientReferenceImpl<T>(
50 + proxyImplementation: any,
51 + id: string,
52 + async: boolean,
53 +): ClientReference<T> {
54 + return Object.defineProperties(proxyImplementation, {
55 + $$typeof: {value: CLIENT_REFERENCE_TAG},
56 + $$id: {value: id},
57 + $$async: {value: async},
58 + });
59 +}
60 +
61 +// $FlowFixMe[method-unbinding]
62 +const FunctionBind = Function.prototype.bind;
63 +// $FlowFixMe[method-unbinding]
64 +const ArraySlice = Array.prototype.slice;
65 +function bind(this: ServerReference<any>): any {
66 + // $FlowFixMe[incompatible-call]
67 + const newFn = FunctionBind.apply(this, arguments);
68 + if (this.$$typeof === SERVER_REFERENCE_TAG) {
69 + if (__DEV__) {
70 + const thisBind = arguments[0];
71 + if (thisBind != null) {
72 + console.error(
73 + 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
74 + );
75 + }
76 + }
77 + const args = ArraySlice.call(arguments, 1);
78 + const $$typeof = {value: SERVER_REFERENCE_TAG};
79 + const $$id = {value: this.$$id};
80 + const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args};
81 + return Object.defineProperties(
82 + (newFn: any),
83 + (__DEV__
84 + ? {
85 + $$typeof,
86 + $$id,
87 + $$bound,
88 + $$location: {
89 + value: this.$$location,
90 + configurable: true,
91 + },
92 + bind: {value: bind, configurable: true},
93 + }
94 + : {
95 + $$typeof,
96 + $$id,
97 + $$bound,
98 + bind: {value: bind, configurable: true},
99 + }) as PropertyDescriptorMap,
100 + );
101 + }
102 + return newFn;
103 +}
104 +
105 +export function registerServerReference<T: Function>(
106 + reference: T,
107 + id: string,
108 + exportName: null | string,
109 +): ServerReference<T> {
110 + const $$typeof = {value: SERVER_REFERENCE_TAG};
111 + const $$id = {
112 + value: exportName === null ? id : id + '#' + exportName,
113 + configurable: true,
114 + };
115 + const $$bound = {value: null, configurable: true};
116 + return Object.defineProperties(
117 + (reference: any),
118 + __DEV__
119 + ? ({
120 + $$typeof,
121 + $$id,
122 + $$bound,
123 + $$location: {
124 + value: Error('react-stack-top-frame'),
125 + configurable: true,
126 + },
127 + bind: {value: bind, configurable: true},
128 + } as PropertyDescriptorMap)
129 + : ({
130 + $$typeof,
131 + $$id,
132 + $$bound,
133 + bind: {value: bind, configurable: true},
134 + } as PropertyDescriptorMap),
135 + );
136 +}
137 +
138 +const PROMISE_PROTOTYPE = Promise.prototype;
139 +
140 +const deepProxyHandlers: Proxy$traps<mixed> = {
141 + get: function (
142 + target: Function,
143 + name: string | symbol,
144 + receiver: Proxy<Function>,
145 + ) {
146 + switch (name) {
147 + // These names are read by the Flight runtime if you end up using the exports object.
148 + case '$$typeof':
149 + // These names are a little too common. We should probably have a way to
150 + // have the Flight runtime extract the inner target instead.
151 + return target.$$typeof;
152 + case '$$id':
153 + return target.$$id;
154 + case '$$async':
155 + return target.$$async;
156 + case 'name':
157 + return target.name;
158 + case 'displayName':
159 + return undefined;
160 + // We need to special case this because createElement reads it if we pass this
161 + // reference.
162 + case 'defaultProps':
163 + return undefined;
164 + // React looks for debugInfo on thenables.
165 + case '_debugInfo':
166 + return undefined;
167 + // Avoid this attempting to be serialized.
168 + case 'toJSON':
169 + return undefined;
170 + case Symbol.toPrimitive:
171 + // $FlowFixMe[prop-missing]
172 + return Object.prototype[Symbol.toPrimitive];
173 + case Symbol.toStringTag:
174 + // $FlowFixMe[prop-missing]
175 + return Object.prototype[Symbol.toStringTag];
176 + case 'Provider':
177 + throw new Error(
178 + `Cannot render a Client Context Provider on the Server. ` +
179 + `Instead, you can export a Client Component wrapper ` +
180 + `that itself renders a Client Context Provider.`,
181 + );
182 + case 'then':
183 + throw new Error(
184 + `Cannot await or return from a thenable. ` +
185 + `You cannot await a client module from a server component.`,
186 + );
187 + }
188 + // eslint-disable-next-line react-internal/safe-string-coercion
189 + const expression = String(target.name) + '.' + String(name);
190 + throw new Error(
191 + `Cannot access ${expression} on the server. ` +
192 + 'You cannot dot into a client module from a server component. ' +
193 + 'You can only pass the imported name through.',
194 + );
195 + },
196 + set: function () {
197 + throw new Error('Cannot assign to a client module from a server module.');
198 + },
199 +};
200 +
201 +function getReference(target: Function, name: string | symbol): $FlowFixMe {
202 + switch (name) {
203 + // These names are read by the Flight runtime if you end up using the exports object.
204 + case '$$typeof':
205 + return target.$$typeof;
206 + case '$$id':
207 + return target.$$id;
208 + case '$$async':
209 + return target.$$async;
210 + case 'name':
211 + return target.name;
212 + // We need to special case this because createElement reads it if we pass this
213 + // reference.
214 + case 'defaultProps':
215 + return undefined;
216 + // React looks for debugInfo on thenables.
217 + case '_debugInfo':
218 + return undefined;
219 + // Avoid this attempting to be serialized.
220 + case 'toJSON':
221 + return undefined;
222 + case Symbol.toPrimitive:
223 + // $FlowFixMe[prop-missing]
224 + return Object.prototype[Symbol.toPrimitive];
225 + case Symbol.toStringTag:
226 + // $FlowFixMe[prop-missing]
227 + return Object.prototype[Symbol.toStringTag];
228 + case '__esModule':
229 + // Something is conditionally checking which export to use. We'll pretend to be
230 + // an ESM compat module but then we'll check again on the client.
231 + const moduleId = target.$$id;
232 + target.default = registerClientReferenceImpl(
233 + (function () {
234 + throw new Error(
235 + `Attempted to call the default export of ${moduleId} from the server ` +
236 + `but it's on the client. It's not possible to invoke a client function from ` +
237 + `the server, it can only be rendered as a Component or passed to props of a ` +
238 + `Client Component.`,
239 + );
240 + }: any),
241 + target.$$id + '#',
242 + target.$$async,
243 + );
244 + return true;
245 + case 'then':
246 + if (target.then) {
247 + // Use a cached value
248 + return target.then;
249 + }
250 + if (!target.$$async) {
251 + // If this module is expected to return a Promise (such as an AsyncModule) then
252 + // we should resolve that with a client reference that unwraps the Promise on
253 + // the client.
254 +
255 + const clientReference: ClientReference<any> =
256 + registerClientReferenceImpl(({}: any), target.$$id, true);
257 + const proxy = new Proxy(clientReference, proxyHandlers);
258 +
259 + // Treat this as a resolved Promise for React's use()
260 + target.status = 'fulfilled';
261 + target.value = proxy;
262 +
263 + const then = (target.then = registerClientReferenceImpl(
264 + (function then(resolve, reject: any) {
265 + // Expose to React.
266 + return Promise.resolve(resolve(proxy));
267 + }: any),
268 + // If this is not used as a Promise but is treated as a reference to a `.then`
269 + // export then we should treat it as a reference to that name.
270 + target.$$id + '#then',
271 + false,
272 + ));
273 + return then;
274 + } else {
275 + // Since typeof .then === 'function' is a feature test we'd continue recursing
276 + // indefinitely if we return a function. Instead, we return an object reference
277 + // if we check further.
278 + return undefined;
279 + }
280 + }
281 + if (typeof name === 'symbol') {
282 + throw new Error(
283 + 'Cannot read Symbol exports. Only named exports are supported on a client module ' +
284 + 'imported on the server.',
285 + );
286 + }
287 + let cachedReference = target[name];
288 + if (!cachedReference) {
289 + const reference: ClientReference<any> = registerClientReferenceImpl(
290 + (function () {
291 + throw new Error(
292 + // eslint-disable-next-line react-internal/safe-string-coercion
293 + `Attempted to call ${String(name)}() from the server but ${String(
294 + name,
295 + )} is on the client. ` +
296 + `It's not possible to invoke a client function from the server, it can ` +
297 + `only be rendered as a Component or passed to props of a Client Component.`,
298 + );
299 + }: any),
300 + target.$$id + '#' + name,
301 + target.$$async,
302 + );
303 + Object.defineProperty((reference: any), 'name', {value: name});
304 + cachedReference = target[name] = new Proxy(reference, deepProxyHandlers);
305 + }
306 + return cachedReference;
307 +}
308 +
309 +const proxyHandlers = {
310 + get: function (
311 + target: Function,
312 + name: string | symbol,
313 + receiver: Proxy<Function>,
314 + ): $FlowFixMe {
315 + return getReference(target, name);
316 + },
317 + getOwnPropertyDescriptor: function (
318 + target: Function,
319 + name: string | symbol,
320 + ): $FlowFixMe {
321 + let descriptor = Object.getOwnPropertyDescriptor(target, name);
322 + if (!descriptor) {
323 + descriptor = {
324 + value: getReference(target, name),
325 + writable: false,
326 + configurable: false,
327 + enumerable: false,
328 + };
329 + Object.defineProperty(target, name, descriptor);
330 + }
331 + return descriptor;
332 + },
333 + getPrototypeOf(target: Function): Object {
334 + // Pretend to be a Promise in case anyone asks.
335 + return PROMISE_PROTOTYPE;
336 + },
337 + set: function (): empty {
338 + throw new Error('Cannot assign to a client module from a server module.');
339 + },
340 +};
341 +
342 +export function createClientModuleProxy<T>(
343 + moduleId: string,
344 +): ClientReference<T> {
345 + const clientReference: ClientReference<T> = registerClientReferenceImpl(
346 + ({}: any),
347 + // Represents the whole Module object instead of a particular import.
348 + moduleId,
349 + false,
350 + );
351 + return new Proxy(clientReference, proxyHandlers);
352 +}
packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js renamed
packages/react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer.js new
+32
@@ -0,0 +1,32 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import {preinitScriptForSSR} from 'react-client/src/ReactFlightClientConfig';
11 +
12 +export type ModuleLoading = null | {
13 + prefix: string,
14 + crossOrigin?: 'use-credentials' | '',
15 +};
16 +
17 +export function prepareDestinationWithChunks(
18 + moduleLoading: ModuleLoading,
19 + // Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
20 + chunks: Array<string>,
21 + nonce: ?string,
22 +) {
23 + if (moduleLoading !== null) {
24 + for (let i = 1; i < chunks.length; i += 2) {
25 + preinitScriptForSSR(
26 + moduleLoading.prefix + chunks[i],
27 + nonce,
28 + moduleLoading.crossOrigin,
29 + );
30 + }
31 + }
32 +}
packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js new
+255
@@ -0,0 +1,255 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
11 +
12 +import type {
13 + DebugChannel,
14 + FindSourceMapURLCallback,
15 + Response as FlightResponse,
16 +} from 'react-client/src/ReactFlightClient';
17 +
18 +import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
19 +
20 +import type {
21 + ServerConsumerModuleMap,
22 + ModuleLoading,
23 + ServerManifest,
24 +} from 'react-client/src/ReactFlightClientConfig';
25 +
26 +type ServerConsumerManifest = {
27 + moduleMap: ServerConsumerModuleMap,
28 + moduleLoading: ModuleLoading,
29 + serverModuleMap: null | ServerManifest,
30 +};
31 +
32 +import {
33 + createResponse,
34 + createStreamState,
35 + getRoot,
36 + reportGlobalError,
37 + processBinaryChunk,
38 + close,
39 +} from 'react-client/src/ReactFlightClient';
40 +
41 +import {
42 + processReply,
43 + createServerReference as createServerReferenceImpl,
44 +} from 'react-client/src/ReactFlightReplyClient';
45 +
46 +export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
47 +
48 +import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
49 +
50 +export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
51 +
52 +export type {TemporaryReferenceSet};
53 +
54 +function noServerCall() {
55 + throw new Error(
56 + 'Server Functions cannot be called during initial render. ' +
57 + 'This would create a fetch waterfall. Try to use a Server Component ' +
58 + 'to pass data to Client Components instead.',
59 + );
60 +}
61 +
62 +export function createServerReference<A: Iterable<any>, T>(
63 + id: any,
64 + callServer: any,
65 +): (...A) => Promise<T> {
66 + return createServerReferenceImpl(id, noServerCall);
67 +}
68 +
69 +type EncodeFormActionCallback = <A>(
70 + id: any,
71 + args: Promise<A>,
72 +) => ReactCustomFormAction;
73 +
74 +export type Options = {
75 + serverConsumerManifest: ServerConsumerManifest,
76 + nonce?: string,
77 + encodeFormAction?: EncodeFormActionCallback,
78 + temporaryReferences?: TemporaryReferenceSet,
79 + findSourceMapURL?: FindSourceMapURLCallback,
80 + replayConsoleLogs?: boolean,
81 + environmentName?: string,
82 + startTime?: number,
83 + endTime?: number,
84 + // For the Edge client we only support a single-direction debug channel.
85 + debugChannel?: {readable?: ReadableStream, ...},
86 +};
87 +
88 +function createResponseFromOptions(options: Options) {
89 + const debugChannel: void | DebugChannel =
90 + __DEV__ && options && options.debugChannel !== undefined
91 + ? {
92 + hasReadable: options.debugChannel.readable !== undefined,
93 + callback: null,
94 + }
95 + : undefined;
96 +
97 + return createResponse(
98 + options.serverConsumerManifest.moduleMap,
99 + options.serverConsumerManifest.serverModuleMap,
100 + options.serverConsumerManifest.moduleLoading,
101 + noServerCall,
102 + options.encodeFormAction,
103 + typeof options.nonce === 'string' ? options.nonce : undefined,
104 + options && options.temporaryReferences
105 + ? options.temporaryReferences
106 + : undefined,
107 + __DEV__ && options && options.findSourceMapURL
108 + ? options.findSourceMapURL
109 + : undefined,
110 + __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
111 + __DEV__ && options && options.environmentName
112 + ? options.environmentName
113 + : undefined,
114 + __DEV__ && options && options.startTime != null
115 + ? options.startTime
116 + : undefined,
117 + __DEV__ && options && options.endTime != null ? options.endTime : undefined,
118 + debugChannel,
119 + );
120 +}
121 +
122 +function startReadingFromStream(
123 + response: FlightResponse,
124 + stream: ReadableStream,
125 + onDone: () => void,
126 + debugValue: mixed,
127 +): void {
128 + const streamState = createStreamState(response, debugValue);
129 + const reader = stream.getReader();
130 + function progress({
131 + done,
132 + value,
133 + }: {
134 + done: boolean,
135 + value: ?any,
136 + ...
137 + }): void | Promise<void> {
138 + if (done) {
139 + return onDone();
140 + }
141 + const buffer: Uint8Array = (value: any);
142 + processBinaryChunk(response, streamState, buffer);
143 + return reader.read().then(progress).catch(error);
144 + }
145 + function error(e: any) {
146 + reportGlobalError(response, e);
147 + }
148 + reader.read().then(progress).catch(error);
149 +}
150 +
151 +function createFromReadableStream<T>(
152 + stream: ReadableStream,
153 + options: Options,
154 +): Thenable<T> {
155 + const response: FlightResponse = createResponseFromOptions(options);
156 +
157 + if (
158 + __DEV__ &&
159 + options &&
160 + options.debugChannel &&
161 + options.debugChannel.readable
162 + ) {
163 + let streamDoneCount = 0;
164 + const handleDone = () => {
165 + if (++streamDoneCount === 2) {
166 + close(response);
167 + }
168 + };
169 + startReadingFromStream(response, options.debugChannel.readable, handleDone);
170 + startReadingFromStream(response, stream, handleDone, stream);
171 + } else {
172 + startReadingFromStream(
173 + response,
174 + stream,
175 + close.bind(null, response),
176 + stream,
177 + );
178 + }
179 +
180 + return getRoot(response);
181 +}
182 +
183 +function createFromFetch<T>(
184 + promiseForResponse: Promise<Response>,
185 + options: Options,
186 +): Thenable<T> {
187 + const response: FlightResponse = createResponseFromOptions(options);
188 + promiseForResponse.then(
189 + function (r) {
190 + if (
191 + __DEV__ &&
192 + options &&
193 + options.debugChannel &&
194 + options.debugChannel.readable
195 + ) {
196 + let streamDoneCount = 0;
197 + const handleDone = () => {
198 + if (++streamDoneCount === 2) {
199 + close(response);
200 + }
201 + };
202 + startReadingFromStream(
203 + response,
204 + options.debugChannel.readable,
205 + handleDone,
206 + );
207 + startReadingFromStream(response, (r.body: any), handleDone, r);
208 + } else {
209 + startReadingFromStream(
210 + response,
211 + (r.body: any),
212 + close.bind(null, response),
213 + r,
214 + );
215 + }
216 + },
217 + function (e) {
218 + reportGlobalError(response, e);
219 + },
220 + );
221 + return getRoot(response);
222 +}
223 +
224 +function encodeReply(
225 + value: ReactServerValue,
226 + options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
227 +): Promise<
228 + string | URLSearchParams | FormData,
229 +> /* We don't use URLSearchParams yet but maybe */ {
230 + return new Promise((resolve, reject) => {
231 + const abort = processReply(
232 + value,
233 + '',
234 + options && options.temporaryReferences
235 + ? options.temporaryReferences
236 + : undefined,
237 + resolve,
238 + reject,
239 + );
240 + if (options && options.signal) {
241 + const signal = options.signal;
242 + if (signal.aborted) {
243 + abort((signal: any).reason);
244 + } else {
245 + const listener = () => {
246 + abort((signal: any).reason);
247 + signal.removeEventListener('abort', listener);
248 + };
249 + signal.addEventListener('abort', listener);
250 + }
251 + }
252 + });
253 +}
254 +
255 +export {createFromFetch, createFromReadableStream, encodeReply};
packages/react-server-dom-unbundled/src/client/ReactFlightDOMClientNode.js new
+139
@@ -0,0 +1,139 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
11 +
12 +import type {
13 + DebugChannel,
14 + FindSourceMapURLCallback,
15 + Response,
16 +} from 'react-client/src/ReactFlightClient';
17 +
18 +import type {
19 + ServerConsumerModuleMap,
20 + ModuleLoading,
21 + ServerManifest,
22 +} from 'react-client/src/ReactFlightClientConfig';
23 +
24 +type ServerConsumerManifest = {
25 + moduleMap: ServerConsumerModuleMap,
26 + moduleLoading: ModuleLoading,
27 + serverModuleMap: null | ServerManifest,
28 +};
29 +
30 +import type {Readable} from 'stream';
31 +
32 +import {
33 + createResponse,
34 + createStreamState,
35 + getRoot,
36 + reportGlobalError,
37 + processStringChunk,
38 + processBinaryChunk,
39 + close,
40 +} from 'react-client/src/ReactFlightClient';
41 +
42 +export * from './ReactFlightDOMClientEdge';
43 +
44 +function noServerCall() {
45 + throw new Error(
46 + 'Server Functions cannot be called during initial render. ' +
47 + 'This would create a fetch waterfall. Try to use a Server Component ' +
48 + 'to pass data to Client Components instead.',
49 + );
50 +}
51 +
52 +type EncodeFormActionCallback = <A>(
53 + id: any,
54 + args: Promise<A>,
55 +) => ReactCustomFormAction;
56 +
57 +export type Options = {
58 + nonce?: string,
59 + encodeFormAction?: EncodeFormActionCallback,
60 + findSourceMapURL?: FindSourceMapURLCallback,
61 + replayConsoleLogs?: boolean,
62 + environmentName?: string,
63 + startTime?: number,
64 + endTime?: number,
65 + // For the Node.js client we only support a single-direction debug channel.
66 + debugChannel?: Readable,
67 +};
68 +
69 +function startReadingFromStream(
70 + response: Response,
71 + stream: Readable,
72 + onEnd: () => void,
73 +): void {
74 + const streamState = createStreamState(response, stream);
75 +
76 + stream.on('data', chunk => {
77 + if (typeof chunk === 'string') {
78 + processStringChunk(response, streamState, chunk);
79 + } else {
80 + processBinaryChunk(response, streamState, chunk);
81 + }
82 + });
83 +
84 + stream.on('error', error => {
85 + reportGlobalError(response, error);
86 + });
87 +
88 + stream.on('end', onEnd);
89 +}
90 +
91 +function createFromNodeStream<T>(
92 + stream: Readable,
93 + serverConsumerManifest: ServerConsumerManifest,
94 + options?: Options,
95 +): Thenable<T> {
96 + const debugChannel: void | DebugChannel =
97 + __DEV__ && options && options.debugChannel !== undefined
98 + ? {hasReadable: true, callback: null}
99 + : undefined;
100 +
101 + const response: Response = createResponse(
102 + serverConsumerManifest.moduleMap,
103 + serverConsumerManifest.serverModuleMap,
104 + serverConsumerManifest.moduleLoading,
105 + noServerCall,
106 + options ? options.encodeFormAction : undefined,
107 + options && typeof options.nonce === 'string' ? options.nonce : undefined,
108 + undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
109 + __DEV__ && options && options.findSourceMapURL
110 + ? options.findSourceMapURL
111 + : undefined,
112 + __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
113 + __DEV__ && options && options.environmentName
114 + ? options.environmentName
115 + : undefined,
116 + __DEV__ && options && options.startTime != null
117 + ? options.startTime
118 + : undefined,
119 + __DEV__ && options && options.endTime != null ? options.endTime : undefined,
120 + debugChannel,
121 + );
122 +
123 + if (__DEV__ && options && options.debugChannel) {
124 + let streamEndedCount = 0;
125 + const handleEnd = () => {
126 + if (++streamEndedCount === 2) {
127 + close(response);
128 + }
129 + };
130 + startReadingFromStream(response, options.debugChannel, handleEnd);
131 + startReadingFromStream(response, stream, handleEnd);
132 + } else {
133 + startReadingFromStream(response, stream, close.bind(null, response));
134 + }
135 +
136 + return getRoot(response);
137 +}
138 +
139 +export {createFromNodeStream};
packages/react-server-dom-unbundled/src/client/react-flight-dom-client.node.js renamed
packages/react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js new
+695
@@ -0,0 +1,695 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {
11 + Request,
12 + ReactClientValue,
13 +} from 'react-server/src/ReactFlightServer';
14 +import type {Destination} from 'react-server/src/ReactServerStreamConfigNode';
15 +import type {ClientManifest} from './ReactFlightServerConfigUnbundledBundler';
16 +import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
17 +import type {Busboy} from 'busboy';
18 +import type {Writable} from 'stream';
19 +import type {Thenable} from 'shared/ReactTypes';
20 +
21 +import type {Duplex} from 'stream';
22 +
23 +import {Readable} from 'stream';
24 +
25 +import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
26 +
27 +import {
28 + createRequest,
29 + createPrerenderRequest,
30 + startWork,
31 + startFlowing,
32 + startFlowingDebug,
33 + stopFlowing,
34 + abort,
35 + resolveDebugMessage,
36 + closeDebugChannel,
37 +} from 'react-server/src/ReactFlightServer';
38 +
39 +import {
40 + createResponse,
41 + reportGlobalError,
42 + close,
43 + resolveField,
44 + resolveFile,
45 + resolveFileInfo,
46 + resolveFileChunk,
47 + resolveFileComplete,
48 + getRoot,
49 +} from 'react-server/src/ReactFlightReplyServer';
50 +
51 +import {
52 + decodeAction,
53 + decodeFormState,
54 +} from 'react-server/src/ReactFlightActionServer';
55 +
56 +export {
57 + registerServerReference,
58 + registerClientReference,
59 + createClientModuleProxy,
60 +} from '../ReactFlightUnbundledReferences';
61 +
62 +import {
63 + createStringDecoder,
64 + readPartialStringChunk,
65 + readFinalStringChunk,
66 +} from 'react-client/src/ReactFlightClientStreamConfigNode';
67 +
68 +import {textEncoder} from 'react-server/src/ReactServerStreamConfigNode';
69 +
70 +import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
71 +
72 +export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
73 +
74 +export type {TemporaryReferenceSet};
75 +
76 +function createDrainHandler(destination: Destination, request: Request) {
77 + return () => startFlowing(request, destination);
78 +}
79 +
80 +function createCancelHandler(request: Request, reason: string) {
81 + return () => {
82 + stopFlowing(request);
83 + abort(request, new Error(reason));
84 + };
85 +}
86 +
87 +function startReadingFromDebugChannelReadable(
88 + request: Request,
89 + stream: Readable | WebSocket,
90 +): void {
91 + const stringDecoder = createStringDecoder();
92 + let lastWasPartial = false;
93 + let stringBuffer = '';
94 + function onData(chunk: string | Uint8Array) {
95 + if (typeof chunk === 'string') {
96 + if (lastWasPartial) {
97 + stringBuffer += readFinalStringChunk(stringDecoder, new Uint8Array(0));
98 + lastWasPartial = false;
99 + }
100 + stringBuffer += chunk;
101 + } else {
102 + const buffer: Uint8Array = (chunk: any);
103 + stringBuffer += readPartialStringChunk(stringDecoder, buffer);
104 + lastWasPartial = true;
105 + }
106 + const messages = stringBuffer.split('\n');
107 + for (let i = 0; i < messages.length - 1; i++) {
108 + resolveDebugMessage(request, messages[i]);
109 + }
110 + stringBuffer = messages[messages.length - 1];
111 + }
112 + function onError(error: mixed) {
113 + abort(
114 + request,
115 + new Error('Lost connection to the Debug Channel.', {
116 + cause: error,
117 + }),
118 + );
119 + }
120 + function onClose() {
121 + closeDebugChannel(request);
122 + }
123 + if (
124 + // $FlowFixMe[method-unbinding]
125 + typeof stream.addEventListener === 'function' &&
126 + // $FlowFixMe[method-unbinding]
127 + typeof stream.binaryType === 'string'
128 + ) {
129 + const ws: WebSocket = (stream: any);
130 + ws.binaryType = 'arraybuffer';
131 + ws.addEventListener('message', event => {
132 + // $FlowFixMe
133 + onData(event.data);
134 + });
135 + ws.addEventListener('error', event => {
136 + // $FlowFixMe
137 + onError(event.error);
138 + });
139 + ws.addEventListener('close', onClose);
140 + } else {
141 + const readable: Readable = (stream: any);
142 + readable.on('data', onData);
143 + readable.on('error', onError);
144 + readable.on('end', onClose);
145 + }
146 +}
147 +
148 +type Options = {
149 + debugChannel?: Readable | Writable | Duplex | WebSocket,
150 + environmentName?: string | (() => string),
151 + filterStackFrame?: (url: string, functionName: string) => boolean,
152 + onError?: (error: mixed) => void,
153 + identifierPrefix?: string,
154 + temporaryReferences?: TemporaryReferenceSet,
155 +};
156 +
157 +type PipeableStream = {
158 + abort(reason: mixed): void,
159 + pipe<T: Writable>(destination: T): T,
160 +};
161 +
162 +function renderToPipeableStream(
163 + model: ReactClientValue,
164 + webpackMap: ClientManifest,
165 + options?: Options,
166 +): PipeableStream {
167 + const debugChannel = __DEV__ && options ? options.debugChannel : undefined;
168 + const debugChannelReadable: void | Readable | WebSocket =
169 + __DEV__ &&
170 + debugChannel !== undefined &&
171 + // $FlowFixMe[method-unbinding]
172 + (typeof debugChannel.read === 'function' ||
173 + typeof debugChannel.readyState === 'number')
174 + ? (debugChannel: any)
175 + : undefined;
176 + const debugChannelWritable: void | Writable =
177 + __DEV__ && debugChannel !== undefined
178 + ? // $FlowFixMe[method-unbinding]
179 + typeof debugChannel.write === 'function'
180 + ? (debugChannel: any)
181 + : // $FlowFixMe[method-unbinding]
182 + typeof debugChannel.send === 'function'
183 + ? createFakeWritableFromWebSocket((debugChannel: any))
184 + : undefined
185 + : undefined;
186 + const request = createRequest(
187 + model,
188 + webpackMap,
189 + options ? options.onError : undefined,
190 + options ? options.identifierPrefix : undefined,
191 + options ? options.temporaryReferences : undefined,
192 + __DEV__ && options ? options.environmentName : undefined,
193 + __DEV__ && options ? options.filterStackFrame : undefined,
194 + debugChannelReadable !== undefined,
195 + );
196 + let hasStartedFlowing = false;
197 + startWork(request);
198 + if (debugChannelWritable !== undefined) {
199 + startFlowingDebug(request, debugChannelWritable);
200 + }
201 + if (debugChannelReadable !== undefined) {
202 + startReadingFromDebugChannelReadable(request, debugChannelReadable);
203 + }
204 + return {
205 + pipe<T: Writable>(destination: T): T {
206 + if (hasStartedFlowing) {
207 + throw new Error(
208 + 'React currently only supports piping to one writable stream.',
209 + );
210 + }
211 + hasStartedFlowing = true;
212 + startFlowing(request, destination);
213 + destination.on('drain', createDrainHandler(destination, request));
214 + destination.on(
215 + 'error',
216 + createCancelHandler(
217 + request,
218 + 'The destination stream errored while writing data.',
219 + ),
220 + );
221 + // We don't close until the debug channel closes.
222 + if (!__DEV__ || debugChannelReadable === undefined) {
223 + destination.on(
224 + 'close',
225 + createCancelHandler(request, 'The destination stream closed early.'),
226 + );
227 + }
228 + return destination;
229 + },
230 + abort(reason: mixed) {
231 + abort(request, reason);
232 + },
233 + };
234 +}
235 +
236 +function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable {
237 + return ({
238 + write(chunk: string | Uint8Array) {
239 + webSocket.send((chunk: any));
240 + return true;
241 + },
242 + end() {
243 + webSocket.close();
244 + },
245 + destroy(reason) {
246 + if (typeof reason === 'object' && reason !== null) {
247 + reason = reason.message;
248 + }
249 + if (typeof reason === 'string') {
250 + webSocket.close(1011, reason);
251 + } else {
252 + webSocket.close(1011);
253 + }
254 + },
255 + }: any);
256 +}
257 +
258 +function createFakeWritableFromReadableStreamController(
259 + controller: ReadableStreamController,
260 +): Writable {
261 + // The current host config expects a Writable so we create
262 + // a fake writable for now to push into the Readable.
263 + return ({
264 + write(chunk: string | Uint8Array) {
265 + if (typeof chunk === 'string') {
266 + chunk = textEncoder.encode(chunk);
267 + }
268 + controller.enqueue(chunk);
269 + // in web streams there is no backpressure so we can always write more
270 + return true;
271 + },
272 + end() {
273 + controller.close();
274 + },
275 + destroy(error) {
276 + // $FlowFixMe[method-unbinding]
277 + if (typeof controller.error === 'function') {
278 + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
279 + controller.error(error);
280 + } else {
281 + controller.close();
282 + }
283 + },
284 + }: any);
285 +}
286 +
287 +function startReadingFromDebugChannelReadableStream(
288 + request: Request,
289 + stream: ReadableStream,
290 +): void {
291 + const reader = stream.getReader();
292 + const stringDecoder = createStringDecoder();
293 + let stringBuffer = '';
294 + function progress({
295 + done,
296 + value,
297 + }: {
298 + done: boolean,
299 + value: ?any,
300 + ...
301 + }): void | Promise<void> {
302 + const buffer: Uint8Array = (value: any);
303 + stringBuffer += done
304 + ? readFinalStringChunk(stringDecoder, new Uint8Array(0))
305 + : readPartialStringChunk(stringDecoder, buffer);
306 + const messages = stringBuffer.split('\n');
307 + for (let i = 0; i < messages.length - 1; i++) {
308 + resolveDebugMessage(request, messages[i]);
309 + }
310 + stringBuffer = messages[messages.length - 1];
311 + if (done) {
312 + closeDebugChannel(request);
313 + return;
314 + }
315 + return reader.read().then(progress).catch(error);
316 + }
317 + function error(e: any) {
318 + abort(
319 + request,
320 + new Error('Lost connection to the Debug Channel.', {
321 + cause: e,
322 + }),
323 + );
324 + }
325 + reader.read().then(progress).catch(error);
326 +}
327 +
328 +function renderToReadableStream(
329 + model: ReactClientValue,
330 + webpackMap: ClientManifest,
331 + options?: Omit<Options, 'debugChannel'> & {
332 + debugChannel?: {readable?: ReadableStream, writable?: WritableStream, ...},
333 + signal?: AbortSignal,
334 + },
335 +): ReadableStream {
336 + const debugChannelReadable =
337 + __DEV__ && options && options.debugChannel
338 + ? options.debugChannel.readable
339 + : undefined;
340 + const debugChannelWritable =
341 + __DEV__ && options && options.debugChannel
342 + ? options.debugChannel.writable
343 + : undefined;
344 + const request = createRequest(
345 + model,
346 + webpackMap,
347 + options ? options.onError : undefined,
348 + options ? options.identifierPrefix : undefined,
349 + options ? options.temporaryReferences : undefined,
350 + __DEV__ && options ? options.environmentName : undefined,
351 + __DEV__ && options ? options.filterStackFrame : undefined,
352 + debugChannelReadable !== undefined,
353 + );
354 + if (options && options.signal) {
355 + const signal = options.signal;
356 + if (signal.aborted) {
357 + abort(request, (signal: any).reason);
358 + } else {
359 + const listener = () => {
360 + abort(request, (signal: any).reason);
361 + signal.removeEventListener('abort', listener);
362 + };
363 + signal.addEventListener('abort', listener);
364 + }
365 + }
366 + if (debugChannelWritable !== undefined) {
367 + let debugWritable: Writable;
368 + const debugStream = new ReadableStream(
369 + {
370 + type: 'bytes',
371 + start: (controller): ?Promise<void> => {
372 + debugWritable =
373 + createFakeWritableFromReadableStreamController(controller);
374 + },
375 + pull: (controller): ?Promise<void> => {
376 + startFlowingDebug(request, debugWritable);
377 + },
378 + },
379 + // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
380 + {highWaterMark: 0},
381 + );
382 + debugStream.pipeTo(debugChannelWritable);
383 + }
384 + if (debugChannelReadable !== undefined) {
385 + startReadingFromDebugChannelReadableStream(request, debugChannelReadable);
386 + }
387 + let writable: Writable;
388 + const stream = new ReadableStream(
389 + {
390 + type: 'bytes',
391 + start: (controller): ?Promise<void> => {
392 + writable = createFakeWritableFromReadableStreamController(controller);
393 + startWork(request);
394 + },
395 + pull: (controller): ?Promise<void> => {
396 + startFlowing(request, writable);
397 + },
398 + cancel: (reason): ?Promise<void> => {
399 + stopFlowing(request);
400 + abort(request, reason);
401 + },
402 + },
403 + // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
404 + {highWaterMark: 0},
405 + );
406 + return stream;
407 +}
408 +
409 +function createFakeWritableFromNodeReadable(readable: any): Writable {
410 + // The current host config expects a Writable so we create
411 + // a fake writable for now to push into the Readable.
412 + return ({
413 + write(chunk: string | Uint8Array) {
414 + return readable.push(chunk);
415 + },
416 + end() {
417 + readable.push(null);
418 + },
419 + destroy(error) {
420 + readable.destroy(error);
421 + },
422 + }: any);
423 +}
424 +
425 +type PrerenderOptions = {
426 + environmentName?: string | (() => string),
427 + filterStackFrame?: (url: string, functionName: string) => boolean,
428 + onError?: (error: mixed) => void,
429 + identifierPrefix?: string,
430 + temporaryReferences?: TemporaryReferenceSet,
431 + signal?: AbortSignal,
432 +};
433 +
434 +type StaticResult = {
435 + prelude: Readable,
436 +};
437 +
438 +function prerenderToNodeStream(
439 + model: ReactClientValue,
440 + webpackMap: ClientManifest,
441 + options?: PrerenderOptions,
442 +): Promise<StaticResult> {
443 + return new Promise((resolve, reject) => {
444 + const onFatalError = reject;
445 + function onAllReady() {
446 + const readable: Readable = new Readable({
447 + read() {
448 + startFlowing(request, writable);
449 + },
450 + });
451 + const writable = createFakeWritableFromNodeReadable(readable);
452 + resolve({prelude: readable});
453 + }
454 +
455 + const request = createPrerenderRequest(
456 + model,
457 + webpackMap,
458 + onAllReady,
459 + onFatalError,
460 + options ? options.onError : undefined,
461 + options ? options.identifierPrefix : undefined,
462 + options ? options.temporaryReferences : undefined,
463 + __DEV__ && options ? options.environmentName : undefined,
464 + __DEV__ && options ? options.filterStackFrame : undefined,
465 + false,
466 + );
467 + if (options && options.signal) {
468 + const signal = options.signal;
469 + if (signal.aborted) {
470 + const reason = (signal: any).reason;
471 + abort(request, reason);
472 + } else {
473 + const listener = () => {
474 + const reason = (signal: any).reason;
475 + abort(request, reason);
476 + signal.removeEventListener('abort', listener);
477 + };
478 + signal.addEventListener('abort', listener);
479 + }
480 + }
481 + startWork(request);
482 + });
483 +}
484 +
485 +function prerender(
486 + model: ReactClientValue,
487 + webpackMap: ClientManifest,
488 + options?: Options & {
489 + signal?: AbortSignal,
490 + },
491 +): Promise<{
492 + prelude: ReadableStream,
493 +}> {
494 + return new Promise((resolve, reject) => {
495 + const onFatalError = reject;
496 + function onAllReady() {
497 + let writable: Writable;
498 + const stream = new ReadableStream(
499 + {
500 + type: 'bytes',
501 + start: (controller): ?Promise<void> => {
502 + writable =
503 + createFakeWritableFromReadableStreamController(controller);
504 + },
505 + pull: (controller): ?Promise<void> => {
506 + startFlowing(request, writable);
507 + },
508 + cancel: (reason): ?Promise<void> => {
509 + stopFlowing(request);
510 + abort(request, reason);
511 + },
512 + },
513 + // $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
514 + {highWaterMark: 0},
515 + );
516 + resolve({prelude: stream});
517 + }
518 + const request = createPrerenderRequest(
519 + model,
520 + webpackMap,
521 + onAllReady,
522 + onFatalError,
523 + options ? options.onError : undefined,
524 + options ? options.identifierPrefix : undefined,
525 + options ? options.temporaryReferences : undefined,
526 + __DEV__ && options ? options.environmentName : undefined,
527 + __DEV__ && options ? options.filterStackFrame : undefined,
528 + false,
529 + );
530 + if (options && options.signal) {
531 + const signal = options.signal;
532 + if (signal.aborted) {
533 + const reason = (signal: any).reason;
534 + abort(request, reason);
535 + } else {
536 + const listener = () => {
537 + const reason = (signal: any).reason;
538 + abort(request, reason);
539 + signal.removeEventListener('abort', listener);
540 + };
541 + signal.addEventListener('abort', listener);
542 + }
543 + }
544 + startWork(request);
545 + });
546 +}
547 +
548 +function decodeReplyFromBusboy<T>(
549 + busboyStream: Busboy,
550 + webpackMap: ServerManifest,
551 + options?: {temporaryReferences?: TemporaryReferenceSet},
552 +): Thenable<T> {
553 + const response = createResponse(
554 + webpackMap,
555 + '',
556 + options ? options.temporaryReferences : undefined,
557 + );
558 + let pendingFiles = 0;
559 + const queuedFields: Array<string> = [];
560 + busboyStream.on('field', (name, value) => {
561 + if (pendingFiles > 0) {
562 + // Because the 'end' event fires two microtasks after the next 'field'
563 + // we would resolve files and fields out of order. To handle this properly
564 + // we queue any fields we receive until the previous file is done.
565 + queuedFields.push(name, value);
566 + } else {
567 + try {
568 + resolveField(response, name, value);
569 + } catch (error) {
570 + busboyStream.destroy(error);
571 + }
572 + }
573 + });
574 + busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
575 + if (encoding.toLowerCase() === 'base64') {
576 + busboyStream.destroy(
577 + new Error(
578 + "React doesn't accept base64 encoded file uploads because we don't expect " +
579 + "form data passed from a browser to ever encode data that way. If that's " +
580 + 'the wrong assumption, we can easily fix it.',
581 + ),
582 + );
583 + return;
584 + }
585 + pendingFiles++;
586 + const file = resolveFileInfo(response, name, filename, mimeType);
587 + value.on('data', chunk => {
588 + resolveFileChunk(response, file, chunk);
589 + });
590 + value.on('end', () => {
591 + try {
592 + resolveFileComplete(response, name, file);
593 + pendingFiles--;
594 + if (pendingFiles === 0) {
595 + // Release any queued fields
596 + for (let i = 0; i < queuedFields.length; i += 2) {
597 + resolveField(response, queuedFields[i], queuedFields[i + 1]);
598 + }
599 + queuedFields.length = 0;
600 + }
601 + } catch (error) {
602 + busboyStream.destroy(error);
603 + }
604 + });
605 + });
606 + busboyStream.on('finish', () => {
607 + close(response);
608 + });
609 + busboyStream.on('error', err => {
610 + reportGlobalError(
611 + response,
612 + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible
613 + err,
614 + );
615 + });
616 + return getRoot(response);
617 +}
618 +
619 +function decodeReply<T>(
620 + body: string | FormData,
621 + webpackMap: ServerManifest,
622 + options?: {temporaryReferences?: TemporaryReferenceSet},
623 +): Thenable<T> {
624 + if (typeof body === 'string') {
625 + const form = new FormData();
626 + form.append('0', body);
627 + body = form;
628 + }
629 + const response = createResponse(
630 + webpackMap,
631 + '',
632 + options ? options.temporaryReferences : undefined,
633 + body,
634 + );
635 + const root = getRoot<T>(response);
636 + close(response);
637 + return root;
638 +}
639 +
640 +function decodeReplyFromAsyncIterable<T>(
641 + iterable: AsyncIterable<[string, string | File]>,
642 + webpackMap: ServerManifest,
643 + options?: {temporaryReferences?: TemporaryReferenceSet},
644 +): Thenable<T> {
645 + const iterator: AsyncIterator<[string, string | File]> =
646 + iterable[ASYNC_ITERATOR]();
647 +
648 + const response = createResponse(
649 + webpackMap,
650 + '',
651 + options ? options.temporaryReferences : undefined,
652 + );
653 +
654 + function progress(
655 + entry:
656 + | {done: false, +value: [string, string | File], ...}
657 + | {done: true, +value: void, ...},
658 + ) {
659 + if (entry.done) {
660 + close(response);
661 + } else {
662 + const [name, value] = entry.value;
663 + if (typeof value === 'string') {
664 + resolveField(response, name, value);
665 + } else {
666 + resolveFile(response, name, value);
667 + }
668 + iterator.next().then(progress, error);
669 + }
670 + }
671 + function error(reason: Error) {
672 + reportGlobalError(response, reason);
673 + if (typeof (iterator: any).throw === 'function') {
674 + // The iterator protocol doesn't necessarily include this but a generator do.
675 + // $FlowFixMe should be able to pass mixed
676 + iterator.throw(reason).then(error, error);
677 + }
678 + }
679 +
680 + iterator.next().then(progress, error);
681 +
682 + return getRoot(response);
683 +}
684 +
685 +export {
686 + renderToReadableStream,
687 + renderToPipeableStream,
688 + prerender,
689 + prerenderToNodeStream,
690 + decodeReply,
691 + decodeReplyFromBusboy,
692 + decodeReplyFromAsyncIterable,
693 + decodeAction,
694 + decodeFormState,
695 +};
packages/react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler.js new
+108
@@ -0,0 +1,108 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
11 +import type {
12 + ImportMetadata,
13 + ImportManifestEntry,
14 +} from '../shared/ReactFlightImportMetadata';
15 +
16 +import type {
17 + ClientReference,
18 + ServerReference,
19 +} from '../ReactFlightUnbundledReferences';
20 +
21 +export type {ClientReference, ServerReference};
22 +
23 +export type ClientManifest = {
24 + [id: string]: ClientReferenceManifestEntry,
25 +};
26 +
27 +export type ServerReferenceId = string;
28 +
29 +export type ClientReferenceMetadata = ImportMetadata;
30 +export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
31 +
32 +export type ClientReferenceKey = string;
33 +
34 +export {
35 + isClientReference,
36 + isServerReference,
37 +} from '../ReactFlightUnbundledReferences';
38 +
39 +export function getClientReferenceKey(
40 + reference: ClientReference<any>,
41 +): ClientReferenceKey {
42 + return reference.$$async ? reference.$$id + '#async' : reference.$$id;
43 +}
44 +
45 +export function resolveClientReferenceMetadata<T>(
46 + config: ClientManifest,
47 + clientReference: ClientReference<T>,
48 +): ClientReferenceMetadata {
49 + const modulePath = clientReference.$$id;
50 + let name = '';
51 + let resolvedModuleData = config[modulePath];
52 + if (resolvedModuleData) {
53 + // The potentially aliased name.
54 + name = resolvedModuleData.name;
55 + } else {
56 + // We didn't find this specific export name but we might have the * export
57 + // which contains this name as well.
58 + // TODO: It's unfortunate that we now have to parse this string. We should
59 + // probably go back to encoding path and name separately on the client reference.
60 + const idx = modulePath.lastIndexOf('#');
61 + if (idx !== -1) {
62 + name = modulePath.slice(idx + 1);
63 + resolvedModuleData = config[modulePath.slice(0, idx)];
64 + }
65 + if (!resolvedModuleData) {
66 + throw new Error(
67 + 'Could not find the module "' +
68 + modulePath +
69 + '" in the React Client Manifest. ' +
70 + 'This is probably a bug in the React Server Components bundler.',
71 + );
72 + }
73 + }
74 + if (resolvedModuleData.async === true && clientReference.$$async === true) {
75 + throw new Error(
76 + 'The module "' +
77 + modulePath +
78 + '" is marked as an async ESM module but was loaded as a CJS proxy. ' +
79 + 'This is probably a bug in the React Server Components bundler.',
80 + );
81 + }
82 + if (resolvedModuleData.async === true || clientReference.$$async === true) {
83 + return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
84 + } else {
85 + return [resolvedModuleData.id, resolvedModuleData.chunks, name];
86 + }
87 +}
88 +
89 +export function getServerReferenceId<T>(
90 + config: ClientManifest,
91 + serverReference: ServerReference<T>,
92 +): ServerReferenceId {
93 + return serverReference.$$id;
94 +}
95 +
96 +export function getServerReferenceBoundArguments<T>(
97 + config: ClientManifest,
98 + serverReference: ServerReference<T>,
99 +): null | Array<ReactClientValue> {
100 + return serverReference.$$bound;
101 +}
102 +
103 +export function getServerReferenceLocation<T>(
104 + config: ClientManifest,
105 + serverReference: ServerReference<T>,
106 +): void | Error {
107 + return serverReference.$$location;
108 +}
packages/react-server-dom-unbundled/src/server/react-flight-dom-server.node.js renamed
packages/react-server-dom-unbundled/src/shared/ReactFlightImportMetadata.js new
+44
@@ -0,0 +1,44 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export type ImportManifestEntry = {
11 + id: string,
12 + // chunks is a double indexed array of chunkId / chunkFilename pairs
13 + chunks: Array<string>,
14 + name: string,
15 + async?: boolean,
16 +};
17 +
18 +// This is the parsed shape of the wire format which is why it is
19 +// condensed to only the essentialy information
20 +export type ImportMetadata =
21 + | [
22 + /* id */ string,
23 + /* chunks id/filename pairs, double indexed */ Array<string>,
24 + /* name */ string,
25 + /* async */ 1,
26 + ]
27 + | [
28 + /* id */ string,
29 + /* chunks id/filename pairs, double indexed */ Array<string>,
30 + /* name */ string,
31 + ];
32 +
33 +export const ID = 0;
34 +export const CHUNKS = 1;
35 +export const NAME = 2;
36 +// export const ASYNC = 3;
37 +
38 +// This logic is correct because currently only include the 4th tuple member
39 +// when the module is async. If that changes we will need to actually assert
40 +// the value is true. We don't index into the 4th slot because flow does not
41 +// like the potential out of bounds access
42 +export function isAsyncImport(metadata: ImportMetadata): boolean {
43 + return metadata.length === 4;
44 +}
packages/react-server-dom-unbundled/static.js new
+13
@@ -0,0 +1,13 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +throw new Error(
11 + 'The React Server cannot be used outside a react-server environment. ' +
12 + 'You must configure Node.js using the `--conditions react-server` flag.',
13 +);
packages/react-server-dom-unbundled/static.node.js renamed
+1 -1
@@ -10,4 +10,4 @@
10 export {
11 prerender,
12 prerenderToNodeStream,
13 -} from './src/server/react-flight-dom-server.node.unbundled';
13 +} from './src/server/react-flight-dom-server.node';
packages/react-server-dom-webpack/package.json
+3 -18
@@ -17,17 +17,14 @@
17 "client.browser.js",
18 "client.edge.js",
19 "client.node.js",
20 - "client.node.unbundled.js",
20 "server.js",
21 "server.browser.js",
22 "server.edge.js",
23 "server.node.js",
25 - "server.node.unbundled.js",
24 "static.js",
25 "static.browser.js",
26 "static.edge.js",
27 "static.node.js",
30 - "static.node.unbundled.js",
28 "node-register.js",
29 "cjs/",
30 "esm/"
@@ -39,10 +36,7 @@
36 "workerd": "./client.edge.js",
37 "deno": "./client.edge.js",
38 "worker": "./client.edge.js",
42 - "node": {
43 - "webpack": "./client.node.js",
44 - "default": "./client.node.unbundled.js"
45 - },
39 + "node": "./client.node.js",
40 "edge-light": "./client.edge.js",
41 "browser": "./client.browser.js",
42 "default": "./client.browser.js"
@@ -50,15 +44,11 @@
44 "./client.browser": "./client.browser.js",
45 "./client.edge": "./client.edge.js",
46 "./client.node": "./client.node.js",
53 - "./client.node.unbundled": "./client.node.unbundled.js",
47 "./server": {
48 "react-server": {
49 "workerd": "./server.edge.js",
50 "deno": "./server.browser.js",
58 - "node": {
59 - "webpack": "./server.node.js",
60 - "default": "./server.node.unbundled.js"
61 - },
51 + "node": "./server.node.js",
52 "edge-light": "./server.edge.js",
53 "browser": "./server.browser.js"
54 },
@@ -67,15 +57,11 @@
57 "./server.browser": "./server.browser.js",
58 "./server.edge": "./server.edge.js",
59 "./server.node": "./server.node.js",
70 - "./server.node.unbundled": "./server.node.unbundled.js",
60 "./static": {
61 "react-server": {
62 "workerd": "./static.edge.js",
63 "deno": "./static.browser.js",
75 - "node": {
76 - "webpack": "./static.node.js",
77 - "default": "./static.node.unbundled.js"
78 - },
64 + "node": "./static.node.js",
65 "edge-light": "./static.edge.js",
66 "browser": "./static.browser.js"
67 },
@@ -84,7 +70,6 @@
70 "./static.browser": "./static.browser.js",
71 "./static.edge": "./static.edge.js",
72 "./static.node": "./static.node.js",
87 - "./static.node.unbundled": "./static.node.unbundled.js",
73 "./node-loader": "./esm/react-server-dom-webpack-node-loader.production.js",
74 "./node-register": "./node-register.js",
75 "./src/*": "./src/*.js",
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+2 -2
@@ -60,10 +60,10 @@ describe('ReactFlightDOM', () => {
60 FlightReactDOM = require('react-dom');
61
62 jest.mock('react-server-dom-webpack/server', () =>
63 - require('react-server-dom-webpack/server.node.unbundled'),
63 + require('react-server-dom-unbundled/server.node'),
64 );
65 jest.mock('react-server-dom-webpack/static', () =>
66 - require('react-server-dom-webpack/static.node.unbundled'),
66 + require('react-server-dom-unbundled/static.node'),
67 );
68 const WebpackMock = require('./utils/WebpackMock');
69 clientExports = WebpackMock.clientExports;
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js
+3 -3
@@ -45,12 +45,12 @@ describe('ReactFlightDOMNode', () => {
45 // Simulate the condition resolution
46 jest.mock('react', () => require('react/react.react-server'));
47 jest.mock('react-server-dom-webpack/server', () =>
48 - require('react-server-dom-webpack/server.node'),
48 + jest.requireActual('react-server-dom-webpack/server.node'),
49 );
50 ReactServer = require('react');
51 ReactServerDOMServer = require('react-server-dom-webpack/server');
52 jest.mock('react-server-dom-webpack/static', () =>
53 - require('react-server-dom-webpack/static.node'),
53 + jest.requireActual('react-server-dom-webpack/static.node'),
54 );
55 ReactServerDOMStaticServer = require('react-server-dom-webpack/static');
56
@@ -64,7 +64,7 @@ describe('ReactFlightDOMNode', () => {
64 __unmockReact();
65 jest.unmock('react-server-dom-webpack/server');
66 jest.mock('react-server-dom-webpack/client', () =>
67 - require('react-server-dom-webpack/client.node'),
67 + jest.requireActual('react-server-dom-webpack/client.node'),
68 );
69
70 React = require('react');
packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
+2 -2
@@ -41,7 +41,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
41
42 jest.mock('react', () => require('react/react.react-server'));
43 jest.mock('react-server-dom-webpack/server', () =>
44 - require('react-server-dom-webpack/server.node'),
44 + jest.requireActual('react-server-dom-webpack/server.node'),
45 );
46 ReactServer = require('react');
47 ReactServerDOMServer = require('react-server-dom-webpack/server');
@@ -54,7 +54,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
54 __unmockReact();
55 jest.unmock('react-server-dom-webpack/server');
56 jest.mock('react-server-dom-webpack/client', () =>
57 - require('react-server-dom-webpack/client.node'),
57 + jest.requireActual('react-server-dom-webpack/client.node'),
58 );
59
60 React = require('react');
packages/react-server/src/forks/ReactFlightServerConfig.dom-node-unbundled.js new
+29
@@ -0,0 +1,29 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import {AsyncLocalStorage} from 'async_hooks';
11 +
12 +import type {Request} from 'react-server/src/ReactFlightServer';
13 +import type {ReactComponentInfo} from 'shared/ReactTypes';
14 +
15 +export * from 'react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler';
16 +export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
17 +
18 +export const supportsRequestStorage = true;
19 +export const requestStorage: AsyncLocalStorage<Request | void> =
20 + new AsyncLocalStorage();
21 +
22 +export const supportsComponentStorage = __DEV__;
23 +export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
24 + supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
25 +
26 +export * from '../ReactFlightServerConfigDebugNode';
27 +
28 +export * from '../ReactFlightStackConfigV8';
29 +export * from '../ReactServerConsoleConfigServer';
scripts/rollup/bundles.js
+57 -30
@@ -476,25 +476,6 @@ const bundles = [
476 'util',
477 ],
478 },
479 - {
480 - bundleTypes: [NODE_DEV, NODE_PROD],
481 - moduleType: RENDERER,
482 - entry:
483 - 'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
484 - name: 'react-server-dom-webpack-server.node.unbundled',
485 - condition: 'react-server',
486 - global: 'ReactServerDOMServer',
487 - minifyWithProdErrorCodes: false,
488 - wrapWithModuleBoundaries: false,
489 - externals: [
490 - 'react',
491 - 'react-dom',
492 - 'async_hooks',
493 - 'crypto',
494 - 'stream',
495 - 'util',
496 - ],
497 - },
479 {
480 bundleTypes: [NODE_DEV, NODE_PROD],
481 moduleType: RENDERER,
@@ -529,17 +510,6 @@ const bundles = [
510 wrapWithModuleBoundaries: false,
511 externals: ['react', 'react-dom', 'util', 'crypto'],
512 },
532 - {
533 - bundleTypes: [NODE_DEV, NODE_PROD],
534 - moduleType: RENDERER,
535 - entry:
536 - 'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
537 - name: 'react-server-dom-webpack-client.node.unbundled',
538 - global: 'ReactServerDOMClient',
539 - minifyWithProdErrorCodes: false,
540 - wrapWithModuleBoundaries: false,
541 - externals: ['react', 'react-dom', 'util', 'crypto'],
542 - },
513 {
514 bundleTypes: [NODE_DEV, NODE_PROD],
515 moduleType: RENDERER,
@@ -786,6 +756,63 @@ const bundles = [
756 externals: ['acorn'],
757 },
758
759 + /******* React Server DOM Unbundled Server *******/
760 + {
761 + bundleTypes: [NODE_DEV, NODE_PROD],
762 + moduleType: RENDERER,
763 + entry: 'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
764 + name: 'react-server-dom-unbundled-server.node',
765 + condition: 'react-server',
766 + global: 'ReactServerDOMServer',
767 + minifyWithProdErrorCodes: false,
768 + wrapWithModuleBoundaries: false,
769 + externals: [
770 + 'react',
771 + 'react-dom',
772 + 'async_hooks',
773 + 'crypto',
774 + 'stream',
775 + 'util',
776 + ],
777 + },
778 +
779 + /******* React Server DOM Unbundled Client *******/
780 + {
781 + bundleTypes: [NODE_DEV, NODE_PROD],
782 + moduleType: RENDERER,
783 + entry: 'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
784 + name: 'react-server-dom-unbundled-client.node',
785 + global: 'ReactServerDOMClient',
786 + minifyWithProdErrorCodes: false,
787 + wrapWithModuleBoundaries: false,
788 + externals: ['react', 'react-dom', 'util', 'crypto'],
789 + },
790 +
791 + /******* React Server DOM Unbundled Node.js Loader *******/
792 + {
793 + bundleTypes: [ESM_PROD],
794 + moduleType: RENDERER_UTILS,
795 + entry: 'react-server-dom-unbundled/node-loader',
796 + condition: 'react-server',
797 + global: 'ReactServerUnbundledNodeLoader',
798 + minifyWithProdErrorCodes: false,
799 + wrapWithModuleBoundaries: false,
800 + externals: ['acorn'],
801 + },
802 +
803 + /******* React Server DOM Unbundled Node.js CommonJS Loader *******/
804 + {
805 + bundleTypes: [NODE_ES2015],
806 + moduleType: RENDERER_UTILS,
807 + entry: 'react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister',
808 + name: 'react-server-dom-unbundled-node-register',
809 + condition: 'react-server',
810 + global: 'ReactFlightUnbundledNodeRegister',
811 + minifyWithProdErrorCodes: false,
812 + wrapWithModuleBoundaries: false,
813 + externals: ['url', 'module', 'react-server-dom-unbundled/server'],
814 + },
815 +
816 /******* React Suspense Test Utils *******/
817 {
818 bundleTypes: [NODE_ES2015],
scripts/shared/inlinedHostConfigs.js
+47 -11
@@ -63,8 +63,8 @@ module.exports = [
63 'react-dom/src/server/react-dom-server.node.js',
64 'react-dom/test-utils',
65 'react-dom/unstable_server-external-runtime',
66 - 'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
67 - 'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
66 + 'react-server-dom-webpack/src/client/react-flight-dom-client.node',
67 + 'react-server-dom-webpack/src/server/react-flight-dom-server.node',
68 ],
69 paths: [
70 'react-dom',
@@ -84,20 +84,13 @@ module.exports = [
84 'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
85 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
86 'react-server-dom-webpack',
87 - 'react-server-dom-webpack/client.node.unbundled',
87 'react-server-dom-webpack/server',
89 - 'react-server-dom-webpack/server.node.unbundled',
88 'react-server-dom-webpack/static',
91 - 'react-server-dom-webpack/static.node.unbundled',
89 'react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-webpack/client.node
90 'react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
94 - 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js',
95 - 'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
96 - 'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
91 + 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js',
92 + 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer.js',
93 'react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js', // react-server-dom-webpack/src/server/react-flight-dom-server.node
98 - 'react-devtools',
99 - 'react-devtools-core',
100 - 'react-devtools-shell',
94 'react-devtools-shared',
95 'shared/ReactDOMSharedInternals',
96 'react-server/src/ReactFlightServerConfigDebugNode.js',
@@ -240,6 +233,49 @@ module.exports = [
233 isFlowTyped: true,
234 isServerSupported: true,
235 },
236 + {
237 + shortName: 'dom-node-unbundled',
238 + entryPoints: [
239 + 'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
240 + 'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
241 + ],
242 + paths: [
243 + 'react-dom',
244 + 'react-dom-bindings',
245 + 'react-dom/client',
246 + 'react-dom/profiling',
247 + 'react-dom/server',
248 + 'react-dom/server.node',
249 + 'react-dom/static',
250 + 'react-dom/static.node',
251 + 'react-dom/src/server/react-dom-server.node',
252 + 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
253 + 'react-dom/src/server/ReactDOMFizzStaticNode.js',
254 + 'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
255 + 'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
256 + 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
257 + 'react-server-dom-unbundled',
258 + 'react-server-dom-unbundled/client',
259 + 'react-server-dom-unbundled/server',
260 + 'react-server-dom-unbundled/server.node',
261 + 'react-server-dom-unbundled/static',
262 + 'react-server-dom-unbundled/static.node',
263 + 'react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-unbundled/client.node
264 + 'react-server-dom-unbundled/src/client/ReactFlightDOMClientNode.js', // react-server-dom-unbundled/client.node
265 + 'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js',
266 + 'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
267 + 'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
268 + 'react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js', // react-server-dom-unbundled/src/server/react-flight-dom-server.node
269 + 'react-devtools',
270 + 'react-devtools-core',
271 + 'react-devtools-shell',
272 + 'react-devtools-shared',
273 + 'shared/ReactDOMSharedInternals',
274 + 'react-server/src/ReactFlightServerConfigDebugNode.js',
275 + ],
276 + isFlowTyped: true,
277 + isServerSupported: true,
278 + },
279 {
280 shortName: 'dom-bun',
281 entryPoints: ['react-dom/src/server/react-dom-server.bun.js'],