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

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
This commit is contained in:
Sebastian "Sebbie" Silbermann
2025-12-05 03:59:21 +01:00
committed by GitHub
parent 3016ff87d8
commit 378973b387
47 changed files with 2908 additions and 137 deletions

View File

@@ -331,6 +331,7 @@ module.exports = {
'packages/react-server-dom-turbopack/**/*.js',
'packages/react-server-dom-parcel/**/*.js',
'packages/react-server-dom-fb/**/*.js',
'packages/react-server-dom-unbundled/**/*.js',
'packages/react-test-renderer/**/*.js',
'packages/react-debug-tools/**/*.js',
'packages/react-devtools-extensions/**/*.js',

View File

@@ -41,7 +41,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
@@ -55,10 +55,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- name: Save cache
@@ -67,7 +65,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
runtime_compiler_node_modules_cache:
name: Cache Runtime, Compiler node_modules
@@ -82,7 +80,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
lookup-only: true
- uses: actions/setup-node@v4
if: steps.node_modules.outputs.cache-hit != 'true'
@@ -98,10 +96,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd compiler install --frozen-lockfile
@@ -112,7 +108,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# ----- FLOW -----
discover_flow_inline_configs:
@@ -154,10 +150,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -184,10 +178,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -216,7 +208,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -274,10 +266,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -306,7 +296,7 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
- name: Install runtime dependencies
run: yarn install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
@@ -349,10 +339,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -440,10 +428,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
restore-keys: |
runtime-and-compiler-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-and-compiler-node_modules-v6-
key: runtime-and-compiler-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -483,10 +469,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -548,10 +532,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -588,10 +570,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -740,10 +720,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile
@@ -802,10 +780,8 @@ jobs:
with:
path: |
**/node_modules
key: runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
runtime-node_modules-v6-${{ runner.arch }}-${{ runner.os }}-
runtime-node_modules-v6-
key: runtime-node_modules-v7-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
- name: Ensure clean build directory
run: rm -rf build
- run: yarn install --frozen-lockfile

View File

@@ -3,7 +3,7 @@ import {
load as reactLoad,
getSource as getSourceImpl,
transformSource as reactTransformSource,
} from 'react-server-dom-webpack/node-loader';
} from 'react-server-dom-unbundled/node-loader';
export {resolve};

View File

@@ -36,7 +36,7 @@ const http = require('http');
const React = require('react');
const {renderToPipeableStream} = require('react-dom/server');
const {createFromNodeStream} = require('react-server-dom-webpack/client');
const {createFromNodeStream} = require('react-server-dom-unbundled/client');
const {PassThrough} = require('stream');
const app = express();

View File

@@ -5,7 +5,8 @@
const path = require('path');
const url = require('url');
const register = require('react-server-dom-webpack/node-register');
const register = require('react-server-dom-unbundled/node-register');
// TODO: This seems to have no effect anymore. Remove?
register();
const babelRegister = require('@babel/register');
@@ -76,7 +77,7 @@ function getDebugChannel(req) {
async function renderApp(res, returnValue, formState, noCache, debugChannel) {
const {renderToPipeableStream} = await import(
'react-server-dom-webpack/server'
'react-server-dom-unbundled/server'
);
// const m = require('../src/App.js');
const m = await import('../src/App.js');
@@ -134,7 +135,7 @@ async function renderApp(res, returnValue, formState, noCache, debugChannel) {
async function prerenderApp(res, returnValue, formState, noCache) {
const {prerenderToNodeStream} = await import(
'react-server-dom-webpack/static'
'react-server-dom-unbundled/static'
);
// const m = require('../src/App.js');
const m = await import('../src/App.js');
@@ -202,7 +203,7 @@ app.get('/', async function (req, res) {
app.post('/', bodyParser.text(), async function (req, res) {
const noCache = req.headers['cache-control'] === 'no-cache';
const {decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState} =
await import('react-server-dom-webpack/server');
await import('react-server-dom-unbundled/server');
const serverReference = req.get('rsc-action');
if (serverReference) {
// This is the client-side case

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import {renderToReadableStream} from 'react-server-dom-webpack/server';
import {renderToReadableStream} from 'react-server-dom-unbundled/server';
import {createFromReadableStream} from 'react-server-dom-webpack/client';
import {PassThrough, Readable} from 'stream';

View File

@@ -126,7 +126,7 @@
"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",
"build-for-devtools-dev": "yarn build-for-devtools --type=NODE_DEV",
"build-for-devtools-prod": "yarn build-for-devtools --type=NODE_PROD",
"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",
"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",
"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",
"flow-typed-install": "yarn flow-typed install --skip --skipFlowRestart --ignore-deps=dev",
"linc": "node ./scripts/tasks/linc.js",

View File

@@ -6,13 +6,13 @@
*
* @flow
*/
export {default as rendererVersion} from 'shared/ReactVersion';
export const rendererPackageName = 'react-server-dom-webpack';
export const rendererPackageName = 'react-server-dom-unbundled';
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode';
export * from 'react-server-dom-unbundled/src/client/ReactFlightClientConfigTargetNodeServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;

View File

@@ -6,13 +6,13 @@
*
* @flow
*/
export {default as rendererVersion} from 'shared/ReactVersion';
export const rendererPackageName = 'react-server-dom-webpack';
export * from 'react-client/src/ReactFlightClientStreamConfigNode';
export * from 'react-client/src/ReactClientConsoleConfigServer';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer';
export * from 'react-server-dom-webpack/src/client/ReactFlightClientConfigTargetWebpackServer';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export const usedWithSSR = true;

View File

@@ -0,0 +1,5 @@
# react-server-dom-unbundled
Test-only React Flight bindings for DOM using Node.js.
This only exists for internal testing.

View File

@@ -7,4 +7,4 @@
* @flow
*/
export * from './src/client/react-flight-dom-client.node.unbundled';
export * from './src/client/react-flight-dom-client.node';

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from '../src/ReactFlightUnbundledNodeLoader.js';

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
throw new Error('Use react-server-dom-webpack/client instead.');

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
module.exports = require('./src/ReactFlightUnbundledNodeRegister');

View File

@@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-server-dom-unbundled-client.node.production.js');
} else {
module.exports = require('./cjs/react-server-dom-unbundled-client.node.development.js');
}

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
throw new Error('Use react-server-dom-unbundled/client instead.');

View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./cjs/react-server-dom-unbundled-node-register.js');

View File

@@ -0,0 +1,6 @@
'use strict';
throw new Error(
'The React Server Writer cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.'
);

View File

@@ -0,0 +1,20 @@
'use strict';
var s;
if (process.env.NODE_ENV === 'production') {
s = require('./cjs/react-server-dom-unbundled-server.node.production.js');
} else {
s = require('./cjs/react-server-dom-unbundled-server.node.development.js');
}
exports.renderToReadableStream = s.renderToReadableStream;
exports.renderToPipeableStream = s.renderToPipeableStream;
exports.decodeReply = s.decodeReply;
exports.decodeReplyFromBusboy = s.decodeReplyFromBusboy;
exports.decodeReplyFromAsyncIterable = s.decodeReplyFromAsyncIterable;
exports.decodeAction = s.decodeAction;
exports.decodeFormState = s.decodeFormState;
exports.registerServerReference = s.registerServerReference;
exports.registerClientReference = s.registerClientReference;
exports.createClientModuleProxy = s.createClientModuleProxy;
exports.createTemporaryReferenceSet = s.createTemporaryReferenceSet;

View File

@@ -0,0 +1,6 @@
'use strict';
throw new Error(
'The React Server Writer cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.'
);

View File

@@ -0,0 +1,11 @@
'use strict';
var s;
if (process.env.NODE_ENV === 'production') {
s = require('./cjs/react-server-dom-unbundled-server.node.production.js');
} else {
s = require('./cjs/react-server-dom-unbundled-server.node.development.js');
}
exports.prerender = s.prerender;
exports.prerenderToNodeStream = s.prerenderToNodeStream;

View File

@@ -0,0 +1,46 @@
{
"name": "react-server-dom-unbundled",
"description": "React Server Components bindings for DOM using Node.js. This only exists for internal testing.",
"version": "0.0.0",
"private": true,
"files": [
"LICENSE",
"README.md",
"index.js",
"client.js",
"server.js",
"server.node.js",
"static.js",
"static.node.js",
"node-register.js",
"cjs/",
"esm/"
],
"exports": {
".": "./index.js",
"./client": "./client.js",
"./server": {
"react-server": "./server.node.js",
"default": "./server.js"
},
"./server.node": "./server.node.js",
"./static": {
"react-server": "./static.node.js",
"default": "./static.js"
},
"./static.node": "./static.node.js",
"./node-loader": "./esm/react-server-dom-unbundled-node-loader.production.js",
"./node-register": "./node-register.js",
"./src/*": "./src/*.js",
"./package.json": "./package.json"
},
"main": "index.js",
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"dependencies": {
"acorn-loose": "^8.3.0",
"webpack-sources": "^3.2.0"
}
}

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
throw new Error(
'The React Server cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.',
);

View File

@@ -19,4 +19,4 @@ export {
registerClientReference,
createClientModuleProxy,
createTemporaryReferenceSet,
} from './src/server/react-flight-dom-server.node.unbundled';
} from './src/server/react-flight-dom-server.node';

View File

@@ -0,0 +1,804 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import * as acorn from 'acorn-loose';
import readMappings from 'webpack-sources/lib/helpers/readMappings.js';
import createMappingsSerializer from 'webpack-sources/lib/helpers/createMappingsSerializer.js';
type ResolveContext = {
conditions: Array<string>,
parentURL: string | void,
};
type ResolveFunction = (
string,
ResolveContext,
ResolveFunction,
) => {url: string} | Promise<{url: string}>;
type GetSourceContext = {
format: string,
};
type GetSourceFunction = (
string,
GetSourceContext,
GetSourceFunction,
) => Promise<{source: Source}>;
type TransformSourceContext = {
format: string,
url: string,
};
type TransformSourceFunction = (
Source,
TransformSourceContext,
TransformSourceFunction,
) => Promise<{source: Source}>;
type LoadContext = {
conditions: Array<string>,
format: string | null | void,
importAssertions: Object,
};
type LoadFunction = (
string,
LoadContext,
LoadFunction,
) => Promise<{format: string, shortCircuit?: boolean, source: Source}>;
type Source = string | ArrayBuffer | Uint8Array;
let warnedAboutConditionsFlag = false;
let stashedGetSource: null | GetSourceFunction = null;
let stashedResolve: null | ResolveFunction = null;
export async function resolve(
specifier: string,
context: ResolveContext,
defaultResolve: ResolveFunction,
): Promise<{url: string}> {
// We stash this in case we end up needing to resolve export * statements later.
stashedResolve = defaultResolve;
if (!context.conditions.includes('react-server')) {
context = {
...context,
conditions: [...context.conditions, 'react-server'],
};
if (!warnedAboutConditionsFlag) {
warnedAboutConditionsFlag = true;
// eslint-disable-next-line react-internal/no-production-logging
console.warn(
'You did not run Node.js with the `--conditions react-server` flag. ' +
'Any "react-server" override will only work with ESM imports.',
);
}
}
return await defaultResolve(specifier, context, defaultResolve);
}
export async function getSource(
url: string,
context: GetSourceContext,
defaultGetSource: GetSourceFunction,
): Promise<{source: Source}> {
// We stash this in case we end up needing to resolve export * statements later.
stashedGetSource = defaultGetSource;
return defaultGetSource(url, context, defaultGetSource);
}
type ExportedEntry = {
localName: string,
exportedName: string,
type: null | string,
loc: {
start: {line: number, column: number},
end: {line: number, column: number},
},
originalLine: number,
originalColumn: number,
originalSource: number,
nameIndex: number,
};
function addExportedEntry(
exportedEntries: Array<ExportedEntry>,
localNames: Set<string>,
localName: string,
exportedName: string,
type: null | 'function',
loc: {
start: {line: number, column: number},
end: {line: number, column: number},
},
) {
if (localNames.has(localName)) {
// If the same local name is exported more than once, we only need one of the names.
return;
}
exportedEntries.push({
localName,
exportedName,
type,
loc,
originalLine: -1,
originalColumn: -1,
originalSource: -1,
nameIndex: -1,
});
}
function addLocalExportedNames(
exportedEntries: Array<ExportedEntry>,
localNames: Set<string>,
node: any,
) {
switch (node.type) {
case 'Identifier':
addExportedEntry(
exportedEntries,
localNames,
node.name,
node.name,
null,
node.loc,
);
return;
case 'ObjectPattern':
for (let i = 0; i < node.properties.length; i++)
addLocalExportedNames(exportedEntries, localNames, node.properties[i]);
return;
case 'ArrayPattern':
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (element)
addLocalExportedNames(exportedEntries, localNames, element);
}
return;
case 'Property':
addLocalExportedNames(exportedEntries, localNames, node.value);
return;
case 'AssignmentPattern':
addLocalExportedNames(exportedEntries, localNames, node.left);
return;
case 'RestElement':
addLocalExportedNames(exportedEntries, localNames, node.argument);
return;
case 'ParenthesizedExpression':
addLocalExportedNames(exportedEntries, localNames, node.expression);
return;
}
}
function transformServerModule(
source: string,
program: any,
url: string,
sourceMap: any,
loader: LoadFunction,
): string {
const body = program.body;
// This entry list needs to be in source location order.
const exportedEntries: Array<ExportedEntry> = [];
// Dedupe set.
const localNames: Set<string> = new Set();
for (let i = 0; i < body.length; i++) {
const node = body[i];
switch (node.type) {
case 'ExportAllDeclaration':
// If export * is used, the other file needs to explicitly opt into "use server" too.
break;
case 'ExportDefaultDeclaration':
if (node.declaration.type === 'Identifier') {
addExportedEntry(
exportedEntries,
localNames,
node.declaration.name,
'default',
null,
node.declaration.loc,
);
} else if (node.declaration.type === 'FunctionDeclaration') {
if (node.declaration.id) {
addExportedEntry(
exportedEntries,
localNames,
node.declaration.id.name,
'default',
'function',
node.declaration.id.loc,
);
} else {
// TODO: This needs to be rewritten inline because it doesn't have a local name.
}
}
continue;
case 'ExportNamedDeclaration':
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
const declarations = node.declaration.declarations;
for (let j = 0; j < declarations.length; j++) {
addLocalExportedNames(
exportedEntries,
localNames,
declarations[j].id,
);
}
} else {
const name = node.declaration.id.name;
addExportedEntry(
exportedEntries,
localNames,
name,
name,
node.declaration.type === 'FunctionDeclaration'
? 'function'
: null,
node.declaration.id.loc,
);
}
}
if (node.specifiers) {
const specifiers = node.specifiers;
for (let j = 0; j < specifiers.length; j++) {
const specifier = specifiers[j];
addExportedEntry(
exportedEntries,
localNames,
specifier.local.name,
specifier.exported.name,
null,
specifier.local.loc,
);
}
}
continue;
}
}
let mappings =
sourceMap && typeof sourceMap.mappings === 'string'
? sourceMap.mappings
: '';
let newSrc = source;
if (exportedEntries.length > 0) {
let lastSourceIndex = 0;
let lastOriginalLine = 0;
let lastOriginalColumn = 0;
let lastNameIndex = 0;
let sourceLineCount = 0;
let lastMappedLine = 0;
if (sourceMap) {
// We iterate source mapping entries and our matched exports in parallel to source map
// them to their original location.
let nextEntryIdx = 0;
let nextEntryLine = exportedEntries[nextEntryIdx].loc.start.line;
let nextEntryColumn = exportedEntries[nextEntryIdx].loc.start.column;
readMappings(
mappings,
(
generatedLine: number,
generatedColumn: number,
sourceIndex: number,
originalLine: number,
originalColumn: number,
nameIndex: number,
) => {
if (
generatedLine > nextEntryLine ||
(generatedLine === nextEntryLine &&
generatedColumn > nextEntryColumn)
) {
// We're past the entry which means that the best match we have is the previous entry.
if (lastMappedLine === nextEntryLine) {
// Match
exportedEntries[nextEntryIdx].originalLine = lastOriginalLine;
exportedEntries[nextEntryIdx].originalColumn = lastOriginalColumn;
exportedEntries[nextEntryIdx].originalSource = lastSourceIndex;
exportedEntries[nextEntryIdx].nameIndex = lastNameIndex;
} else {
// Skip if we didn't have any mappings on the exported line.
}
nextEntryIdx++;
if (nextEntryIdx < exportedEntries.length) {
nextEntryLine = exportedEntries[nextEntryIdx].loc.start.line;
nextEntryColumn = exportedEntries[nextEntryIdx].loc.start.column;
} else {
nextEntryLine = -1;
nextEntryColumn = -1;
}
}
lastMappedLine = generatedLine;
if (sourceIndex > -1) {
lastSourceIndex = sourceIndex;
}
if (originalLine > -1) {
lastOriginalLine = originalLine;
}
if (originalColumn > -1) {
lastOriginalColumn = originalColumn;
}
if (nameIndex > -1) {
lastNameIndex = nameIndex;
}
},
);
if (nextEntryIdx < exportedEntries.length) {
if (lastMappedLine === nextEntryLine) {
// Match
exportedEntries[nextEntryIdx].originalLine = lastOriginalLine;
exportedEntries[nextEntryIdx].originalColumn = lastOriginalColumn;
exportedEntries[nextEntryIdx].originalSource = lastSourceIndex;
exportedEntries[nextEntryIdx].nameIndex = lastNameIndex;
}
}
for (
let lastIdx = mappings.length - 1;
lastIdx >= 0 && mappings[lastIdx] === ';';
lastIdx--
) {
// If the last mapped lines don't contain any segments, we don't get a callback from readMappings
// so we need to pad the number of mapped lines, with one for each empty line.
lastMappedLine++;
}
sourceLineCount = program.loc.end.line;
if (sourceLineCount < lastMappedLine) {
throw new Error(
'The source map has more mappings than there are lines.',
);
}
// If the original source string had more lines than there are mappings in the source map.
// Add some extra padding of unmapped lines so that any lines that we add line up.
for (
let extraLines = sourceLineCount - lastMappedLine;
extraLines > 0;
extraLines--
) {
mappings += ';';
}
} else {
// If a file doesn't have a source map then we generate a blank source map that just
// contains the original content and segments pointing to the original lines.
sourceLineCount = 1;
let idx = -1;
while ((idx = source.indexOf('\n', idx + 1)) !== -1) {
sourceLineCount++;
}
mappings = 'AAAA' + ';AACA'.repeat(sourceLineCount - 1);
sourceMap = {
version: 3,
sources: [url],
sourcesContent: [source],
mappings: mappings,
sourceRoot: '',
};
lastSourceIndex = 0;
lastOriginalLine = sourceLineCount;
lastOriginalColumn = 0;
lastNameIndex = -1;
lastMappedLine = sourceLineCount;
for (let i = 0; i < exportedEntries.length; i++) {
// Point each entry to original location.
const entry = exportedEntries[i];
entry.originalSource = 0;
entry.originalLine = entry.loc.start.line;
// We use column zero since we do the short-hand line-only source maps above.
entry.originalColumn = 0; // entry.loc.start.column;
}
}
newSrc += '\n\n;';
newSrc +=
'import {registerServerReference} from "react-server-dom-webpack/server";\n';
if (mappings) {
mappings += ';;';
}
const createMapping = createMappingsSerializer();
// Create an empty mapping pointing to where we last left off to reset the counters.
let generatedLine = 1;
createMapping(
generatedLine,
0,
lastSourceIndex,
lastOriginalLine,
lastOriginalColumn,
lastNameIndex,
);
for (let i = 0; i < exportedEntries.length; i++) {
const entry = exportedEntries[i];
generatedLine++;
if (entry.type !== 'function') {
// We first check if the export is a function and if so annotate it.
newSrc += 'if (typeof ' + entry.localName + ' === "function") ';
}
newSrc += 'registerServerReference(' + entry.localName + ',';
newSrc += JSON.stringify(url) + ',';
newSrc += JSON.stringify(entry.exportedName) + ');\n';
mappings += createMapping(
generatedLine,
0,
entry.originalSource,
entry.originalLine,
entry.originalColumn,
entry.nameIndex,
);
}
}
if (sourceMap) {
// Override with an new mappings and serialize an inline source map.
sourceMap.mappings = mappings;
newSrc +=
'//# sourceMappingURL=data:application/json;charset=utf-8;base64,' +
Buffer.from(JSON.stringify(sourceMap)).toString('base64');
}
return newSrc;
}
function addExportNames(names: Array<string>, node: any) {
switch (node.type) {
case 'Identifier':
names.push(node.name);
return;
case 'ObjectPattern':
for (let i = 0; i < node.properties.length; i++)
addExportNames(names, node.properties[i]);
return;
case 'ArrayPattern':
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (element) addExportNames(names, element);
}
return;
case 'Property':
addExportNames(names, node.value);
return;
case 'AssignmentPattern':
addExportNames(names, node.left);
return;
case 'RestElement':
addExportNames(names, node.argument);
return;
case 'ParenthesizedExpression':
addExportNames(names, node.expression);
return;
}
}
function resolveClientImport(
specifier: string,
parentURL: string,
): {url: string} | Promise<{url: string}> {
// Resolve an import specifier as if it was loaded by the client. This doesn't use
// the overrides that this loader does but instead reverts to the default.
// This resolution algorithm will not necessarily have the same configuration
// as the actual client loader. It should mostly work and if it doesn't you can
// always convert to explicit exported names instead.
const conditions = ['node', 'import'];
if (stashedResolve === null) {
throw new Error(
'Expected resolve to have been called before transformSource',
);
}
return stashedResolve(specifier, {conditions, parentURL}, stashedResolve);
}
async function parseExportNamesInto(
body: any,
names: Array<string>,
parentURL: string,
loader: LoadFunction,
): Promise<void> {
for (let i = 0; i < body.length; i++) {
const node = body[i];
switch (node.type) {
case 'ExportAllDeclaration':
if (node.exported) {
addExportNames(names, node.exported);
continue;
} else {
const {url} = await resolveClientImport(node.source.value, parentURL);
const {source} = await loader(
url,
{format: 'module', conditions: [], importAssertions: {}},
loader,
);
if (typeof source !== 'string') {
throw new Error('Expected the transformed source to be a string.');
}
let childBody;
try {
childBody = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
}).body;
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
continue;
}
await parseExportNamesInto(childBody, names, url, loader);
continue;
}
case 'ExportDefaultDeclaration':
names.push('default');
continue;
case 'ExportNamedDeclaration':
if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') {
const declarations = node.declaration.declarations;
for (let j = 0; j < declarations.length; j++) {
addExportNames(names, declarations[j].id);
}
} else {
addExportNames(names, node.declaration.id);
}
}
if (node.specifiers) {
const specifiers = node.specifiers;
for (let j = 0; j < specifiers.length; j++) {
addExportNames(names, specifiers[j].exported);
}
}
continue;
}
}
}
async function transformClientModule(
program: any,
url: string,
sourceMap: any,
loader: LoadFunction,
): Promise<string> {
const body = program.body;
const names: Array<string> = [];
await parseExportNamesInto(body, names, url, loader);
if (names.length === 0) {
return '';
}
let newSrc =
'import {registerClientReference} from "react-server-dom-webpack/server";\n';
for (let i = 0; i < names.length; i++) {
const name = names[i];
if (name === 'default') {
newSrc += 'export default ';
newSrc += 'registerClientReference(function() {';
newSrc +=
'throw new Error(' +
JSON.stringify(
`Attempted to call the default export of ${url} from the server ` +
`but it's on the client. It's not possible to invoke a client function from ` +
`the server, it can only be rendered as a Component or passed to props of a ` +
`Client Component.`,
) +
');';
} else {
newSrc += 'export const ' + name + ' = ';
newSrc += 'registerClientReference(function() {';
newSrc +=
'throw new Error(' +
JSON.stringify(
`Attempted to call ${name}() from the server but ${name} is on the client. ` +
`It's not possible to invoke a client function from the server, it can ` +
`only be rendered as a Component or passed to props of a Client Component.`,
) +
');';
}
newSrc += '},';
newSrc += JSON.stringify(url) + ',';
newSrc += JSON.stringify(name) + ');\n';
}
// TODO: Generate source maps for Client Reference functions so they can point to their
// original locations.
return newSrc;
}
async function loadClientImport(
url: string,
defaultTransformSource: TransformSourceFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
if (stashedGetSource === null) {
throw new Error(
'Expected getSource to have been called before transformSource',
);
}
// TODO: Validate that this is another module by calling getFormat.
const {source} = await stashedGetSource(
url,
{format: 'module'},
stashedGetSource,
);
const result = await defaultTransformSource(
source,
{format: 'module', url},
defaultTransformSource,
);
return {format: 'module', source: result.source};
}
async function transformModuleIfNeeded(
source: string,
url: string,
loader: LoadFunction,
): Promise<string> {
// Do a quick check for the exact string. If it doesn't exist, don't
// bother parsing.
if (
source.indexOf('use client') === -1 &&
source.indexOf('use server') === -1
) {
return source;
}
let sourceMappingURL = null;
let sourceMappingStart = 0;
let sourceMappingEnd = 0;
let sourceMappingLines = 0;
let program;
try {
program = acorn.parse(source, {
ecmaVersion: '2024',
sourceType: 'module',
locations: true,
onComment(
block: boolean,
text: string,
start: number,
end: number,
startLoc: {line: number, column: number},
endLoc: {line: number, column: number},
) {
if (
text.startsWith('# sourceMappingURL=') ||
text.startsWith('@ sourceMappingURL=')
) {
sourceMappingURL = text.slice(19);
sourceMappingStart = start;
sourceMappingEnd = end;
sourceMappingLines = endLoc.line - startLoc.line;
}
},
});
} catch (x) {
// eslint-disable-next-line react-internal/no-production-logging
console.error('Error parsing %s %s', url, x.message);
return source;
}
let useClient = false;
let useServer = false;
const body = program.body;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type !== 'ExpressionStatement' || !node.directive) {
break;
}
if (node.directive === 'use client') {
useClient = true;
}
if (node.directive === 'use server') {
useServer = true;
}
}
if (!useClient && !useServer) {
return source;
}
if (useClient && useServer) {
throw new Error(
'Cannot have both "use client" and "use server" directives in the same file.',
);
}
let sourceMap = null;
if (sourceMappingURL) {
const sourceMapResult = await loader(
sourceMappingURL,
// $FlowFixMe
{
format: 'json',
conditions: [],
importAssertions: {type: 'json'},
importAttributes: {type: 'json'},
},
loader,
);
const sourceMapString =
typeof sourceMapResult.source === 'string'
? sourceMapResult.source
: // $FlowFixMe
sourceMapResult.source.toString('utf8');
sourceMap = JSON.parse(sourceMapString);
// Strip the source mapping comment. We'll re-add it below if needed.
source =
source.slice(0, sourceMappingStart) +
'\n'.repeat(sourceMappingLines) +
source.slice(sourceMappingEnd);
}
if (useClient) {
return transformClientModule(program, url, sourceMap, loader);
}
return transformServerModule(source, program, url, sourceMap, loader);
}
export async function transformSource(
source: Source,
context: TransformSourceContext,
defaultTransformSource: TransformSourceFunction,
): Promise<{source: Source}> {
const transformed = await defaultTransformSource(
source,
context,
defaultTransformSource,
);
if (context.format === 'module') {
const transformedSource = transformed.source;
if (typeof transformedSource !== 'string') {
throw new Error('Expected source to have been transformed to a string.');
}
const newSrc = await transformModuleIfNeeded(
transformedSource,
context.url,
(url: string, ctx: LoadContext, defaultLoad: LoadFunction) => {
return loadClientImport(url, defaultTransformSource);
},
);
return {source: newSrc};
}
return transformed;
}
export async function load(
url: string,
context: LoadContext,
defaultLoad: LoadFunction,
): Promise<{format: string, shortCircuit?: boolean, source: Source}> {
const result = await defaultLoad(url, context, defaultLoad);
if (result.format === 'module') {
if (typeof result.source !== 'string') {
throw new Error('Expected source to have been loaded into a string.');
}
const newSrc = await transformModuleIfNeeded(
result.source,
url,
defaultLoad,
);
return {format: 'module', source: newSrc};
}
return result;
}

View File

@@ -0,0 +1,109 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
const acorn = require('acorn-loose');
const url = require('url');
const Module = require('module');
module.exports = function register() {
const Server: any = require('react-server-dom-unbundled/server');
const registerServerReference = Server.registerServerReference;
const createClientModuleProxy = Server.createClientModuleProxy;
// $FlowFixMe[prop-missing] found when upgrading Flow
const originalCompile = Module.prototype._compile;
// $FlowFixMe[prop-missing] found when upgrading Flow
Module.prototype._compile = function (
this: any,
content: string,
filename: string,
): void {
// Do a quick check for the exact string. If it doesn't exist, don't
// bother parsing.
if (
content.indexOf('use client') === -1 &&
content.indexOf('use server') === -1
) {
return originalCompile.apply(this, arguments);
}
let body;
try {
body = acorn.parse(content, {
ecmaVersion: '2024',
sourceType: 'source',
}).body;
} catch (x) {
console['error']('Error parsing %s %s', url, x.message);
return originalCompile.apply(this, arguments);
}
let useClient = false;
let useServer = false;
for (let i = 0; i < body.length; i++) {
const node = body[i];
if (node.type !== 'ExpressionStatement' || !node.directive) {
break;
}
if (node.directive === 'use client') {
useClient = true;
}
if (node.directive === 'use server') {
useServer = true;
}
}
if (!useClient && !useServer) {
return originalCompile.apply(this, arguments);
}
if (useClient && useServer) {
throw new Error(
'Cannot have both "use client" and "use server" directives in the same file.',
);
}
if (useClient) {
const moduleId: string = (url.pathToFileURL(filename).href: any);
this.exports = createClientModuleProxy(moduleId);
}
if (useServer) {
originalCompile.apply(this, arguments);
const moduleId: string = (url.pathToFileURL(filename).href: any);
const exports = this.exports;
// This module is imported server to server, but opts in to exposing functions by
// reference. If there are any functions in the export.
if (typeof exports === 'function') {
// The module exports a function directly,
registerServerReference(
(exports: any),
moduleId,
// Represents the whole Module object instead of a particular import.
null,
);
} else {
const keys = Object.keys(exports);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = exports[keys[i]];
if (typeof value === 'function') {
registerServerReference((value: any), moduleId, key);
}
}
}
}
};
};

View File

@@ -0,0 +1,352 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
export type ServerReference<T: Function> = T & {
$$typeof: symbol,
$$id: string,
$$bound: null | Array<ReactClientValue>,
$$location?: Error,
};
// eslint-disable-next-line no-unused-vars
export type ClientReference<T> = {
$$typeof: symbol,
$$id: string,
$$async: boolean,
};
const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
export function isClientReference(reference: Object): boolean {
return reference.$$typeof === CLIENT_REFERENCE_TAG;
}
export function isServerReference(reference: Object): boolean {
return reference.$$typeof === SERVER_REFERENCE_TAG;
}
export function registerClientReference<T>(
proxyImplementation: any,
id: string,
exportName: string,
): ClientReference<T> {
return registerClientReferenceImpl(
proxyImplementation,
id + '#' + exportName,
false,
);
}
function registerClientReferenceImpl<T>(
proxyImplementation: any,
id: string,
async: boolean,
): ClientReference<T> {
return Object.defineProperties(proxyImplementation, {
$$typeof: {value: CLIENT_REFERENCE_TAG},
$$id: {value: id},
$$async: {value: async},
});
}
// $FlowFixMe[method-unbinding]
const FunctionBind = Function.prototype.bind;
// $FlowFixMe[method-unbinding]
const ArraySlice = Array.prototype.slice;
function bind(this: ServerReference<any>): any {
// $FlowFixMe[incompatible-call]
const newFn = FunctionBind.apply(this, arguments);
if (this.$$typeof === SERVER_REFERENCE_TAG) {
if (__DEV__) {
const thisBind = arguments[0];
if (thisBind != null) {
console.error(
'Cannot bind "this" of a Server Action. Pass null or undefined as the first argument to .bind().',
);
}
}
const args = ArraySlice.call(arguments, 1);
const $$typeof = {value: SERVER_REFERENCE_TAG};
const $$id = {value: this.$$id};
const $$bound = {value: this.$$bound ? this.$$bound.concat(args) : args};
return Object.defineProperties(
(newFn: any),
(__DEV__
? {
$$typeof,
$$id,
$$bound,
$$location: {
value: this.$$location,
configurable: true,
},
bind: {value: bind, configurable: true},
}
: {
$$typeof,
$$id,
$$bound,
bind: {value: bind, configurable: true},
}) as PropertyDescriptorMap,
);
}
return newFn;
}
export function registerServerReference<T: Function>(
reference: T,
id: string,
exportName: null | string,
): ServerReference<T> {
const $$typeof = {value: SERVER_REFERENCE_TAG};
const $$id = {
value: exportName === null ? id : id + '#' + exportName,
configurable: true,
};
const $$bound = {value: null, configurable: true};
return Object.defineProperties(
(reference: any),
__DEV__
? ({
$$typeof,
$$id,
$$bound,
$$location: {
value: Error('react-stack-top-frame'),
configurable: true,
},
bind: {value: bind, configurable: true},
} as PropertyDescriptorMap)
: ({
$$typeof,
$$id,
$$bound,
bind: {value: bind, configurable: true},
} as PropertyDescriptorMap),
);
}
const PROMISE_PROTOTYPE = Promise.prototype;
const deepProxyHandlers: Proxy$traps<mixed> = {
get: function (
target: Function,
name: string | symbol,
receiver: Proxy<Function>,
) {
switch (name) {
// These names are read by the Flight runtime if you end up using the exports object.
case '$$typeof':
// These names are a little too common. We should probably have a way to
// have the Flight runtime extract the inner target instead.
return target.$$typeof;
case '$$id':
return target.$$id;
case '$$async':
return target.$$async;
case 'name':
return target.name;
case 'displayName':
return undefined;
// We need to special case this because createElement reads it if we pass this
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
case Symbol.toPrimitive:
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toPrimitive];
case Symbol.toStringTag:
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toStringTag];
case 'Provider':
throw new Error(
`Cannot render a Client Context Provider on the Server. ` +
`Instead, you can export a Client Component wrapper ` +
`that itself renders a Client Context Provider.`,
);
case 'then':
throw new Error(
`Cannot await or return from a thenable. ` +
`You cannot await a client module from a server component.`,
);
}
// eslint-disable-next-line react-internal/safe-string-coercion
const expression = String(target.name) + '.' + String(name);
throw new Error(
`Cannot access ${expression} on the server. ` +
'You cannot dot into a client module from a server component. ' +
'You can only pass the imported name through.',
);
},
set: function () {
throw new Error('Cannot assign to a client module from a server module.');
},
};
function getReference(target: Function, name: string | symbol): $FlowFixMe {
switch (name) {
// These names are read by the Flight runtime if you end up using the exports object.
case '$$typeof':
return target.$$typeof;
case '$$id':
return target.$$id;
case '$$async':
return target.$$async;
case 'name':
return target.name;
// We need to special case this because createElement reads it if we pass this
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
case Symbol.toPrimitive:
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toPrimitive];
case Symbol.toStringTag:
// $FlowFixMe[prop-missing]
return Object.prototype[Symbol.toStringTag];
case '__esModule':
// Something is conditionally checking which export to use. We'll pretend to be
// an ESM compat module but then we'll check again on the client.
const moduleId = target.$$id;
target.default = registerClientReferenceImpl(
(function () {
throw new Error(
`Attempted to call the default export of ${moduleId} from the server ` +
`but it's on the client. It's not possible to invoke a client function from ` +
`the server, it can only be rendered as a Component or passed to props of a ` +
`Client Component.`,
);
}: any),
target.$$id + '#',
target.$$async,
);
return true;
case 'then':
if (target.then) {
// Use a cached value
return target.then;
}
if (!target.$$async) {
// If this module is expected to return a Promise (such as an AsyncModule) then
// we should resolve that with a client reference that unwraps the Promise on
// the client.
const clientReference: ClientReference<any> =
registerClientReferenceImpl(({}: any), target.$$id, true);
const proxy = new Proxy(clientReference, proxyHandlers);
// Treat this as a resolved Promise for React's use()
target.status = 'fulfilled';
target.value = proxy;
const then = (target.then = registerClientReferenceImpl(
(function then(resolve, reject: any) {
// Expose to React.
return Promise.resolve(resolve(proxy));
}: any),
// If this is not used as a Promise but is treated as a reference to a `.then`
// export then we should treat it as a reference to that name.
target.$$id + '#then',
false,
));
return then;
} else {
// Since typeof .then === 'function' is a feature test we'd continue recursing
// indefinitely if we return a function. Instead, we return an object reference
// if we check further.
return undefined;
}
}
if (typeof name === 'symbol') {
throw new Error(
'Cannot read Symbol exports. Only named exports are supported on a client module ' +
'imported on the server.',
);
}
let cachedReference = target[name];
if (!cachedReference) {
const reference: ClientReference<any> = registerClientReferenceImpl(
(function () {
throw new Error(
// eslint-disable-next-line react-internal/safe-string-coercion
`Attempted to call ${String(name)}() from the server but ${String(
name,
)} is on the client. ` +
`It's not possible to invoke a client function from the server, it can ` +
`only be rendered as a Component or passed to props of a Client Component.`,
);
}: any),
target.$$id + '#' + name,
target.$$async,
);
Object.defineProperty((reference: any), 'name', {value: name});
cachedReference = target[name] = new Proxy(reference, deepProxyHandlers);
}
return cachedReference;
}
const proxyHandlers = {
get: function (
target: Function,
name: string | symbol,
receiver: Proxy<Function>,
): $FlowFixMe {
return getReference(target, name);
},
getOwnPropertyDescriptor: function (
target: Function,
name: string | symbol,
): $FlowFixMe {
let descriptor = Object.getOwnPropertyDescriptor(target, name);
if (!descriptor) {
descriptor = {
value: getReference(target, name),
writable: false,
configurable: false,
enumerable: false,
};
Object.defineProperty(target, name, descriptor);
}
return descriptor;
},
getPrototypeOf(target: Function): Object {
// Pretend to be a Promise in case anyone asks.
return PROMISE_PROTOTYPE;
},
set: function (): empty {
throw new Error('Cannot assign to a client module from a server module.');
},
};
export function createClientModuleProxy<T>(
moduleId: string,
): ClientReference<T> {
const clientReference: ClientReference<T> = registerClientReferenceImpl(
({}: any),
// Represents the whole Module object instead of a particular import.
moduleId,
false,
);
return new Proxy(clientReference, proxyHandlers);
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {preinitScriptForSSR} from 'react-client/src/ReactFlightClientConfig';
export type ModuleLoading = null | {
prefix: string,
crossOrigin?: 'use-credentials' | '',
};
export function prepareDestinationWithChunks(
moduleLoading: ModuleLoading,
// Chunks are double-indexed [..., idx, filenamex, idy, filenamey, ...]
chunks: Array<string>,
nonce: ?string,
) {
if (moduleLoading !== null) {
for (let i = 1; i < chunks.length; i += 2) {
preinitScriptForSSR(
moduleLoading.prefix + chunks[i],
nonce,
moduleLoading.crossOrigin,
);
}
}
}

View File

@@ -0,0 +1,255 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
import type {
DebugChannel,
FindSourceMapURLCallback,
Response as FlightResponse,
} from 'react-client/src/ReactFlightClient';
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
import type {
ServerConsumerModuleMap,
ModuleLoading,
ServerManifest,
} from 'react-client/src/ReactFlightClientConfig';
type ServerConsumerManifest = {
moduleMap: ServerConsumerModuleMap,
moduleLoading: ModuleLoading,
serverModuleMap: null | ServerManifest,
};
import {
createResponse,
createStreamState,
getRoot,
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
import {
processReply,
createServerReference as createServerReferenceImpl,
} from 'react-client/src/ReactFlightReplyClient';
export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
export type {TemporaryReferenceSet};
function noServerCall() {
throw new Error(
'Server Functions cannot be called during initial render. ' +
'This would create a fetch waterfall. Try to use a Server Component ' +
'to pass data to Client Components instead.',
);
}
export function createServerReference<A: Iterable<any>, T>(
id: any,
callServer: any,
): (...A) => Promise<T> {
return createServerReferenceImpl(id, noServerCall);
}
type EncodeFormActionCallback = <A>(
id: any,
args: Promise<A>,
) => ReactCustomFormAction;
export type Options = {
serverConsumerManifest: ServerConsumerManifest,
nonce?: string,
encodeFormAction?: EncodeFormActionCallback,
temporaryReferences?: TemporaryReferenceSet,
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
startTime?: number,
endTime?: number,
// For the Edge client we only support a single-direction debug channel.
debugChannel?: {readable?: ReadableStream, ...},
};
function createResponseFromOptions(options: Options) {
const debugChannel: void | DebugChannel =
__DEV__ && options && options.debugChannel !== undefined
? {
hasReadable: options.debugChannel.readable !== undefined,
callback: null,
}
: undefined;
return createResponse(
options.serverConsumerManifest.moduleMap,
options.serverConsumerManifest.serverModuleMap,
options.serverConsumerManifest.moduleLoading,
noServerCall,
options.encodeFormAction,
typeof options.nonce === 'string' ? options.nonce : undefined,
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
__DEV__ && options && options.findSourceMapURL
? options.findSourceMapURL
: undefined,
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
__DEV__ && options && options.startTime != null
? options.startTime
: undefined,
__DEV__ && options && options.endTime != null ? options.endTime : undefined,
debugChannel,
);
}
function startReadingFromStream(
response: FlightResponse,
stream: ReadableStream,
onDone: () => void,
debugValue: mixed,
): void {
const streamState = createStreamState(response, debugValue);
const reader = stream.getReader();
function progress({
done,
value,
}: {
done: boolean,
value: ?any,
...
}): void | Promise<void> {
if (done) {
return onDone();
}
const buffer: Uint8Array = (value: any);
processBinaryChunk(response, streamState, buffer);
return reader.read().then(progress).catch(error);
}
function error(e: any) {
reportGlobalError(response, e);
}
reader.read().then(progress).catch(error);
}
function createFromReadableStream<T>(
stream: ReadableStream,
options: Options,
): Thenable<T> {
const response: FlightResponse = createResponseFromOptions(options);
if (
__DEV__ &&
options &&
options.debugChannel &&
options.debugChannel.readable
) {
let streamDoneCount = 0;
const handleDone = () => {
if (++streamDoneCount === 2) {
close(response);
}
};
startReadingFromStream(response, options.debugChannel.readable, handleDone);
startReadingFromStream(response, stream, handleDone, stream);
} else {
startReadingFromStream(
response,
stream,
close.bind(null, response),
stream,
);
}
return getRoot(response);
}
function createFromFetch<T>(
promiseForResponse: Promise<Response>,
options: Options,
): Thenable<T> {
const response: FlightResponse = createResponseFromOptions(options);
promiseForResponse.then(
function (r) {
if (
__DEV__ &&
options &&
options.debugChannel &&
options.debugChannel.readable
) {
let streamDoneCount = 0;
const handleDone = () => {
if (++streamDoneCount === 2) {
close(response);
}
};
startReadingFromStream(
response,
options.debugChannel.readable,
handleDone,
);
startReadingFromStream(response, (r.body: any), handleDone, r);
} else {
startReadingFromStream(
response,
(r.body: any),
close.bind(null, response),
r,
);
}
},
function (e) {
reportGlobalError(response, e);
},
);
return getRoot(response);
}
function encodeReply(
value: ReactServerValue,
options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
): Promise<
string | URLSearchParams | FormData,
> /* We don't use URLSearchParams yet but maybe */ {
return new Promise((resolve, reject) => {
const abort = processReply(
value,
'',
options && options.temporaryReferences
? options.temporaryReferences
: undefined,
resolve,
reject,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort((signal: any).reason);
} else {
const listener = () => {
abort((signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
});
}
export {createFromFetch, createFromReadableStream, encodeReply};

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
import type {
DebugChannel,
FindSourceMapURLCallback,
Response,
} from 'react-client/src/ReactFlightClient';
import type {
ServerConsumerModuleMap,
ModuleLoading,
ServerManifest,
} from 'react-client/src/ReactFlightClientConfig';
type ServerConsumerManifest = {
moduleMap: ServerConsumerModuleMap,
moduleLoading: ModuleLoading,
serverModuleMap: null | ServerManifest,
};
import type {Readable} from 'stream';
import {
createResponse,
createStreamState,
getRoot,
reportGlobalError,
processStringChunk,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
export * from './ReactFlightDOMClientEdge';
function noServerCall() {
throw new Error(
'Server Functions cannot be called during initial render. ' +
'This would create a fetch waterfall. Try to use a Server Component ' +
'to pass data to Client Components instead.',
);
}
type EncodeFormActionCallback = <A>(
id: any,
args: Promise<A>,
) => ReactCustomFormAction;
export type Options = {
nonce?: string,
encodeFormAction?: EncodeFormActionCallback,
findSourceMapURL?: FindSourceMapURLCallback,
replayConsoleLogs?: boolean,
environmentName?: string,
startTime?: number,
endTime?: number,
// For the Node.js client we only support a single-direction debug channel.
debugChannel?: Readable,
};
function startReadingFromStream(
response: Response,
stream: Readable,
onEnd: () => void,
): void {
const streamState = createStreamState(response, stream);
stream.on('data', chunk => {
if (typeof chunk === 'string') {
processStringChunk(response, streamState, chunk);
} else {
processBinaryChunk(response, streamState, chunk);
}
});
stream.on('error', error => {
reportGlobalError(response, error);
});
stream.on('end', onEnd);
}
function createFromNodeStream<T>(
stream: Readable,
serverConsumerManifest: ServerConsumerManifest,
options?: Options,
): Thenable<T> {
const debugChannel: void | DebugChannel =
__DEV__ && options && options.debugChannel !== undefined
? {hasReadable: true, callback: null}
: undefined;
const response: Response = createResponse(
serverConsumerManifest.moduleMap,
serverConsumerManifest.serverModuleMap,
serverConsumerManifest.moduleLoading,
noServerCall,
options ? options.encodeFormAction : undefined,
options && typeof options.nonce === 'string' ? options.nonce : undefined,
undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
__DEV__ && options && options.findSourceMapURL
? options.findSourceMapURL
: undefined,
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
__DEV__ && options && options.environmentName
? options.environmentName
: undefined,
__DEV__ && options && options.startTime != null
? options.startTime
: undefined,
__DEV__ && options && options.endTime != null ? options.endTime : undefined,
debugChannel,
);
if (__DEV__ && options && options.debugChannel) {
let streamEndedCount = 0;
const handleEnd = () => {
if (++streamEndedCount === 2) {
close(response);
}
};
startReadingFromStream(response, options.debugChannel, handleEnd);
startReadingFromStream(response, stream, handleEnd);
} else {
startReadingFromStream(response, stream, close.bind(null, response));
}
return getRoot(response);
}
export {createFromNodeStream};

View File

@@ -0,0 +1,695 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Request,
ReactClientValue,
} from 'react-server/src/ReactFlightServer';
import type {Destination} from 'react-server/src/ReactServerStreamConfigNode';
import type {ClientManifest} from './ReactFlightServerConfigUnbundledBundler';
import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
import type {Busboy} from 'busboy';
import type {Writable} from 'stream';
import type {Thenable} from 'shared/ReactTypes';
import type {Duplex} from 'stream';
import {Readable} from 'stream';
import {ASYNC_ITERATOR} from 'shared/ReactSymbols';
import {
createRequest,
createPrerenderRequest,
startWork,
startFlowing,
startFlowingDebug,
stopFlowing,
abort,
resolveDebugMessage,
closeDebugChannel,
} from 'react-server/src/ReactFlightServer';
import {
createResponse,
reportGlobalError,
close,
resolveField,
resolveFile,
resolveFileInfo,
resolveFileChunk,
resolveFileComplete,
getRoot,
} from 'react-server/src/ReactFlightReplyServer';
import {
decodeAction,
decodeFormState,
} from 'react-server/src/ReactFlightActionServer';
export {
registerServerReference,
registerClientReference,
createClientModuleProxy,
} from '../ReactFlightUnbundledReferences';
import {
createStringDecoder,
readPartialStringChunk,
readFinalStringChunk,
} from 'react-client/src/ReactFlightClientStreamConfigNode';
import {textEncoder} from 'react-server/src/ReactServerStreamConfigNode';
import type {TemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export {createTemporaryReferenceSet} from 'react-server/src/ReactFlightServerTemporaryReferences';
export type {TemporaryReferenceSet};
function createDrainHandler(destination: Destination, request: Request) {
return () => startFlowing(request, destination);
}
function createCancelHandler(request: Request, reason: string) {
return () => {
stopFlowing(request);
abort(request, new Error(reason));
};
}
function startReadingFromDebugChannelReadable(
request: Request,
stream: Readable | WebSocket,
): void {
const stringDecoder = createStringDecoder();
let lastWasPartial = false;
let stringBuffer = '';
function onData(chunk: string | Uint8Array) {
if (typeof chunk === 'string') {
if (lastWasPartial) {
stringBuffer += readFinalStringChunk(stringDecoder, new Uint8Array(0));
lastWasPartial = false;
}
stringBuffer += chunk;
} else {
const buffer: Uint8Array = (chunk: any);
stringBuffer += readPartialStringChunk(stringDecoder, buffer);
lastWasPartial = true;
}
const messages = stringBuffer.split('\n');
for (let i = 0; i < messages.length - 1; i++) {
resolveDebugMessage(request, messages[i]);
}
stringBuffer = messages[messages.length - 1];
}
function onError(error: mixed) {
abort(
request,
new Error('Lost connection to the Debug Channel.', {
cause: error,
}),
);
}
function onClose() {
closeDebugChannel(request);
}
if (
// $FlowFixMe[method-unbinding]
typeof stream.addEventListener === 'function' &&
// $FlowFixMe[method-unbinding]
typeof stream.binaryType === 'string'
) {
const ws: WebSocket = (stream: any);
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', event => {
// $FlowFixMe
onData(event.data);
});
ws.addEventListener('error', event => {
// $FlowFixMe
onError(event.error);
});
ws.addEventListener('close', onClose);
} else {
const readable: Readable = (stream: any);
readable.on('data', onData);
readable.on('error', onError);
readable.on('end', onClose);
}
}
type Options = {
debugChannel?: Readable | Writable | Duplex | WebSocket,
environmentName?: string | (() => string),
filterStackFrame?: (url: string, functionName: string) => boolean,
onError?: (error: mixed) => void,
identifierPrefix?: string,
temporaryReferences?: TemporaryReferenceSet,
};
type PipeableStream = {
abort(reason: mixed): void,
pipe<T: Writable>(destination: T): T,
};
function renderToPipeableStream(
model: ReactClientValue,
webpackMap: ClientManifest,
options?: Options,
): PipeableStream {
const debugChannel = __DEV__ && options ? options.debugChannel : undefined;
const debugChannelReadable: void | Readable | WebSocket =
__DEV__ &&
debugChannel !== undefined &&
// $FlowFixMe[method-unbinding]
(typeof debugChannel.read === 'function' ||
typeof debugChannel.readyState === 'number')
? (debugChannel: any)
: undefined;
const debugChannelWritable: void | Writable =
__DEV__ && debugChannel !== undefined
? // $FlowFixMe[method-unbinding]
typeof debugChannel.write === 'function'
? (debugChannel: any)
: // $FlowFixMe[method-unbinding]
typeof debugChannel.send === 'function'
? createFakeWritableFromWebSocket((debugChannel: any))
: undefined
: undefined;
const request = createRequest(
model,
webpackMap,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
debugChannelReadable !== undefined,
);
let hasStartedFlowing = false;
startWork(request);
if (debugChannelWritable !== undefined) {
startFlowingDebug(request, debugChannelWritable);
}
if (debugChannelReadable !== undefined) {
startReadingFromDebugChannelReadable(request, debugChannelReadable);
}
return {
pipe<T: Writable>(destination: T): T {
if (hasStartedFlowing) {
throw new Error(
'React currently only supports piping to one writable stream.',
);
}
hasStartedFlowing = true;
startFlowing(request, destination);
destination.on('drain', createDrainHandler(destination, request));
destination.on(
'error',
createCancelHandler(
request,
'The destination stream errored while writing data.',
),
);
// We don't close until the debug channel closes.
if (!__DEV__ || debugChannelReadable === undefined) {
destination.on(
'close',
createCancelHandler(request, 'The destination stream closed early.'),
);
}
return destination;
},
abort(reason: mixed) {
abort(request, reason);
},
};
}
function createFakeWritableFromWebSocket(webSocket: WebSocket): Writable {
return ({
write(chunk: string | Uint8Array) {
webSocket.send((chunk: any));
return true;
},
end() {
webSocket.close();
},
destroy(reason) {
if (typeof reason === 'object' && reason !== null) {
reason = reason.message;
}
if (typeof reason === 'string') {
webSocket.close(1011, reason);
} else {
webSocket.close(1011);
}
},
}: any);
}
function createFakeWritableFromReadableStreamController(
controller: ReadableStreamController,
): Writable {
// The current host config expects a Writable so we create
// a fake writable for now to push into the Readable.
return ({
write(chunk: string | Uint8Array) {
if (typeof chunk === 'string') {
chunk = textEncoder.encode(chunk);
}
controller.enqueue(chunk);
// in web streams there is no backpressure so we can always write more
return true;
},
end() {
controller.close();
},
destroy(error) {
// $FlowFixMe[method-unbinding]
if (typeof controller.error === 'function') {
// $FlowFixMe[incompatible-call]: This is an Error object or the destination accepts other types.
controller.error(error);
} else {
controller.close();
}
},
}: any);
}
function startReadingFromDebugChannelReadableStream(
request: Request,
stream: ReadableStream,
): void {
const reader = stream.getReader();
const stringDecoder = createStringDecoder();
let stringBuffer = '';
function progress({
done,
value,
}: {
done: boolean,
value: ?any,
...
}): void | Promise<void> {
const buffer: Uint8Array = (value: any);
stringBuffer += done
? readFinalStringChunk(stringDecoder, new Uint8Array(0))
: readPartialStringChunk(stringDecoder, buffer);
const messages = stringBuffer.split('\n');
for (let i = 0; i < messages.length - 1; i++) {
resolveDebugMessage(request, messages[i]);
}
stringBuffer = messages[messages.length - 1];
if (done) {
closeDebugChannel(request);
return;
}
return reader.read().then(progress).catch(error);
}
function error(e: any) {
abort(
request,
new Error('Lost connection to the Debug Channel.', {
cause: e,
}),
);
}
reader.read().then(progress).catch(error);
}
function renderToReadableStream(
model: ReactClientValue,
webpackMap: ClientManifest,
options?: Omit<Options, 'debugChannel'> & {
debugChannel?: {readable?: ReadableStream, writable?: WritableStream, ...},
signal?: AbortSignal,
},
): ReadableStream {
const debugChannelReadable =
__DEV__ && options && options.debugChannel
? options.debugChannel.readable
: undefined;
const debugChannelWritable =
__DEV__ && options && options.debugChannel
? options.debugChannel.writable
: undefined;
const request = createRequest(
model,
webpackMap,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
debugChannelReadable !== undefined,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
abort(request, (signal: any).reason);
} else {
const listener = () => {
abort(request, (signal: any).reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
if (debugChannelWritable !== undefined) {
let debugWritable: Writable;
const debugStream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
debugWritable =
createFakeWritableFromReadableStreamController(controller);
},
pull: (controller): ?Promise<void> => {
startFlowingDebug(request, debugWritable);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
debugStream.pipeTo(debugChannelWritable);
}
if (debugChannelReadable !== undefined) {
startReadingFromDebugChannelReadableStream(request, debugChannelReadable);
}
let writable: Writable;
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
writable = createFakeWritableFromReadableStreamController(controller);
startWork(request);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, writable);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
return stream;
}
function createFakeWritableFromNodeReadable(readable: any): Writable {
// The current host config expects a Writable so we create
// a fake writable for now to push into the Readable.
return ({
write(chunk: string | Uint8Array) {
return readable.push(chunk);
},
end() {
readable.push(null);
},
destroy(error) {
readable.destroy(error);
},
}: any);
}
type PrerenderOptions = {
environmentName?: string | (() => string),
filterStackFrame?: (url: string, functionName: string) => boolean,
onError?: (error: mixed) => void,
identifierPrefix?: string,
temporaryReferences?: TemporaryReferenceSet,
signal?: AbortSignal,
};
type StaticResult = {
prelude: Readable,
};
function prerenderToNodeStream(
model: ReactClientValue,
webpackMap: ClientManifest,
options?: PrerenderOptions,
): Promise<StaticResult> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
const readable: Readable = new Readable({
read() {
startFlowing(request, writable);
},
});
const writable = createFakeWritableFromNodeReadable(readable);
resolve({prelude: readable});
}
const request = createPrerenderRequest(
model,
webpackMap,
onAllReady,
onFatalError,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
false,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
const reason = (signal: any).reason;
abort(request, reason);
} else {
const listener = () => {
const reason = (signal: any).reason;
abort(request, reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
function prerender(
model: ReactClientValue,
webpackMap: ClientManifest,
options?: Options & {
signal?: AbortSignal,
},
): Promise<{
prelude: ReadableStream,
}> {
return new Promise((resolve, reject) => {
const onFatalError = reject;
function onAllReady() {
let writable: Writable;
const stream = new ReadableStream(
{
type: 'bytes',
start: (controller): ?Promise<void> => {
writable =
createFakeWritableFromReadableStreamController(controller);
},
pull: (controller): ?Promise<void> => {
startFlowing(request, writable);
},
cancel: (reason): ?Promise<void> => {
stopFlowing(request);
abort(request, reason);
},
},
// $FlowFixMe[prop-missing] size() methods are not allowed on byte streams.
{highWaterMark: 0},
);
resolve({prelude: stream});
}
const request = createPrerenderRequest(
model,
webpackMap,
onAllReady,
onFatalError,
options ? options.onError : undefined,
options ? options.identifierPrefix : undefined,
options ? options.temporaryReferences : undefined,
__DEV__ && options ? options.environmentName : undefined,
__DEV__ && options ? options.filterStackFrame : undefined,
false,
);
if (options && options.signal) {
const signal = options.signal;
if (signal.aborted) {
const reason = (signal: any).reason;
abort(request, reason);
} else {
const listener = () => {
const reason = (signal: any).reason;
abort(request, reason);
signal.removeEventListener('abort', listener);
};
signal.addEventListener('abort', listener);
}
}
startWork(request);
});
}
function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
const response = createResponse(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
busboyStream.on('field', (name, value) => {
if (pendingFiles > 0) {
// Because the 'end' event fires two microtasks after the next 'field'
// we would resolve files and fields out of order. To handle this properly
// we queue any fields we receive until the previous file is done.
queuedFields.push(name, value);
} else {
try {
resolveField(response, name, value);
} catch (error) {
busboyStream.destroy(error);
}
}
});
busboyStream.on('file', (name, value, {filename, encoding, mimeType}) => {
if (encoding.toLowerCase() === 'base64') {
busboyStream.destroy(
new Error(
"React doesn't accept base64 encoded file uploads because we don't expect " +
"form data passed from a browser to ever encode data that way. If that's " +
'the wrong assumption, we can easily fix it.',
),
);
return;
}
pendingFiles++;
const file = resolveFileInfo(response, name, filename, mimeType);
value.on('data', chunk => {
resolveFileChunk(response, file, chunk);
});
value.on('end', () => {
try {
resolveFileComplete(response, name, file);
pendingFiles--;
if (pendingFiles === 0) {
// Release any queued fields
for (let i = 0; i < queuedFields.length; i += 2) {
resolveField(response, queuedFields[i], queuedFields[i + 1]);
}
queuedFields.length = 0;
}
} catch (error) {
busboyStream.destroy(error);
}
});
});
busboyStream.on('finish', () => {
close(response);
});
busboyStream.on('error', err => {
reportGlobalError(
response,
// $FlowFixMe[incompatible-call] types Error and mixed are incompatible
err,
);
});
return getRoot(response);
}
function decodeReply<T>(
body: string | FormData,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
form.append('0', body);
body = form;
}
const response = createResponse(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
body,
);
const root = getRoot<T>(response);
close(response);
return root;
}
function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
const response = createResponse(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
);
function progress(
entry:
| {done: false, +value: [string, string | File], ...}
| {done: true, +value: void, ...},
) {
if (entry.done) {
close(response);
} else {
const [name, value] = entry.value;
if (typeof value === 'string') {
resolveField(response, name, value);
} else {
resolveFile(response, name, value);
}
iterator.next().then(progress, error);
}
}
function error(reason: Error) {
reportGlobalError(response, reason);
if (typeof (iterator: any).throw === 'function') {
// The iterator protocol doesn't necessarily include this but a generator do.
// $FlowFixMe should be able to pass mixed
iterator.throw(reason).then(error, error);
}
}
iterator.next().then(progress, error);
return getRoot(response);
}
export {
renderToReadableStream,
renderToPipeableStream,
prerender,
prerenderToNodeStream,
decodeReply,
decodeReplyFromBusboy,
decodeReplyFromAsyncIterable,
decodeAction,
decodeFormState,
};

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {
ImportMetadata,
ImportManifestEntry,
} from '../shared/ReactFlightImportMetadata';
import type {
ClientReference,
ServerReference,
} from '../ReactFlightUnbundledReferences';
export type {ClientReference, ServerReference};
export type ClientManifest = {
[id: string]: ClientReferenceManifestEntry,
};
export type ServerReferenceId = string;
export type ClientReferenceMetadata = ImportMetadata;
export opaque type ClientReferenceManifestEntry = ImportManifestEntry;
export type ClientReferenceKey = string;
export {
isClientReference,
isServerReference,
} from '../ReactFlightUnbundledReferences';
export function getClientReferenceKey(
reference: ClientReference<any>,
): ClientReferenceKey {
return reference.$$async ? reference.$$id + '#async' : reference.$$id;
}
export function resolveClientReferenceMetadata<T>(
config: ClientManifest,
clientReference: ClientReference<T>,
): ClientReferenceMetadata {
const modulePath = clientReference.$$id;
let name = '';
let resolvedModuleData = config[modulePath];
if (resolvedModuleData) {
// The potentially aliased name.
name = resolvedModuleData.name;
} else {
// We didn't find this specific export name but we might have the * export
// which contains this name as well.
// TODO: It's unfortunate that we now have to parse this string. We should
// probably go back to encoding path and name separately on the client reference.
const idx = modulePath.lastIndexOf('#');
if (idx !== -1) {
name = modulePath.slice(idx + 1);
resolvedModuleData = config[modulePath.slice(0, idx)];
}
if (!resolvedModuleData) {
throw new Error(
'Could not find the module "' +
modulePath +
'" in the React Client Manifest. ' +
'This is probably a bug in the React Server Components bundler.',
);
}
}
if (resolvedModuleData.async === true && clientReference.$$async === true) {
throw new Error(
'The module "' +
modulePath +
'" is marked as an async ESM module but was loaded as a CJS proxy. ' +
'This is probably a bug in the React Server Components bundler.',
);
}
if (resolvedModuleData.async === true || clientReference.$$async === true) {
return [resolvedModuleData.id, resolvedModuleData.chunks, name, 1];
} else {
return [resolvedModuleData.id, resolvedModuleData.chunks, name];
}
}
export function getServerReferenceId<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): ServerReferenceId {
return serverReference.$$id;
}
export function getServerReferenceBoundArguments<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): null | Array<ReactClientValue> {
return serverReference.$$bound;
}
export function getServerReferenceLocation<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): void | Error {
return serverReference.$$location;
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type ImportManifestEntry = {
id: string,
// chunks is a double indexed array of chunkId / chunkFilename pairs
chunks: Array<string>,
name: string,
async?: boolean,
};
// This is the parsed shape of the wire format which is why it is
// condensed to only the essentialy information
export type ImportMetadata =
| [
/* id */ string,
/* chunks id/filename pairs, double indexed */ Array<string>,
/* name */ string,
/* async */ 1,
]
| [
/* id */ string,
/* chunks id/filename pairs, double indexed */ Array<string>,
/* name */ string,
];
export const ID = 0;
export const CHUNKS = 1;
export const NAME = 2;
// export const ASYNC = 3;
// This logic is correct because currently only include the 4th tuple member
// when the module is async. If that changes we will need to actually assert
// the value is true. We don't index into the 4th slot because flow does not
// like the potential out of bounds access
export function isAsyncImport(metadata: ImportMetadata): boolean {
return metadata.length === 4;
}

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
throw new Error(
'The React Server cannot be used outside a react-server environment. ' +
'You must configure Node.js using the `--conditions react-server` flag.',
);

View File

@@ -10,4 +10,4 @@
export {
prerender,
prerenderToNodeStream,
} from './src/server/react-flight-dom-server.node.unbundled';
} from './src/server/react-flight-dom-server.node';

View File

@@ -17,17 +17,14 @@
"client.browser.js",
"client.edge.js",
"client.node.js",
"client.node.unbundled.js",
"server.js",
"server.browser.js",
"server.edge.js",
"server.node.js",
"server.node.unbundled.js",
"static.js",
"static.browser.js",
"static.edge.js",
"static.node.js",
"static.node.unbundled.js",
"node-register.js",
"cjs/",
"esm/"
@@ -39,10 +36,7 @@
"workerd": "./client.edge.js",
"deno": "./client.edge.js",
"worker": "./client.edge.js",
"node": {
"webpack": "./client.node.js",
"default": "./client.node.unbundled.js"
},
"node": "./client.node.js",
"edge-light": "./client.edge.js",
"browser": "./client.browser.js",
"default": "./client.browser.js"
@@ -50,15 +44,11 @@
"./client.browser": "./client.browser.js",
"./client.edge": "./client.edge.js",
"./client.node": "./client.node.js",
"./client.node.unbundled": "./client.node.unbundled.js",
"./server": {
"react-server": {
"workerd": "./server.edge.js",
"deno": "./server.browser.js",
"node": {
"webpack": "./server.node.js",
"default": "./server.node.unbundled.js"
},
"node": "./server.node.js",
"edge-light": "./server.edge.js",
"browser": "./server.browser.js"
},
@@ -67,15 +57,11 @@
"./server.browser": "./server.browser.js",
"./server.edge": "./server.edge.js",
"./server.node": "./server.node.js",
"./server.node.unbundled": "./server.node.unbundled.js",
"./static": {
"react-server": {
"workerd": "./static.edge.js",
"deno": "./static.browser.js",
"node": {
"webpack": "./static.node.js",
"default": "./static.node.unbundled.js"
},
"node": "./static.node.js",
"edge-light": "./static.edge.js",
"browser": "./static.browser.js"
},
@@ -84,7 +70,6 @@
"./static.browser": "./static.browser.js",
"./static.edge": "./static.edge.js",
"./static.node": "./static.node.js",
"./static.node.unbundled": "./static.node.unbundled.js",
"./node-loader": "./esm/react-server-dom-webpack-node-loader.production.js",
"./node-register": "./node-register.js",
"./src/*": "./src/*.js",

View File

@@ -60,10 +60,10 @@ describe('ReactFlightDOM', () => {
FlightReactDOM = require('react-dom');
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.node.unbundled'),
require('react-server-dom-unbundled/server.node'),
);
jest.mock('react-server-dom-webpack/static', () =>
require('react-server-dom-webpack/static.node.unbundled'),
require('react-server-dom-unbundled/static.node'),
);
const WebpackMock = require('./utils/WebpackMock');
clientExports = WebpackMock.clientExports;

View File

@@ -45,12 +45,12 @@ describe('ReactFlightDOMNode', () => {
// Simulate the condition resolution
jest.mock('react', () => require('react/react.react-server'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.node'),
jest.requireActual('react-server-dom-webpack/server.node'),
);
ReactServer = require('react');
ReactServerDOMServer = require('react-server-dom-webpack/server');
jest.mock('react-server-dom-webpack/static', () =>
require('react-server-dom-webpack/static.node'),
jest.requireActual('react-server-dom-webpack/static.node'),
);
ReactServerDOMStaticServer = require('react-server-dom-webpack/static');
@@ -64,7 +64,7 @@ describe('ReactFlightDOMNode', () => {
__unmockReact();
jest.unmock('react-server-dom-webpack/server');
jest.mock('react-server-dom-webpack/client', () =>
require('react-server-dom-webpack/client.node'),
jest.requireActual('react-server-dom-webpack/client.node'),
);
React = require('react');

View File

@@ -41,7 +41,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
jest.mock('react', () => require('react/react.react-server'));
jest.mock('react-server-dom-webpack/server', () =>
require('react-server-dom-webpack/server.node'),
jest.requireActual('react-server-dom-webpack/server.node'),
);
ReactServer = require('react');
ReactServerDOMServer = require('react-server-dom-webpack/server');
@@ -54,7 +54,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
__unmockReact();
jest.unmock('react-server-dom-webpack/server');
jest.mock('react-server-dom-webpack/client', () =>
require('react-server-dom-webpack/client.node'),
jest.requireActual('react-server-dom-webpack/client.node'),
);
React = require('react');

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-unbundled/src/server/ReactFlightServerConfigUnbundledBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
export const supportsComponentStorage = __DEV__;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
export * from '../ReactFlightServerConfigDebugNode';
export * from '../ReactFlightStackConfigV8';
export * from '../ReactServerConsoleConfigServer';

View File

@@ -476,25 +476,6 @@ const bundles = [
'util',
],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry:
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
name: 'react-server-dom-webpack-server.node.unbundled',
condition: 'react-server',
global: 'ReactServerDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: [
'react',
'react-dom',
'async_hooks',
'crypto',
'stream',
'util',
],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
@@ -529,17 +510,6 @@ const bundles = [
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom', 'util', 'crypto'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry:
'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
name: 'react-server-dom-webpack-client.node.unbundled',
global: 'ReactServerDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom', 'util', 'crypto'],
},
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
@@ -786,6 +756,63 @@ const bundles = [
externals: ['acorn'],
},
/******* React Server DOM Unbundled Server *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
name: 'react-server-dom-unbundled-server.node',
condition: 'react-server',
global: 'ReactServerDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: [
'react',
'react-dom',
'async_hooks',
'crypto',
'stream',
'util',
],
},
/******* React Server DOM Unbundled Client *******/
{
bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RENDERER,
entry: 'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
name: 'react-server-dom-unbundled-client.node',
global: 'ReactServerDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom', 'util', 'crypto'],
},
/******* React Server DOM Unbundled Node.js Loader *******/
{
bundleTypes: [ESM_PROD],
moduleType: RENDERER_UTILS,
entry: 'react-server-dom-unbundled/node-loader',
condition: 'react-server',
global: 'ReactServerUnbundledNodeLoader',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['acorn'],
},
/******* React Server DOM Unbundled Node.js CommonJS Loader *******/
{
bundleTypes: [NODE_ES2015],
moduleType: RENDERER_UTILS,
entry: 'react-server-dom-unbundled/src/ReactFlightUnbundledNodeRegister',
name: 'react-server-dom-unbundled-node-register',
condition: 'react-server',
global: 'ReactFlightUnbundledNodeRegister',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['url', 'module', 'react-server-dom-unbundled/server'],
},
/******* React Suspense Test Utils *******/
{
bundleTypes: [NODE_ES2015],

View File

@@ -63,8 +63,8 @@ module.exports = [
'react-dom/src/server/react-dom-server.node.js',
'react-dom/test-utils',
'react-dom/unstable_server-external-runtime',
'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
'react-server-dom-webpack/src/client/react-flight-dom-client.node',
'react-server-dom-webpack/src/server/react-flight-dom-server.node',
],
paths: [
'react-dom',
@@ -84,20 +84,13 @@ module.exports = [
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
'react-server-dom-webpack',
'react-server-dom-webpack/client.node.unbundled',
'react-server-dom-webpack/server',
'react-server-dom-webpack/server.node.unbundled',
'react-server-dom-webpack/static',
'react-server-dom-webpack/static.node.unbundled',
'react-server-dom-webpack/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-webpack/client.node
'react-server-dom-webpack/src/client/ReactFlightDOMClientNode.js', // react-server-dom-webpack/client.node
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerNode.js',
'react-server-dom-webpack/src/client/react-flight-dom-client.node.unbundled',
'react-server-dom-webpack/src/server/react-flight-dom-server.node.unbundled',
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js',
'react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpackServer.js',
'react-server-dom-webpack/src/server/ReactFlightDOMServerNode.js', // react-server-dom-webpack/src/server/react-flight-dom-server.node
'react-devtools',
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
@@ -240,6 +233,49 @@ module.exports = [
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-node-unbundled',
entryPoints: [
'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
],
paths: [
'react-dom',
'react-dom-bindings',
'react-dom/client',
'react-dom/profiling',
'react-dom/server',
'react-dom/server.node',
'react-dom/static',
'react-dom/static.node',
'react-dom/src/server/react-dom-server.node',
'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/server.node
'react-dom/src/server/ReactDOMFizzStaticNode.js',
'react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js',
'react-dom-bindings/src/server/ReactFlightServerConfigDOM.js',
'react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js',
'react-server-dom-unbundled',
'react-server-dom-unbundled/client',
'react-server-dom-unbundled/server',
'react-server-dom-unbundled/server.node',
'react-server-dom-unbundled/static',
'react-server-dom-unbundled/static.node',
'react-server-dom-unbundled/src/client/ReactFlightDOMClientEdge.js', // react-server-dom-unbundled/client.node
'react-server-dom-unbundled/src/client/ReactFlightDOMClientNode.js', // react-server-dom-unbundled/client.node
'react-server-dom-unbundled/src/client/ReactFlightClientConfigBundlerNode.js',
'react-server-dom-unbundled/src/client/react-flight-dom-client.node',
'react-server-dom-unbundled/src/server/react-flight-dom-server.node',
'react-server-dom-unbundled/src/server/ReactFlightDOMServerNode.js', // react-server-dom-unbundled/src/server/react-flight-dom-server.node
'react-devtools',
'react-devtools-core',
'react-devtools-shell',
'react-devtools-shared',
'shared/ReactDOMSharedInternals',
'react-server/src/ReactFlightServerConfigDebugNode.js',
],
isFlowTyped: true,
isServerSupported: true,
},
{
shortName: 'dom-bun',
entryPoints: ['react-dom/src/server/react-dom-server.bun.js'],