[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'],