@samitouri / QOS-React-1 / commits / ad5dfc82b7

Add react-flight-server-fb package for Meta's internal bundler (#36309)

<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn test --debug --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary <!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? --> - Adds a new react-flight-server-fb package providing RSC Flight bindings for Meta's internal bundler stack - Unlike webpack/turbopack integrations, this uses no manifest. Module metadata is self-contained in ClientReference objects and sent over the wire as-is - Registers dom-browser-fb and dom-node-fb host configs for Rollup builds targeting FB_WWW_DEV and FB_WWW_PROD Key design differences from other bundler - No build-time manifest - Module IDs use Haste module names (e.g. `"MyComponent"`), with named exports encoded as `"Module#export"`, rather than file paths resolved through a manifest - Client-side loading uses `Bootloader.handlePayload()` + `JSResource().load()` - `resolveClientReferenceMetadata` and `resolveClientReference` are pass-throughs ## How did you test this change? <!-- Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes the user interface. How exactly did you verify that your PR solves the issue you wanted to solve? If you leave this empty, your PR will very likely be closed. --> E2E integration test is set up on Meta's internal system.

Xinzi Zhou committed Apr 27, 2026 at 09:48 UTC ad5dfc82b7107728da1430dd142f75b97b684dac
24 files changed +1733 -23
.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-flight-server-fb/**/*.js',
335 'packages/react-server-dom-unbundled/**/*.js',
336 'packages/react-test-renderer/**/*.js',
337 'packages/react-debug-tools/**/*.js',
.github/workflows/runtime_build_and_test.yml
+19 -19
@@ -45,7 +45,7 @@ jobs:
45 with:
46 path: |
47 **/node_modules
48 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
48 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
49 lookup-only: true
50 - uses: actions/setup-node@v4
51 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -59,7 +59,7 @@ jobs:
59 with:
60 path: |
61 **/node_modules
62 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
62 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
63 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
64 - run: yarn install --frozen-lockfile
65 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -69,7 +69,7 @@ jobs:
69 with:
70 path: |
71 **/node_modules
72 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
72 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
73
74 runtime_compiler_node_modules_cache:
75 name: Cache Runtime, Compiler node_modules
@@ -84,7 +84,7 @@ jobs:
84 with:
85 path: |
86 **/node_modules
87 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
87 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
88 lookup-only: true
89 - uses: actions/setup-node@v4
90 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -100,7 +100,7 @@ jobs:
100 with:
101 path: |
102 **/node_modules
103 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
103 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
104 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
105 - run: yarn install --frozen-lockfile
106 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -112,7 +112,7 @@ jobs:
112 with:
113 path: |
114 **/node_modules
115 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
115 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
116
117 # ----- FLOW -----
118 discover_flow_inline_configs:
@@ -154,7 +154,7 @@ jobs:
154 with:
155 path: |
156 **/node_modules
157 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
157 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
158 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
159 - name: Ensure clean build directory
160 run: rm -rf build
@@ -182,7 +182,7 @@ jobs:
182 with:
183 path: |
184 **/node_modules
185 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
185 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
186 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
187 - name: Ensure clean build directory
188 run: rm -rf build
@@ -212,7 +212,7 @@ jobs:
212 with:
213 path: |
214 **/node_modules
215 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
215 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
216 - name: Ensure clean build directory
217 run: rm -rf build
218 - run: yarn install --frozen-lockfile
@@ -270,7 +270,7 @@ jobs:
270 with:
271 path: |
272 **/node_modules
273 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
273 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
274 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
275 - name: Ensure clean build directory
276 run: rm -rf build
@@ -301,7 +301,7 @@ jobs:
301 with:
302 path: |
303 **/node_modules
304 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
304 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
305 - name: Install runtime dependencies
306 run: yarn install --frozen-lockfile
307 if: steps.node_modules.outputs.cache-hit != 'true'
@@ -344,7 +344,7 @@ jobs:
344 with:
345 path: |
346 **/node_modules
347 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
347 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
348 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
349 - name: Ensure clean build directory
350 run: rm -rf build
@@ -430,7 +430,7 @@ jobs:
430 with:
431 path: |
432 **/node_modules
433 - key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
433 + key: runtime-and-compiler-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
434 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
435 - name: Ensure clean build directory
436 run: rm -rf build
@@ -477,7 +477,7 @@ jobs:
477 with:
478 path: |
479 **/node_modules
480 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
480 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
481 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
482 - name: Ensure clean build directory
483 run: rm -rf build
@@ -517,7 +517,7 @@ jobs:
517 with:
518 path: |
519 **/node_modules
520 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
520 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
521 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
522 - name: Ensure clean build directory
523 run: rm -rf build
@@ -580,7 +580,7 @@ jobs:
580 with:
581 path: |
582 **/node_modules
583 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
583 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
584 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
585 - name: Ensure clean build directory
586 run: rm -rf build
@@ -618,7 +618,7 @@ jobs:
618 with:
619 path: |
620 **/node_modules
621 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
621 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
622 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
623 - name: Ensure clean build directory
624 run: rm -rf build
@@ -768,7 +768,7 @@ jobs:
768 with:
769 path: |
770 **/node_modules
771 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
771 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
772 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
773 - name: Ensure clean build directory
774 run: rm -rf build
@@ -828,7 +828,7 @@ jobs:
828 with:
829 path: |
830 **/node_modules
831 - key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
831 + key: runtime-node_modules-v8-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
832 # Don't use restore-keys here. Otherwise the cache grows indefinitely.
833 - name: Ensure clean build directory
834 run: rm -rf build
.github/workflows/shared_lint.yml
+4 -4
@@ -33,7 +33,7 @@ jobs:
33 with:
34 path: |
35 **/node_modules
36 - key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
36 + key: shared-lint-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
37 - name: Ensure clean build directory
38 run: rm -rf build
39 - run: yarn install --frozen-lockfile
@@ -56,7 +56,7 @@ jobs:
56 with:
57 path: |
58 **/node_modules
59 - key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
59 + key: shared-lint-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
60 - name: Ensure clean build directory
61 run: rm -rf build
62 - run: yarn install --frozen-lockfile
@@ -79,7 +79,7 @@ jobs:
79 with:
80 path: |
81 **/node_modules
82 - key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
82 + key: shared-lint-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
83 - name: Ensure clean build directory
84 run: rm -rf build
85 - run: yarn install --frozen-lockfile
@@ -102,7 +102,7 @@ jobs:
102 with:
103 path: |
104 **/node_modules
105 - key: shared-lint-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
105 + key: shared-lint-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
106 - name: Ensure clean build directory
107 run: rm -rf build
108 - run: yarn install --frozen-lockfile
packages/react-client/src/forks/ReactFlightClientConfig.dom-browser-fb.js new
+19
@@ -0,0 +1,19 @@
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 {default as rendererVersion} from 'shared/ReactVersion';
11 +export const rendererPackageName = 'react-flight-server-fb';
12 +
13 +export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 +export * from 'react-client/src/ReactClientConsoleConfigBrowser';
15 +export * from 'react-client/src/ReactClientDebugConfigBrowser';
16 +export * from 'react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB';
17 +export * from 'react-flight-server-fb/src/client/ReactFlightClientConfigTargetFBBrowser';
18 +export * from 'react-flight-server-fb/src/client/ReactFlightClientConfigDOMFB';
19 +export const usedWithSSR = false;
packages/react-client/src/forks/ReactFlightClientConfig.dom-node-fb.js new
+19
@@ -0,0 +1,19 @@
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 {default as rendererVersion} from 'shared/ReactVersion';
11 +export const rendererPackageName = 'react-flight-server-fb';
12 +
13 +export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
14 +export * from 'react-client/src/ReactClientConsoleConfigServer';
15 +export * from 'react-client/src/ReactClientDebugConfigNode';
16 +export * from 'react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB';
17 +export * from 'react-flight-server-fb/src/client/ReactFlightClientConfigTargetFBBrowser';
18 +export * from 'react-flight-server-fb/src/client/ReactFlightClientConfigDOMFB';
19 +export const usedWithSSR = false;
packages/react-flight-server-fb/README.md new
+5
@@ -0,0 +1,5 @@
1 +# react-flight-server-fb
2 +
3 +React Flight bindings for DOM using Meta's internal bundler.
4 +
5 +**Use it at your own risk.**
packages/react-flight-server-fb/client.browser.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/client/ReactFlightDOMClientBrowser';
packages/react-flight-server-fb/package.json new
+34
@@ -0,0 +1,34 @@
1 +{
2 + "name": "react-flight-server-fb",
3 + "description": "React Server Components bindings for DOM using Meta's internal bundler. It is not intended to be imported directly.",
4 + "version": "19.3.0",
5 + "keywords": [
6 + "react"
7 + ],
8 + "homepage": "https://react.dev/",
9 + "bugs": "https://github.com/facebook/react/issues",
10 + "license": "MIT",
11 + "files": [
12 + "LICENSE",
13 + "README.md",
14 + "client.browser.js"
15 + ],
16 + "exports": {
17 + "./client": "./client.browser.js",
18 + "./client.browser": "./client.browser.js",
19 + "./src/*": "./src/*.js",
20 + "./package.json": "./package.json"
21 + },
22 + "repository": {
23 + "type" : "git",
24 + "url" : "https://github.com/facebook/react.git",
25 + "directory": "packages/react-flight-server-fb"
26 + },
27 + "engines": {
28 + "node": ">=0.10.0"
29 + },
30 + "peerDependencies": {
31 + "react": "^19.0.0",
32 + "react-dom": "^19.0.0"
33 + }
34 +}
packages/react-flight-server-fb/src/ReactFlightFBReferences.js new
+131
@@ -0,0 +1,131 @@
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 + $$hblp: mixed,
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 + hblp: mixed,
41 +): ClientReference<T> {
42 + return Object.defineProperties(proxyImplementation, {
43 + $$typeof: {value: CLIENT_REFERENCE_TAG},
44 + $$id: {value: id},
45 + $$hblp: {value: hblp},
46 + });
47 +}
48 +
49 +// $FlowFixMe[method-unbinding]
50 +const FunctionBind = Function.prototype.bind;
51 +// $FlowFixMe[method-unbinding]
52 +const ArraySlice = Array.prototype.slice;
53 +function bind(this: ServerReference<any>): any {
54 + // $FlowFixMe[incompatible-call]
55 + const newFn = FunctionBind.apply(this, arguments);
56 + if (this.$$typeof === SERVER_REFERENCE_TAG) {
57 + if (__DEV__) {
58 + const thisBind = arguments[0];
59 + if (thisBind != null) {
60 + console.error(
61 + 'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
62 + );
63 + }
64 + }
65 + const args = ArraySlice.call(arguments, 1);
66 + const $$typeof = {value: SERVER_REFERENCE_TAG};
67 + const $$id = {value: this.$$id};
68 + const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args};
69 + return Object.defineProperties(
70 + (newFn: any),
71 + (__DEV__
72 + ? {
73 + $$typeof,
74 + $$id,
75 + $$bound,
76 + $$location: {
77 + value: this.$$location,
78 + configurable: true,
79 + },
80 + bind: {value: bind, configurable: true},
81 + }
82 + : {
83 + $$typeof,
84 + $$id,
85 + $$bound,
86 + bind: {value: bind, configurable: true},
87 + }) as PropertyDescriptorMap,
88 + );
89 + }
90 + return newFn;
91 +}
92 +
93 +const serverReferenceToString = {
94 + value: () => 'function () { [omitted code] }',
95 + configurable: true,
96 + writable: true,
97 +};
98 +
99 +export function registerServerReference<T: Function>(
100 + reference: T,
101 + id: string,
102 +): ServerReference<T> {
103 + const $$typeof = {value: SERVER_REFERENCE_TAG};
104 + const $$id = {
105 + value: id,
106 + configurable: true,
107 + };
108 + const $$bound = {value: null, configurable: true};
109 + return Object.defineProperties(
110 + (reference: any),
111 + (__DEV__
112 + ? {
113 + $$typeof,
114 + $$id,
115 + $$bound,
116 + $$location: {
117 + value: Error('react-stack-top-frame'),
118 + configurable: true,
119 + },
120 + bind: {value: bind, configurable: true},
121 + toString: serverReferenceToString,
122 + }
123 + : {
124 + $$typeof,
125 + $$id,
126 + $$bound,
127 + bind: {value: bind, configurable: true},
128 + toString: serverReferenceToString,
129 + }) as PropertyDescriptorMap,
130 + );
131 +}
packages/react-flight-server-fb/src/ReactServerStreamConfigFB.js new
+240
@@ -0,0 +1,240 @@
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 +// Fork of ReactServerStreamConfigNode for the FB Flight server.
11 +// Avoids importing from 'util' and 'crypto'. Uses the global TextEncoder
12 +// and stubs createFastHash.
13 +
14 +import type {Writable} from 'stream';
15 +
16 +import type {TextEncoder as TextEncoderType} from 'util';
17 +
18 +interface MightBeFlushable {
19 + flush?: () => void;
20 +}
21 +
22 +export type Destination = Writable & MightBeFlushable;
23 +
24 +export type PrecomputedChunk = Uint8Array;
25 +export opaque type Chunk = string;
26 +export type BinaryChunk = Uint8Array;
27 +
28 +export function scheduleWork(callback: () => void) {
29 + setImmediate(callback);
30 +}
31 +
32 +export function scheduleMicrotask(callback: () => void) {
33 + Promise.resolve().then(callback);
34 +}
35 +
36 +export function flushBuffered(destination: Destination) {
37 + // If we don't have any more data to send right now.
38 + // Flush whatever is in the buffer to the wire.
39 + if (typeof destination.flush === 'function') {
40 + // By convention the Zlib streams provide a flush function for this purpose.
41 + // For Express, compression middleware adds this method.
42 + destination.flush();
43 + }
44 +}
45 +
46 +const VIEW_SIZE = 4096;
47 +let currentView = null;
48 +let writtenBytes = 0;
49 +let destinationHasCapacity = true;
50 +
51 +export function beginWriting(destination: Destination) {
52 + currentView = new Uint8Array(VIEW_SIZE);
53 + writtenBytes = 0;
54 + destinationHasCapacity = true;
55 +}
56 +
57 +function writeStringChunk(destination: Destination, stringChunk: string) {
58 + if (stringChunk.length === 0) {
59 + return;
60 + }
61 + // maximum possible view needed to encode entire string
62 + if (stringChunk.length * 3 > VIEW_SIZE) {
63 + if (writtenBytes > 0) {
64 + writeToDestination(
65 + destination,
66 + ((currentView: any): Uint8Array).subarray(0, writtenBytes),
67 + );
68 + currentView = new Uint8Array(VIEW_SIZE);
69 + writtenBytes = 0;
70 + }
71 + // Write the raw string chunk and let the consumer handle the encoding.
72 + writeToDestination(destination, stringChunk);
73 + return;
74 + }
75 +
76 + let target: Uint8Array = (currentView: any);
77 + if (writtenBytes > 0) {
78 + target = ((currentView: any): Uint8Array).subarray(writtenBytes);
79 + }
80 + const {read, written} = textEncoder.encodeInto(stringChunk, target);
81 + writtenBytes += written;
82 +
83 + if (read < stringChunk.length) {
84 + writeToDestination(
85 + destination,
86 + (currentView: any).subarray(0, writtenBytes),
87 + );
88 + currentView = new Uint8Array(VIEW_SIZE);
89 + writtenBytes = textEncoder.encodeInto(
90 + stringChunk.slice(read),
91 + (currentView: any),
92 + ).written;
93 + }
94 +
95 + if (writtenBytes === VIEW_SIZE) {
96 + writeToDestination(destination, (currentView: any));
97 + currentView = new Uint8Array(VIEW_SIZE);
98 + writtenBytes = 0;
99 + }
100 +}
101 +
102 +function writeViewChunk(
103 + destination: Destination,
104 + chunk: PrecomputedChunk | BinaryChunk,
105 +) {
106 + if (chunk.byteLength === 0) {
107 + return;
108 + }
109 + if (chunk.byteLength > VIEW_SIZE) {
110 + if (writtenBytes > 0) {
111 + writeToDestination(
112 + destination,
113 + ((currentView: any): Uint8Array).subarray(0, writtenBytes),
114 + );
115 + currentView = new Uint8Array(VIEW_SIZE);
116 + writtenBytes = 0;
117 + }
118 + writeToDestination(destination, chunk);
119 + return;
120 + }
121 +
122 + let bytesToWrite = chunk;
123 + const allowableBytes = ((currentView: any): Uint8Array).length - writtenBytes;
124 + if (allowableBytes < bytesToWrite.byteLength) {
125 + if (allowableBytes === 0) {
126 + writeToDestination(destination, (currentView: any));
127 + } else {
128 + ((currentView: any): Uint8Array).set(
129 + bytesToWrite.subarray(0, allowableBytes),
130 + writtenBytes,
131 + );
132 + writtenBytes += allowableBytes;
133 + writeToDestination(destination, (currentView: any));
134 + bytesToWrite = bytesToWrite.subarray(allowableBytes);
135 + }
136 + currentView = new Uint8Array(VIEW_SIZE);
137 + writtenBytes = 0;
138 + }
139 + ((currentView: any): Uint8Array).set(bytesToWrite, writtenBytes);
140 + writtenBytes += bytesToWrite.byteLength;
141 +
142 + if (writtenBytes === VIEW_SIZE) {
143 + writeToDestination(destination, (currentView: any));
144 + currentView = new Uint8Array(VIEW_SIZE);
145 + writtenBytes = 0;
146 + }
147 +}
148 +
149 +export function writeChunk(
150 + destination: Destination,
151 + chunk: PrecomputedChunk | Chunk | BinaryChunk,
152 +): void {
153 + if (typeof chunk === 'string') {
154 + writeStringChunk(destination, chunk);
155 + } else {
156 + writeViewChunk(destination, ((chunk: any): PrecomputedChunk | BinaryChunk));
157 + }
158 +}
159 +
160 +function writeToDestination(
161 + destination: Destination,
162 + view: string | Uint8Array,
163 +) {
164 + const currentHasCapacity = destination.write(view);
165 + destinationHasCapacity = destinationHasCapacity && currentHasCapacity;
166 +}
167 +
168 +export function writeChunkAndReturn(
169 + destination: Destination,
170 + chunk: PrecomputedChunk | Chunk,
171 +): boolean {
172 + writeChunk(destination, chunk);
173 + return destinationHasCapacity;
174 +}
175 +
176 +export function completeWriting(destination: Destination) {
177 + if (currentView && writtenBytes > 0) {
178 + destination.write(currentView.subarray(0, writtenBytes));
179 + }
180 + currentView = null;
181 + writtenBytes = 0;
182 + destinationHasCapacity = true;
183 +}
184 +
185 +export function close(destination: Destination) {
186 + destination.end();
187 +}
188 +
189 +export const textEncoder: TextEncoderType = (new TextEncoder(): any);
190 +
191 +export function stringToChunk(content: string): Chunk {
192 + return content;
193 +}
194 +
195 +export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
196 + const precomputedChunk = textEncoder.encode(content);
197 +
198 + if (__DEV__) {
199 + if (precomputedChunk.byteLength > VIEW_SIZE) {
200 + console.error(
201 + 'precomputed chunks must be smaller than the view size configured for this host. This is a bug in React.',
202 + );
203 + }
204 + }
205 +
206 + return precomputedChunk;
207 +}
208 +
209 +export function typedArrayToBinaryChunk(
210 + content: $ArrayBufferView,
211 +): BinaryChunk {
212 + return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
213 +}
214 +
215 +export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
216 + return typeof chunk === 'string'
217 + ? Buffer.byteLength(chunk, 'utf8')
218 + : chunk.byteLength;
219 +}
220 +
221 +export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
222 + return chunk.byteLength;
223 +}
224 +
225 +export function closeWithError(destination: Destination, error: mixed): void {
226 + // $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
227 + destination.destroy(error);
228 +}
229 +
230 +export function createFastHash(_input: string): string | number {
231 + throw new Error('createFastHash is not supported in this environment.');
232 +}
233 +
234 +export function readAsDataURL(blob: Blob): Promise<string> {
235 + return blob.arrayBuffer().then(arrayBuffer => {
236 + const encoded = Buffer.from(arrayBuffer).toString('base64');
237 + const mimeType = blob.type || 'application/octet-stream';
238 + return 'data:' + mimeType + ';base64,' + encoded;
239 + });
240 +}
packages/react-flight-server-fb/src/client/ReactFlightClientConfigBundlerFB.js new
+239
@@ -0,0 +1,239 @@
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 + Thenable,
12 + FulfilledThenable,
13 + RejectedThenable,
14 + ReactDebugInfo,
15 + ReactIOInfo,
16 + ReactAsyncInfo,
17 +} from 'shared/ReactTypes';
18 +
19 +import type {ModuleLoading} from 'react-client/src/ReactFlightClientConfig';
20 +
21 +import {canUseDOM} from 'shared/ExecutionEnvironment';
22 +
23 +export type ServerConsumerModuleMap = null;
24 +
25 +export type ServerManifest = null;
26 +
27 +export type ServerReferenceId = string;
28 +
29 +import {prepareDestinationForModuleImpl} from 'react-client/src/ReactFlightClientConfig';
30 +
31 +export opaque type ClientReferenceMetadata = {
32 + $$typeof: symbol,
33 + $$id: string,
34 + $$hblp: mixed,
35 +};
36 +
37 +// eslint-disable-next-line no-unused-vars
38 +export opaque type ClientReference<T> = {
39 + $$typeof: symbol,
40 + $$id: string,
41 + $$hblp: mixed,
42 +};
43 +
44 +export function prepareDestinationForModule(
45 + moduleLoading: ModuleLoading,
46 + nonce: ?string,
47 + metadata: ClientReferenceMetadata,
48 +) {
49 + prepareDestinationForModuleImpl(moduleLoading, metadata.$$hblp, nonce);
50 +}
51 +
52 +export function resolveClientReference<T>(
53 + bundlerConfig: ServerConsumerModuleMap,
54 + metadata: ClientReferenceMetadata,
55 +): ClientReference<T> {
56 + return metadata;
57 +}
58 +
59 +// Called on the server when decoding a client's action reply (via decodeReply).
60 +// Maps the $$id string sent by the client back to module metadata so the server
61 +// can locate and execute the actual function. $$hblp is null because this runs
62 +// on the server — there is no client module to download.
63 +export function resolveServerReference<T>(
64 + config: ServerManifest,
65 + id: ServerReferenceId,
66 +): ClientReference<T> {
67 + return ({
68 + $$typeof: Symbol.for('react.client.reference'),
69 + $$id: id,
70 + $$hblp: null,
71 + }: any);
72 +}
73 +
74 +const asyncModuleCache: Map<string, Thenable<any>> = new Map();
75 +
76 +export function preloadModule<T>(
77 + metadata: ClientReference<T>,
78 +): null | Thenable<any> {
79 + if (!canUseDOM) {
80 + // Server environment: modules are synchronously available via require().
81 + return null;
82 + }
83 +
84 + // $FlowFixMe[cannot-resolve-module] JSResource is a Meta-internal module
85 + const jsr: any = require('JSResource')(metadata.$$id);
86 +
87 + const previouslyLoadedModule = jsr.getModuleIfRequireable();
88 + if (previouslyLoadedModule != null) {
89 + return null;
90 + }
91 +
92 + if (metadata.$$hblp != null) {
93 + // Register our updates with the bootloader.
94 + window.Bootloader.handlePayload(metadata.$$hblp);
95 + }
96 +
97 + const modulePromise: Thenable<T> = jsr.load();
98 + modulePromise.then(
99 + value => {
100 + const fulfilledThenable: FulfilledThenable<mixed> = (modulePromise: any);
101 + fulfilledThenable.status = 'fulfilled';
102 + fulfilledThenable.value = value;
103 + },
104 + reason => {
105 + const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
106 + rejectedThenable.status = 'rejected';
107 + rejectedThenable.reason = reason;
108 + },
109 + );
110 + asyncModuleCache.set(metadata.$$id, modulePromise);
111 + return modulePromise;
112 +}
113 +
114 +export function requireModule<T>(metadata: ClientReference<T>): T {
115 + if (!canUseDOM) {
116 + // When the Flight client runs on the server to consume a Flight stream,
117 + // modules are resolved synchronously via require() with Haste module names.
118 + const id = metadata.$$id;
119 + const idx = id.lastIndexOf('#');
120 + if (idx !== -1) {
121 + const moduleName = id.slice(0, idx);
122 + const exportName = id.slice(idx + 1);
123 + // Use .call to prevent bundlers from statically resolving this require.
124 + const mod = require.call(null, moduleName); // eslint-disable-line no-useless-call
125 + if (exportName === '' || exportName === 'default') {
126 + return mod.__esModule ? mod.default : mod;
127 + }
128 + return mod[exportName];
129 + }
130 + // Use .call to prevent bundlers from statically resolving this require.
131 + return require.call(null, id); // eslint-disable-line no-useless-call
132 + }
133 +
134 + // $FlowFixMe[cannot-resolve-module] JSResource is a Meta-internal module
135 + const jsr: any = require('JSResource')(metadata.$$id);
136 +
137 + const moduleExports = jsr.getModuleIfRequireable();
138 + if (moduleExports != null) {
139 + return moduleExports;
140 + }
141 +
142 + // Fall back to the async cache if JSResource doesn't have it yet.
143 + const promise: any = asyncModuleCache.get(metadata.$$id);
144 + if (promise && promise.status === 'fulfilled') {
145 + return promise.value;
146 + } else {
147 + throw promise.reason;
148 + }
149 +}
150 +
151 +// We cache ReactIOInfo across requests so that inner refreshes can dedupe with outer.
152 +const moduleIOInfoCache: Map<string, ReactIOInfo> = __DEV__
153 + ? new Map()
154 + : (null: any);
155 +
156 +export function getModuleDebugInfo<T>(
157 + metadata: ClientReference<T>,
158 +): null | ReactDebugInfo {
159 + if (!__DEV__) {
160 + return null;
161 + }
162 + const filename = metadata.$$id;
163 + let ioInfo = moduleIOInfoCache.get(filename);
164 + if (ioInfo === undefined) {
165 + let href;
166 + try {
167 + // $FlowFixMe
168 + href = new URL(filename, document.baseURI).href;
169 + } catch (_) {
170 + href = filename;
171 + }
172 + let start = -1;
173 + let end = -1;
174 + let byteSize = 0;
175 + // $FlowFixMe[method-unbinding]
176 + if (typeof performance.getEntriesByType === 'function') {
177 + // We may be able to collect the start and end time of this resource from Performance Observer.
178 + const resourceEntries = performance.getEntriesByType('resource');
179 + for (let i = 0; i < resourceEntries.length; i++) {
180 + const resourceEntry = resourceEntries[i];
181 + if (resourceEntry.name === href) {
182 + start = resourceEntry.startTime;
183 + end = start + resourceEntry.duration;
184 + // $FlowFixMe[prop-missing]
185 + byteSize = (resourceEntry.transferSize: any) || 0;
186 + }
187 + }
188 + }
189 + const value = Promise.resolve(href);
190 + // $FlowFixMe
191 + value.status = 'fulfilled';
192 + // Is there some more useful representation for the chunk?
193 + // $FlowFixMe
194 + value.value = href;
195 + // Create a fake stack frame that points to the beginning of the chunk. This is
196 + // probably not source mapped so will link to the compiled source rather than
197 + // any individual file that goes into the chunks.
198 + const fakeStack = new Error('react-stack-top-frame');
199 + if (fakeStack.stack.startsWith('Error: react-stack-top-frame')) {
200 + // Looks like V8
201 + fakeStack.stack =
202 + 'Error: react-stack-top-frame\n' +
203 + // Add two frames since we always trim one off the top.
204 + ' at Client Component Bundle (' +
205 + href +
206 + ':1:1)\n' +
207 + ' at Client Component Bundle (' +
208 + href +
209 + ':1:1)';
210 + } else {
211 + // Looks like Firefox or Safari.
212 + // Add two frames since we always trim one off the top.
213 + fakeStack.stack =
214 + 'Client Component Bundle@' +
215 + href +
216 + ':1:1\n' +
217 + 'Client Component Bundle@' +
218 + href +
219 + ':1:1';
220 + }
221 + ioInfo = ({
222 + name: 'script',
223 + start: start,
224 + end: end,
225 + value: value,
226 + debugStack: fakeStack,
227 + }: ReactIOInfo);
228 + if (byteSize > 0) {
229 + // $FlowFixMe[cannot-write]
230 + ioInfo.byteSize = byteSize;
231 + }
232 + moduleIOInfoCache.set(filename, ioInfo);
233 + }
234 + // We could dedupe the async info too but conceptually each request is its own await.
235 + const asyncInfo: ReactAsyncInfo = {
236 + awaited: ioInfo,
237 + };
238 + return [asyncInfo];
239 +}
packages/react-flight-server-fb/src/client/ReactFlightClientConfigDOMFB.js new
+34
@@ -0,0 +1,34 @@
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 +// Custom implementation of FlightClientConfigDOM.
11 +// Resource hint dispatching is a no-op since we handle resource loading
12 +// through our own runtime.
13 +
14 +import type {
15 + HintCode,
16 + HintModel,
17 +} from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
18 +
19 +export function dispatchHint<Code: HintCode>(
20 + code: Code,
21 + model: HintModel<Code>,
22 +): void {}
23 +
24 +export function preinitModuleForSSR(
25 + href: string,
26 + nonce: ?string,
27 + crossOrigin: ?string,
28 +) {}
29 +
30 +export function preinitScriptForSSR(
31 + href: string,
32 + nonce: ?string,
33 + crossOrigin: ?string,
34 +) {}
packages/react-flight-server-fb/src/client/ReactFlightClientConfigTargetFBBrowser.js new
+18
@@ -0,0 +1,18 @@
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 ModuleLoading = null;
11 +
12 +export function prepareDestinationForModuleImpl(
13 + moduleLoading: ModuleLoading,
14 + chunks: mixed,
15 + nonce: ?string,
16 +) {
17 + // In the browser we don't need to prepare our destination since the browser is the Destination
18 +}
packages/react-flight-server-fb/src/client/ReactFlightDOMClientBrowser.js new
+295
@@ -0,0 +1,295 @@
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} from 'shared/ReactTypes.js';
11 +
12 +import type {
13 + DebugChannel,
14 + DebugChannelCallback,
15 + FindSourceMapURLCallback,
16 + Response as FlightResponse,
17 +} from 'react-client/src/ReactFlightClient';
18 +
19 +import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
20 +
21 +import {
22 + createResponse,
23 + createStreamState,
24 + getRoot,
25 + reportGlobalError,
26 + processBinaryChunk,
27 + processStringChunk,
28 + close,
29 + injectIntoDevTools,
30 +} from 'react-client/src/ReactFlightClient';
31 +
32 +import {processReply} from 'react-client/src/ReactFlightReplyClient';
33 +
34 +export {
35 + createServerReference,
36 + registerServerReference,
37 +} from 'react-client/src/ReactFlightReplyClient';
38 +
39 +import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
40 +
41 +export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
42 +
43 +export type {TemporaryReferenceSet};
44 +
45 +type CallServerCallback = <A, T>(string, args: A) => Promise<T>;
46 +
47 +export type Options = {
48 + callServer?: CallServerCallback,
49 + debugChannel?: {writable?: WritableStream, readable?: ReadableStream, ...},
50 + temporaryReferences?: TemporaryReferenceSet,
51 + unstable_allowPartialStream?: boolean,
52 + findSourceMapURL?: FindSourceMapURLCallback,
53 + replayConsoleLogs?: boolean,
54 + environmentName?: string,
55 + startTime?: number,
56 + endTime?: number,
57 +};
58 +
59 +function createDebugCallbackFromWritableStream(
60 + debugWritable: WritableStream,
61 +): DebugChannelCallback {
62 + const textEncoder = new TextEncoder();
63 + const writer = debugWritable.getWriter();
64 + return message => {
65 + if (message === '') {
66 + writer.close();
67 + } else {
68 + // Note: It's important that this function doesn't close over the Response object or it can't be GC:ed.
69 + // Therefore, we can't report errors from this write back to the Response object.
70 + if (__DEV__) {
71 + writer.write(textEncoder.encode(message + '\n')).catch(console.error);
72 + }
73 + }
74 + };
75 +}
76 +
77 +function createResponseFromOptions(options: void | Options) {
78 + const debugChannel: void | DebugChannel =
79 + __DEV__ && options && options.debugChannel !== undefined
80 + ? {
81 + hasReadable: options.debugChannel.readable !== undefined,
82 + callback:
83 + options.debugChannel.writable !== undefined
84 + ? createDebugCallbackFromWritableStream(
85 + options.debugChannel.writable,
86 + )
87 + : null,
88 + }
89 + : undefined;
90 +
91 + return createResponse(
92 + null,
93 + null,
94 + null,
95 + options && options.callServer ? options.callServer : undefined,
96 + undefined, // encodeFormAction
97 + undefined, // nonce
98 + options && options.temporaryReferences
99 + ? options.temporaryReferences
100 + : undefined,
101 + options && options.unstable_allowPartialStream
102 + ? options.unstable_allowPartialStream
103 + : false,
104 + __DEV__ && options && options.findSourceMapURL
105 + ? options.findSourceMapURL
106 + : undefined,
107 + __DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
108 + __DEV__ && options && options.environmentName
109 + ? options.environmentName
110 + : undefined,
111 + __DEV__ && options && options.startTime != null
112 + ? options.startTime
113 + : undefined,
114 + __DEV__ && options && options.endTime != null ? options.endTime : undefined,
115 + debugChannel,
116 + );
117 +}
118 +
119 +function startReadingFromUniversalStream(
120 + response: FlightResponse,
121 + stream: ReadableStream,
122 + onDone: () => void,
123 +): void {
124 + // This is the same as startReadingFromStream except this allows WebSocketStreams which
125 + // return ArrayBuffer and string chunks instead of Uint8Array chunks. We could potentially
126 + // always allow streams with variable chunk types.
127 + const streamState = createStreamState(response, stream);
128 + const reader = stream.getReader();
129 + function progress({
130 + done,
131 + value,
132 + }: {
133 + done: boolean,
134 + value: any,
135 + ...
136 + }): void | Promise<void> {
137 + if (done) {
138 + return onDone();
139 + }
140 + if (value instanceof ArrayBuffer) {
141 + // WebSockets can produce ArrayBuffer values in ReadableStreams.
142 + processBinaryChunk(response, streamState, new Uint8Array(value));
143 + } else if (typeof value === 'string') {
144 + // WebSockets can produce string values in ReadableStreams.
145 + processStringChunk(response, streamState, value);
146 + } else {
147 + processBinaryChunk(response, streamState, value);
148 + }
149 + return reader.read().then(progress).catch(error);
150 + }
151 + function error(e: any) {
152 + reportGlobalError(response, e);
153 + }
154 + reader.read().then(progress).catch(error);
155 +}
156 +
157 +function startReadingFromStream(
158 + response: FlightResponse,
159 + stream: ReadableStream,
160 + onDone: () => void,
161 + debugValue: mixed,
162 +): void {
163 + const streamState = createStreamState(response, debugValue);
164 + const reader = stream.getReader();
165 + function progress({
166 + done,
167 + value,
168 + }: {
169 + done: boolean,
170 + value: ?any,
171 + ...
172 + }): void | Promise<void> {
173 + if (done) {
174 + return onDone();
175 + }
176 + const buffer: Uint8Array = (value: any);
177 + processBinaryChunk(response, streamState, buffer);
178 + return reader.read().then(progress).catch(error);
179 + }
180 + function error(e: any) {
181 + reportGlobalError(response, e);
182 + }
183 + reader.read().then(progress).catch(error);
184 +}
185 +function createFromReadableStream<T>(
186 + stream: ReadableStream,
187 + options?: Options,
188 +): Thenable<T> {
189 + const response: FlightResponse = createResponseFromOptions(options);
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 + startReadingFromUniversalStream(
203 + response,
204 + options.debugChannel.readable,
205 + handleDone,
206 + );
207 + startReadingFromStream(response, stream, handleDone, stream);
208 + } else {
209 + startReadingFromStream(
210 + response,
211 + stream,
212 + close.bind(null, response),
213 + stream,
214 + );
215 + }
216 + return getRoot(response);
217 +}
218 +
219 +function createFromFetch<T>(
220 + promiseForResponse: Promise<Response>,
221 + options?: Options,
222 +): Thenable<T> {
223 + const response: FlightResponse = createResponseFromOptions(options);
224 + promiseForResponse.then(
225 + function (r) {
226 + if (
227 + __DEV__ &&
228 + options &&
229 + options.debugChannel &&
230 + options.debugChannel.readable
231 + ) {
232 + let streamDoneCount = 0;
233 + const handleDone = () => {
234 + if (++streamDoneCount === 2) {
235 + close(response);
236 + }
237 + };
238 + startReadingFromUniversalStream(
239 + response,
240 + options.debugChannel.readable,
241 + handleDone,
242 + );
243 + startReadingFromStream(response, (r.body: any), handleDone, r);
244 + } else {
245 + startReadingFromStream(
246 + response,
247 + (r.body: any),
248 + close.bind(null, response),
249 + r,
250 + );
251 + }
252 + },
253 + function (e) {
254 + reportGlobalError(response, e);
255 + },
256 + );
257 + return getRoot(response);
258 +}
259 +
260 +function encodeReply(
261 + value: ReactServerValue,
262 + options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
263 +): Promise<
264 + string | URLSearchParams | FormData,
265 +> /* We don't use URLSearchParams yet but maybe */ {
266 + return new Promise((resolve, reject) => {
267 + const abort = processReply(
268 + value,
269 + '',
270 + options && options.temporaryReferences
271 + ? options.temporaryReferences
272 + : undefined,
273 + resolve,
274 + reject,
275 + );
276 + if (options && options.signal) {
277 + const signal = options.signal;
278 + if (signal.aborted) {
279 + abort((signal: any).reason);
280 + } else {
281 + const listener = () => {
282 + abort((signal: any).reason);
283 + signal.removeEventListener('abort', listener);
284 + };
285 + signal.addEventListener('abort', listener);
286 + }
287 + }
288 + });
289 +}
290 +
291 +export {createFromFetch, createFromReadableStream, encodeReply};
292 +
293 +if (__DEV__) {
294 + injectIntoDevTools();
295 +}
packages/react-flight-server-fb/src/server/ReactFlightDOMServerNode.js new
+379
@@ -0,0 +1,379 @@
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 {Busboy} from 'busboy';
16 +import type {Writable} from 'stream';
17 +import type {Thenable} from 'shared/ReactTypes';
18 +
19 +import type {Duplex} from 'stream';
20 +
21 +import {Readable} from 'stream';
22 +
23 +import {
24 + createRequest,
25 + startWork,
26 + startFlowing,
27 + startFlowingDebug,
28 + stopFlowing,
29 + abort,
30 + resolveDebugMessage,
31 + closeDebugChannel,
32 +} from 'react-server/src/ReactFlightServer';
33 +
34 +import {
35 + createResponse,
36 + reportGlobalError,
37 + close,
38 + resolveField,
39 + resolveFileInfo,
40 + resolveFileChunk,
41 + resolveFileComplete,
42 + getRoot,
43 +} from 'react-server/src/ReactFlightReplyServer';
44 +
45 +import {
46 + decodeAction,
47 + decodeFormState,
48 +} from 'react-server/src/ReactFlightActionServer';
49 +
50 +export {
51 + registerServerReference,
52 + registerClientReference,
53 +} from '../ReactFlightFBReferences';
54 +
55 +// Buffer-based string decoder helpers. The FB server environment does not
56 +// have TextDecoder, so we use Buffer.toString('utf8') instead.
57 +type BufferDecoder = {_pendingBytes: Array<Uint8Array>};
58 +
59 +function createStringDecoder(): BufferDecoder {
60 + return {_pendingBytes: []};
61 +}
62 +
63 +function readPartialStringChunk(
64 + decoder: BufferDecoder,
65 + buffer: Uint8Array,
66 +): string {
67 + return Buffer.from(
68 + buffer.buffer,
69 + buffer.byteOffset,
70 + buffer.byteLength,
71 + ).toString('utf8');
72 +}
73 +
74 +function readFinalStringChunk(
75 + decoder: BufferDecoder,
76 + buffer: Uint8Array,
77 +): string {
78 + return Buffer.from(
79 + buffer.buffer,
80 + buffer.byteOffset,
81 + buffer.byteLength,
82 + ).toString('utf8');
83 +}
84 +
85 +import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
86 +
87 +export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
88 +
89 +export type {TemporaryReferenceSet};
90 +
91 +function createDrainHandler(destination: Destination, request: Request) {
92 + return () => startFlowing(request, destination);
93 +}
94 +
95 +function createCancelHandler(request: Request, reason: string) {
96 + return () => {
97 + stopFlowing(request);
98 + abort(request, new Error(reason));
99 + };
100 +}
101 +
102 +function startReadingFromDebugChannelReadable(
103 + request: Request,
104 + stream: Readable | WebSocket,
105 +): void {
106 + const stringDecoder = createStringDecoder();
107 + let lastWasPartial = false;
108 + let stringBuffer = '';
109 + function onData(chunk: string | Uint8Array) {
110 + if (typeof chunk === 'string') {
111 + if (lastWasPartial) {
112 + stringBuffer += readFinalStringChunk(stringDecoder, new Uint8Array(0));
113 + lastWasPartial = false;
114 + }
115 + stringBuffer += chunk;
116 + } else {
117 + const buffer: Uint8Array = (chunk: any);
118 + stringBuffer += readPartialStringChunk(stringDecoder, buffer);
119 + lastWasPartial = true;
120 + }
121 + const messages = stringBuffer.split('\n');
122 + for (let i = 0; i < messages.length - 1; i++) {
123 + resolveDebugMessage(request, messages[i]);
124 + }
125 + stringBuffer = messages[messages.length - 1];
126 + }
127 + function onError(error: mixed) {
128 + abort(
129 + request,
130 + new Error('Lost connection to the Debug Channel.', {
131 + cause: error,
132 + }),
133 + );
134 + }
135 + function onClose() {
136 + closeDebugChannel(request);
137 + }
138 + if (
139 + // $FlowFixMe[method-unbinding]
140 + typeof stream.addEventListener === 'function' &&
141 + // $FlowFixMe[method-unbinding]
142 + typeof stream.binaryType === 'string'
143 + ) {
144 + const ws: WebSocket = (stream: any);
145 + ws.binaryType = 'arraybuffer';
146 + ws.addEventListener('message', event => {
147 + // $FlowFixMe
148 + onData(event.data);
149 + });
150 + ws.addEventListener('error', event => {
151 + // $FlowFixMe
152 + onError(event.error);
153 + });
154 + ws.addEventListener('close', onClose);
155 + } else {
156 + const readable: Readable = (stream: any);
157 + readable.on('data', onData);
158 + readable.on('error', onError);
159 + readable.on('end', onClose);
160 + }
161 +}
162 +
163 +type Options = {
164 + debugChannel?: Readable | Writable | Duplex | WebSocket,
165 + environmentName?: string | (() => string),
166 + filterStackFrame?: (url: string, functionName: string) => boolean,
167 + onError?: (error: mixed) => void,
168 + identifierPrefix?: string,
169 + temporaryReferences?: TemporaryReferenceSet,
170 + startTime?: number,
171 +};
172 +
173 +type PipeableStream = {
174 + abort(reason: mixed): void,
175 + pipe<T: Writable>(destination: T): T,
176 +};
177 +
178 +function renderToPipeableStream(
179 + model: ReactClientValue,
180 + options?: Options,
181 +): PipeableStream {
182 + const debugChannel = __DEV__ && options ? options.debugChannel : undefined;
183 + const debugChannelReadable: void | Readable | WebSocket =
184 + __DEV__ &&
185 + debugChannel !== undefined &&
186 + // $FlowFixMe[method-unbinding]
187 + (typeof debugChannel.read === 'function' ||
188 + typeof debugChannel.readyState === 'number')
189 + ? (debugChannel: any)
190 + : undefined;
191 + const debugChannelWritable: void | Writable =
192 + __DEV__ && debugChannel !== undefined
193 + ? // $FlowFixMe[method-unbinding]
194 + typeof debugChannel.write === 'function'
195 + ? (debugChannel: any)
196 + : // $FlowFixMe[method-unbinding]
197 + typeof debugChannel.send === 'function'
198 + ? createFakeWritableFromWebSocket((debugChannel: any))
199 + : undefined
200 + : undefined;
201 + const request = createRequest(
202 + model,
203 + null,
204 + options ? options.onError : undefined,
205 + options ? options.identifierPrefix : undefined,
206 + options ? options.temporaryReferences : undefined,
207 + options ? options.startTime : undefined,
208 + __DEV__ && options ? options.environmentName : undefined,
209 + __DEV__ && options ? options.filterStackFrame : undefined,
210 + debugChannelReadable !== undefined,
211 + );
212 + let hasStartedFlowing = false;
213 + startWork(request);
214 + if (debugChannelWritable !== undefined) {
215 + startFlowingDebug(request, debugChannelWritable);
216 + }
217 + if (debugChannelReadable !== undefined) {
218 + startReadingFromDebugChannelReadable(request, debugChannelReadable);
219 + }
220 + return {
221 + pipe<T: Writable>(destination: T): T {
222 + if (hasStartedFlowing) {
223 + throw new Error(
224 + 'React currently only supports piping to one writable stream.',
225 + );
226 + }
227 + hasStartedFlowing = true;
228 + startFlowing(request, destination);
229 + destination.on('drain', createDrainHandler(destination, request));
230 + destination.on(
231 + 'error',
232 + createCancelHandler(
233 + request,
234 + 'The destination stream errored while writing data.',
235 + ),
236 + );
237 + // We don't close until the debug channel closes.
238 + if (!__DEV__ || debugChannelReadable === undefined) {
239 + destination.on(
240 + 'close',
241 + createCancelHandler(request, 'The destination stream closed early.'),
242 + );
243 + }
244 + return destination;
245 + },
246 + abort(reason: mixed) {
247 + abort(request, reason);
248 + },
249 + };
250 +}
251 +
252 +function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable {
253 + return ({
254 + write(chunk: string | Uint8Array) {
255 + webSocket.send((chunk: any));
256 + return true;
257 + },
258 + end() {
259 + webSocket.close();
260 + },
261 + destroy(reason) {
262 + if (typeof reason === 'object' && reason !== null) {
263 + reason = reason.message;
264 + }
265 + if (typeof reason === 'string') {
266 + webSocket.close(1011, reason);
267 + } else {
268 + webSocket.close(1011);
269 + }
270 + },
271 + }: any);
272 +}
273 +
274 +function decodeReplyFromBusboy<T>(
275 + busboyStream: Busboy,
276 + options?: {
277 + temporaryReferences?: TemporaryReferenceSet,
278 + arraySizeLimit?: number,
279 + },
280 +): Thenable<T> {
281 + const response = createResponse(
282 + null,
283 + '',
284 + options ? options.temporaryReferences : undefined,
285 + undefined,
286 + options ? options.arraySizeLimit : undefined,
287 + );
288 + let pendingFiles = 0;
289 + const queuedFields: Array<string> = [];
290 + busboyStream.on('field', (name, value) => {
291 + if (pendingFiles > 0) {
292 + // Because the 'end' event fires two microtasks after the next 'field'
293 + // we would resolve files and fields out of order. To handle this properly
294 + // we queue any fields we receive until the previous file is done.
295 + queuedFields.push(name, value);
296 + } else {
297 + try {
298 + resolveField(response, name, value);
299 + } catch (error) {
300 + busboyStream.destroy(error);
301 + }
302 + }
303 + });
304 + busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
305 + if (encoding.toLowerCase() === 'base64') {
306 + busboyStream.destroy(
307 + new Error(
308 + "React doesn't accept base64 encoded file uploads because we don't expect " +
309 + "form data passed from a browser to ever encode data that way. If that's " +
310 + 'the wrong assumption, we can easily fix it.',
311 + ),
312 + );
313 + return;
314 + }
315 + pendingFiles++;
316 + const file = resolveFileInfo(response, name, filename, mimeType);
317 + value.on('data', chunk => {
318 + resolveFileChunk(response, file, chunk);
319 + });
320 + value.on('end', () => {
321 + try {
322 + resolveFileComplete(response, name, file);
323 + pendingFiles--;
324 + if (pendingFiles === 0) {
325 + // Release any queued fields
326 + for (let i = 0; i < queuedFields.length; i += 2) {
327 + resolveField(response, queuedFields[i], queuedFields[i + 1]);
328 + }
329 + queuedFields.length = 0;
330 + }
331 + } catch (error) {
332 + busboyStream.destroy(error);
333 + }
334 + });
335 + });
336 + busboyStream.on('finish', () => {
337 + close(response);
338 + });
339 + busboyStream.on('error', err => {
340 + reportGlobalError(
341 + response,
342 + // $FlowFixMe[incompatible-call] types Error and mixed are incompatible
343 + err,
344 + );
345 + });
346 + return getRoot(response);
347 +}
348 +
349 +function decodeReply<T>(
350 + body: string | FormData,
351 + options?: {
352 + temporaryReferences?: TemporaryReferenceSet,
353 + arraySizeLimit?: number,
354 + },
355 +): Thenable<T> {
356 + if (typeof body === 'string') {
357 + const form = new FormData();
358 + form.append('0', body);
359 + body = form;
360 + }
361 + const response = createResponse(
362 + null,
363 + '',
364 + options ? options.temporaryReferences : undefined,
365 + body,
366 + options ? options.arraySizeLimit : undefined,
367 + );
368 + const root = getRoot<T>(response);
369 + close(response);
370 + return root;
371 +}
372 +
373 +export {
374 + renderToPipeableStream,
375 + decodeReply,
376 + decodeReplyFromBusboy,
377 + decodeAction,
378 + decodeFormState,
379 +};
packages/react-flight-server-fb/src/server/ReactFlightServerConfigDOMFB.js new
+48
@@ -0,0 +1,48 @@
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 +// Custom implementation of FlightServerConfigDOM.
11 +// Resource hint dispatching is a no-op since we handle resource loading
12 +// through our own runtime.
13 +
14 +// We keep the same type definitions so the Flight server protocol is compatible.
15 +
16 +// prettier-ignore
17 +type TypeMap = {
18 + 'D': string,
19 + 'C': string | [string, string],
20 + 'L': [string, string] | [string, string, any],
21 + 'm': string | [string, any],
22 + 'S': string | [string, string] | [string, string | 0, any],
23 + 'X': string | [string, any],
24 + 'M': string | [string, any],
25 +}
26 +
27 +export type HintCode = $Keys<TypeMap>;
28 +export type HintModel<T: HintCode> = TypeMap[T];
29 +
30 +export type Hints = Set<string>;
31 +
32 +export function createHints(): Hints {
33 + return new Set();
34 +}
35 +
36 +export opaque type FormatContext = number;
37 +
38 +export function createRootFormatContext(): FormatContext {
39 + return 0;
40 +}
41 +
42 +export function getChildFormatContext(
43 + parentContext: FormatContext,
44 + type: string,
45 + props: Object,
46 +): FormatContext {
47 + return parentContext;
48 +}
packages/react-flight-server-fb/src/server/ReactFlightServerConfigFBBundler.js new
+61
@@ -0,0 +1,61 @@
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 +import type {
13 + ClientReference,
14 + ServerReference,
15 +} from '../ReactFlightFBReferences';
16 +
17 +export type {ClientReference, ServerReference};
18 +
19 +export type ClientManifest = null;
20 +
21 +export type ServerReferenceId = string;
22 +
23 +export type ClientReferenceMetadata = ClientReference<any>;
24 +
25 +export type ClientReferenceKey = string;
26 +
27 +export {isClientReference, isServerReference} from '../ReactFlightFBReferences';
28 +
29 +export function getClientReferenceKey(
30 + reference: ClientReference<any>,
31 +): ClientReferenceKey {
32 + return reference.$$id;
33 +}
34 +
35 +export function resolveClientReferenceMetadata<T>(
36 + config: ClientManifest,
37 + clientReference: ClientReference<T>,
38 +): ClientReferenceMetadata {
39 + return clientReference;
40 +}
41 +
42 +export function getServerReferenceId<T>(
43 + config: ClientManifest,
44 + serverReference: ServerReference<T>,
45 +): ServerReferenceId {
46 + return serverReference.$$id;
47 +}
48 +
49 +export function getServerReferenceBoundArguments<T>(
50 + config: ClientManifest,
51 + serverReference: ServerReference<T>,
52 +): null | Array<ReactClientValue> {
53 + return serverReference.$$bound;
54 +}
55 +
56 +export function getServerReferenceLocation<T>(
57 + config: ClientManifest,
58 + serverReference: ServerReference<T>,
59 +): void | Error {
60 + return serverReference.$$location;
61 +}
packages/react-flight-server-fb/src/server/react-flight-dom-server.node.js new
+18
@@ -0,0 +1,18 @@
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 {
11 + renderToPipeableStream,
12 + decodeReply,
13 + decodeAction,
14 + decodeFormState,
15 + registerServerReference,
16 + registerClientReference,
17 + createTemporaryReferenceSet,
18 +} from './ReactFlightDOMServerNode';
packages/react-server/src/forks/ReactFlightServerConfig.dom-browser-fb.js new
+26
@@ -0,0 +1,26 @@
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 {Request} from 'react-server/src/ReactFlightServer';
11 +import type {ReactComponentInfo} from 'shared/ReactTypes';
12 +
13 +export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigFBBundler';
14 +export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigDOMFB';
15 +
16 +export const supportsRequestStorage = false;
17 +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
18 +
19 +export const supportsComponentStorage = false;
20 +export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
21 + (null: any);
22 +
23 +export * from '../ReactFlightServerConfigDebugNoop';
24 +
25 +export * from '../ReactFlightStackConfigV8';
26 +export * from '../ReactServerConsoleConfigBrowser';
packages/react-server/src/forks/ReactFlightServerConfig.dom-node-fb.js new
+26
@@ -0,0 +1,26 @@
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 {Request} from 'react-server/src/ReactFlightServer';
11 +import type {ReactComponentInfo} from 'shared/ReactTypes';
12 +
13 +export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigFBBundler';
14 +export * from 'react-flight-server-fb/src/server/ReactFlightServerConfigDOMFB';
15 +
16 +export const supportsRequestStorage = false;
17 +export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
18 +
19 +export const supportsComponentStorage = false;
20 +export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
21 + (null: any);
22 +
23 +export * from '../ReactFlightServerConfigDebugNoop';
24 +
25 +export * from '../ReactFlightStackConfigV8';
26 +export * from '../ReactServerConsoleConfigServer';
packages/react-server/src/forks/ReactServerStreamConfig.dom-browser-fb.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 'react-flight-server-fb/src/ReactServerStreamConfigFB';
packages/react-server/src/forks/ReactServerStreamConfig.dom-node-fb.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 'react-flight-server-fb/src/ReactServerStreamConfigFB';
scripts/rollup/bundles.js
+23
@@ -756,6 +756,29 @@ const bundles = [
756 externals: ['acorn'],
757 },
758
759 + /******* React Flight Server FB Server *******/
760 + {
761 + bundleTypes: [FB_WWW_DEV, FB_WWW_PROD],
762 + moduleType: RENDERER,
763 + entry: 'react-flight-server-fb/src/server/react-flight-dom-server.node',
764 + global: 'ReactFlightServer',
765 + condition: 'react-server',
766 + minifyWithProdErrorCodes: false,
767 + wrapWithModuleBoundaries: false,
768 + externals: ['react', 'react-dom', 'stream'],
769 + },
770 +
771 + /******* React Flight Client FB *******/
772 + {
773 + bundleTypes: [FB_WWW_DEV, FB_WWW_PROD],
774 + moduleType: RENDERER,
775 + entry: 'react-flight-server-fb/client.browser',
776 + global: 'ReactFlightClient',
777 + minifyWithProdErrorCodes: false,
778 + wrapWithModuleBoundaries: false,
779 + externals: ['react', 'react-dom'],
780 + },
781 +
782 /******* React Server DOM Unbundled Server *******/
783 {
784 bundleTypes: [NODE_DEV, NODE_PROD],
scripts/shared/inlinedHostConfigs.js
+64
@@ -326,6 +326,36 @@ module.exports = [
326 isFlowTyped: true,
327 isServerSupported: true,
328 },
329 + {
330 + shortName: 'dom-browser-fb',
331 + entryPoints: ['react-flight-server-fb/client.browser'],
332 + paths: [
333 + 'react-dom',
334 + 'react-dom/src/ReactDOMReactServer.js',
335 + 'react-dom/client',
336 + 'react-dom/profiling',
337 + 'react-dom/server',
338 + 'react-dom/server.node',
339 + 'react-dom-bindings',
340 + 'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
341 + 'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
342 + 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
343 + 'react-flight-server-fb',
344 + 'react-flight-server-fb/client',
345 + 'react-flight-server-fb/client.browser',
346 + 'react-flight-server-fb/src/client/ReactFlightDOMClientBrowser.js', // react-flight-server-fb/client.browser
347 + 'react-flight-server-fb/src/server/react-flight-dom-server.node',
348 + 'react-flight-server-fb/src/server/ReactFlightDOMServerNode.js', // react-flight-server-fb/src/server/react-flight-dom-server.node
349 + 'react-devtools',
350 + 'react-devtools-core',
351 + 'react-devtools-shell',
352 + 'react-devtools-shared',
353 + 'shared/ReactDOMSharedInternals',
354 + 'react-server/src/ReactFlightServerConfigDebugNoop.js',
355 + ],
356 + isFlowTyped: true,
357 + isServerSupported: true,
358 + },
359 {
360 shortName: 'dom-browser-turbopack',
361 entryPoints: [
@@ -564,6 +594,40 @@ module.exports = [
594 isFlowTyped: true,
595 isServerSupported: true,
596 },
597 + {
598 + shortName: 'dom-node-fb',
599 + entryPoints: [
600 + 'react-flight-server-fb/src/server/react-flight-dom-server.node',
601 + ],
602 + paths: [
603 + 'react-dom',
604 + 'react-dom/src/ReactDOMReactServer.js',
605 + 'react-dom-bindings',
606 + 'react-dom/client',
607 + 'react-dom/profiling',
608 + 'react-dom/server',
609 + 'react-dom/server.node',
610 + 'react-dom/static',
611 + 'react-dom/static.node',
612 + 'react-dom/src/server/react-dom-server.node',
613 + 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
614 + 'react-dom/src/server/ReactDOMFizzStaticNode.js',
615 + 'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
616 + 'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
617 + 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
618 + 'react-flight-server-fb',
619 + 'react-flight-server-fb/src/server/react-flight-dom-server.node',
620 + 'react-flight-server-fb/src/server/ReactFlightDOMServerNode.js', // react-flight-server-fb/src/server/react-flight-dom-server.node
621 + 'react-devtools',
622 + 'react-devtools-core',
623 + 'react-devtools-shell',
624 + 'react-devtools-shared',
625 + 'shared/ReactDOMSharedInternals',
626 + 'react-server/src/ReactFlightServerConfigDebugNoop.js',
627 + ],
628 + isFlowTyped: true,
629 + isServerSupported: true,
630 + },
631 {
632 shortName: 'dom-legacy',
633 entryPoints: [