FB-specific builds of Flight Server, Flight Client, and React Shared Subset (#27579)

This PR adds a new FB-specific configuration of Flight. We also need to
bundle a version of ReactSharedSubset that will be used for running
Flight on the server.

This initial implementation does not support server actions yet.

The FB-Flight still uses the text protocol on the server (the flag
`enableBinaryFlight` is set to false). It looks like we need some
changes in Hermes to properly support this binary format.
This commit is contained in:
Andrey Lunyov 2023-11-27 18:34:58 -05:00 committed by GitHub
parent 6c7b41da3d
commit c17a27ef49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 960 additions and 9 deletions

View File

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

View File

@ -0,0 +1,14 @@
/**
* 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 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export * from 'react-server-dom-fb/src/ReactFlightClientConfigFBBundler';
export const usedWithSSR = false;

View File

@ -8,7 +8,7 @@
*/ */
// This client file is in the shared folder because it applies to both SSR and browser contexts. // This client file is in the shared folder because it applies to both SSR and browser contexts.
// It is the configuraiton of the FlightClient behavior which can run in either environment. // It is the configuration of the FlightClient behavior which can run in either environment.
import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM'; import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM';
@ -107,7 +107,7 @@ export function dispatchHint<Code: HintCode>(
} }
} }
// Flow is having troulbe refining the HintModels so we help it a bit. // Flow is having trouble refining the HintModels so we help it a bit.
// This should be compiled out in the production build. // This should be compiled out in the production build.
function refineModel<T>(code: T, model: HintModel<any>): HintModel<T> { function refineModel<T>(code: T, model: HintModel<any>): HintModel<T> {
return model; return model;

View File

@ -0,0 +1,112 @@
/**
* 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,
FulfilledThenable,
RejectedThenable,
} from 'shared/ReactTypes';
export type ModuleLoading = mixed;
type ResolveClientReferenceFn<T> =
ClientReferenceMetadata => ClientReference<T>;
export type SSRModuleMap = {
resolveClientReference?: ResolveClientReferenceFn<any>,
};
export type ServerManifest = string;
export type {
ClientManifest,
ServerReferenceId,
ClientReferenceMetadata,
} from './ReactFlightReferencesFB';
import type {
ServerReferenceId,
ClientReferenceMetadata,
} from './ReactFlightReferencesFB';
export type ClientReference<T> = {
getModuleId: () => string,
load: () => Thenable<T>,
};
export function prepareDestinationForModule(
moduleLoading: ModuleLoading,
nonce: ?string,
metadata: ClientReferenceMetadata,
) {
return;
}
export function resolveClientReference<T>(
moduleMap: SSRModuleMap,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
if (typeof moduleMap.resolveClientReference === 'function') {
return moduleMap.resolveClientReference(metadata);
} else {
throw new Error(
'Expected `resolveClientReference` to be defined on the moduleMap.',
);
}
}
export function resolveServerReference<T>(
config: ServerManifest,
id: ServerReferenceId,
): ClientReference<T> {
throw new Error('Not implemented');
}
const asyncModuleCache: Map<string, Thenable<any>> = new Map();
export function preloadModule<T>(
clientReference: ClientReference<T>,
): null | Thenable<any> {
const existingPromise = asyncModuleCache.get(clientReference.getModuleId());
if (existingPromise) {
if (existingPromise.status === 'fulfilled') {
return null;
}
return existingPromise;
} else {
const modulePromise: Thenable<T> = clientReference.load();
modulePromise.then(
value => {
const fulfilledThenable: FulfilledThenable<mixed> =
(modulePromise: any);
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = value;
},
reason => {
const rejectedThenable: RejectedThenable<mixed> = (modulePromise: any);
rejectedThenable.status = 'rejected';
rejectedThenable.reason = reason;
},
);
asyncModuleCache.set(clientReference.getModuleId(), modulePromise);
return modulePromise;
}
}
export function requireModule<T>(clientReference: ClientReference<T>): T {
let module;
// We assume that preloadModule has been called before, which
// should have added something to the module cache.
const promise: any = asyncModuleCache.get(clientReference.getModuleId());
if (promise.status === 'fulfilled') {
module = promise.value;
} else {
throw promise.reason;
}
// We are currently only support default exports for client components
return module;
}

View File

@ -0,0 +1,91 @@
/**
* 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 {enableBinaryFlight} from 'shared/ReactFeatureFlags';
import type {Thenable} from 'shared/ReactTypes';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
import {
createResponse,
getRoot,
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
import type {SSRModuleMap} from './ReactFlightClientConfigFBBundler';
type Options = {
moduleMap: SSRModuleMap,
};
function createResponseFromOptions(options: void | Options) {
const moduleMap = options && options.moduleMap;
if (moduleMap == null) {
throw new Error('Expected `moduleMap` to be defined.');
}
return createResponse(moduleMap, null, undefined, undefined);
}
function processChunk(response: FlightResponse, chunk: string | Uint8Array) {
if (enableBinaryFlight) {
if (typeof chunk === 'string') {
throw new Error(
'`enableBinaryFlight` flag is enabled, expected a Uint8Array as input, got string.',
);
}
}
const buffer = typeof chunk !== 'string' ? chunk : encodeString(chunk);
processBinaryChunk(response, buffer);
}
function encodeString(string: string) {
const textEncoder = new TextEncoder();
return textEncoder.encode(string);
}
function startReadingFromStream(
response: FlightResponse,
stream: ReadableStream,
): void {
const reader = stream.getReader();
function progress({
done,
value,
}: {
done: boolean,
value: ?any,
...
}): void | Promise<void> {
if (done) {
close(response);
return;
}
const buffer: Uint8Array = (value: any);
processChunk(response, 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);
startReadingFromStream(response, stream);
return getRoot(response);
}
export {createFromReadableStream};

View File

@ -0,0 +1,68 @@
/**
* 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 {
Destination,
Chunk,
PrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';
import type {ClientManifest} from './ReactFlightReferencesFB';
import {
createRequest,
startWork,
startFlowing,
} from 'react-server/src/ReactFlightServer';
import {setByteLengthOfChunkImplementation} from 'react-server/src/ReactServerStreamConfig';
export {
registerClientReference,
registerServerReference,
getRequestedClientReferencesKeys,
clearRequestedClientReferencesKeysSet,
} from './ReactFlightReferencesFB';
type Options = {
onError?: (error: mixed) => void,
};
function renderToDestination(
destination: Destination,
model: ReactClientValue,
bundlerConfig: ClientManifest,
options?: Options,
): void {
if (!configured) {
throw new Error(
'Please make sure to call `setConfig(...)` before calling `renderToDestination`.',
);
}
const request = createRequest(
model,
bundlerConfig,
options ? options.onError : undefined,
);
startWork(request);
startFlowing(request, destination);
}
type Config = {
byteLength: (chunk: Chunk | PrecomputedChunk) => number,
};
let configured = false;
function setConfig(config: Config): void {
setByteLengthOfChunkImplementation(config.byteLength);
configured = true;
}
export {renderToDestination, setConfig};

View File

@ -0,0 +1,98 @@
/**
* 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 opaque type ClientManifest = mixed;
// eslint-disable-next-line no-unused-vars
export type ServerReference<T> = string;
// eslint-disable-next-line no-unused-vars
export type ClientReference<T> = string;
const registeredClientReferences = new Map<mixed, ClientReferenceMetadata>();
const requestedClientReferencesKeys = new Set<ClientReferenceKey>();
export type ClientReferenceKey = string;
export type ClientReferenceMetadata = {
moduleId: ClientReferenceKey,
exportName: string,
};
export type ServerReferenceId = string;
export function registerClientReference<T>(
clientReference: ClientReference<T>,
moduleId: ClientReferenceKey,
): ClientReference<T> {
const exportName = 'default'; // Currently, we only support modules with `default` export
registeredClientReferences.set(clientReference, {
moduleId,
exportName,
});
return clientReference;
}
export function isClientReference<T>(reference: T): boolean {
return registeredClientReferences.has(reference);
}
export function getClientReferenceKey<T>(
clientReference: ClientReference<T>,
): ClientReferenceKey {
const reference = registeredClientReferences.get(clientReference);
if (reference != null) {
requestedClientReferencesKeys.add(reference.moduleId);
return reference.moduleId;
}
throw new Error(
'Expected client reference ' + clientReference + ' to be registered.',
);
}
export function resolveClientReferenceMetadata<T>(
config: ClientManifest,
clientReference: ClientReference<T>,
): ClientReferenceMetadata {
const metadata = registeredClientReferences.get(clientReference);
if (metadata != null) {
return metadata;
}
throw new Error(
'Expected client reference ' + clientReference + ' to be registered.',
);
}
export function registerServerReference<T>(
serverReference: ServerReference<T>,
exportName: string,
): ServerReference<T> {
throw new Error('registerServerReference: Not Implemented.');
}
export function isServerReference<T>(reference: T): boolean {
throw new Error('isServerReference: Not Implemented.');
}
export function getServerReferenceId<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): ServerReferenceId {
throw new Error('getServerReferenceId: Not Implemented.');
}
export function getRequestedClientReferencesKeys(): $ReadOnlyArray<ClientReferenceKey> {
return Array.from(requestedClientReferencesKeys);
}
export function clearRequestedClientReferencesKeysSet(): void {
requestedClientReferencesKeys.clear();
}

View File

@ -0,0 +1,36 @@
/**
* 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 {
ClientManifest,
ClientReference,
ServerReference,
} from './ReactFlightReferencesFB';
export type {ClientManifest, ClientReference, ServerReference};
export {
ClientReferenceKey,
ClientReferenceMetadata,
getClientReferenceKey,
isClientReference,
resolveClientReferenceMetadata,
isServerReference,
ServerReferenceId,
getServerReferenceId,
} from './ReactFlightReferencesFB';
export function getServerReferenceBoundArguments<T>(
config: ClientManifest,
serverReference: ServerReference<T>,
): null | Array<ReactClientValue> {
throw new Error('getServerReferenceBoundArguments: Not Implemented.');
}

View File

@ -0,0 +1,364 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
// Polyfills for test environment
global.ReadableStream =
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
global.TextEncoder = require('util').TextEncoder;
global.TextDecoder = require('util').TextDecoder;
// Don't wait before processing work on the server.
// TODO: we can replace this with FlightServer.act().
global.setImmediate = cb => cb();
let act;
let use;
let clientExports;
let moduleMap;
let React;
let ReactDOMClient;
let ReactServerDOMServer;
let ReactServerDOMClient;
let Suspense;
let registerClientReference;
class Destination {
#buffer = '';
#controller = null;
constructor() {
const self = this;
this.stream = new ReadableStream({
start(controller) {
self.#controller = controller;
},
});
}
write(chunk) {
this.#buffer += chunk;
}
beginWriting() {}
completeWriting() {}
flushBuffered() {
if (!this.#controller) {
throw new Error('Expected a controller.');
}
this.#controller.enqueue(this.#buffer);
this.#buffer = '';
}
close() {}
onError() {}
}
describe('ReactFlightDOM for FB', () => {
beforeEach(() => {
// For this first reset we are going to load the dom-node version of react-server-dom-turbopack/server
// This can be thought of as essentially being the React Server Components scope with react-server
// condition
jest.resetModules();
registerClientReference =
require('../ReactFlightReferencesFB').registerClientReference;
jest.mock('react', () => require('react/src/ReactSharedSubsetFB'));
jest.mock('shared/ReactFeatureFlags', () => {
jest.mock(
'ReactFeatureFlags',
() => jest.requireActual('shared/forks/ReactFeatureFlags.www-dynamic'),
{virtual: true},
);
return jest.requireActual('shared/forks/ReactFeatureFlags.www');
});
clientExports = value => {
registerClientReference(value, value.name);
return value;
};
moduleMap = {
resolveClientReference(metadata) {
throw new Error('Do not expect to load client components.');
},
};
ReactServerDOMServer = require('../ReactFlightDOMServerFB');
ReactServerDOMServer.setConfig({
byteLength: str => Buffer.byteLength(str),
});
// This reset is to load modules for the SSR/Browser scope.
jest.resetModules();
__unmockReact();
act = require('internal-test-utils').act;
React = require('react');
use = React.use;
Suspense = React.Suspense;
ReactDOMClient = require('react-dom/client');
ReactServerDOMClient = require('../ReactFlightDOMClientFB');
});
it('should resolve HTML with renderToDestination', async () => {
function Text({children}) {
return <span>{children}</span>;
}
function HTML() {
return (
<div>
<Text>hello</Text>
<Text>world</Text>
</div>
);
}
function App() {
const model = {
html: <HTML />,
};
return model;
}
const destination = new Destination();
ReactServerDOMServer.renderToDestination(destination, <App />);
const response = ReactServerDOMClient.createFromReadableStream(
destination.stream,
{
moduleMap,
},
);
const model = await response;
expect(model).toEqual({
html: (
<div>
<span>hello</span>
<span>world</span>
</div>
),
});
});
it('should resolve the root', async () => {
// Model
function Text({children}) {
return <span>{children}</span>;
}
function HTML() {
return (
<div>
<Text>hello</Text>
<Text>world</Text>
</div>
);
}
function RootModel() {
return {
html: <HTML />,
};
}
// View
function Message({response}) {
return <section>{use(response).html}</section>;
}
function App({response}) {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Message response={response} />
</Suspense>
);
}
const destination = new Destination();
ReactServerDOMServer.renderToDestination(destination, <RootModel />);
const response = ReactServerDOMClient.createFromReadableStream(
destination.stream,
{
moduleMap,
},
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App response={response} />);
});
expect(container.innerHTML).toBe(
'<section><div><span>hello</span><span>world</span></div></section>',
);
});
it('should not get confused by $', async () => {
// Model
function RootModel() {
return {text: '$1'};
}
// View
function Message({response}) {
return <p>{use(response).text}</p>;
}
function App({response}) {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Message response={response} />
</Suspense>
);
}
const destination = new Destination();
ReactServerDOMServer.renderToDestination(destination, <RootModel />);
const response = ReactServerDOMClient.createFromReadableStream(
destination.stream,
{
moduleMap,
},
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App response={response} />);
});
expect(container.innerHTML).toBe('<p>$1</p>');
});
it('should not get confused by @', async () => {
// Model
function RootModel() {
return {text: '@div'};
}
// View
function Message({response}) {
return <p>{use(response).text}</p>;
}
function App({response}) {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Message response={response} />
</Suspense>
);
}
const destination = new Destination();
ReactServerDOMServer.renderToDestination(destination, <RootModel />);
const response = ReactServerDOMClient.createFromReadableStream(
destination.stream,
{
moduleMap,
},
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App response={response} />);
});
expect(container.innerHTML).toBe('<p>@div</p>');
});
it('should be able to render a client component', async () => {
const Component = function ({greeting}) {
return greeting + ' World';
};
function Print({response}) {
return <p>{use(response)}</p>;
}
function App({response}) {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Print response={response} />
</Suspense>
);
}
const ClientComponent = clientExports(Component);
const destination = new Destination();
ReactServerDOMServer.renderToDestination(
destination,
<ClientComponent greeting={'Hello'} />,
moduleMap,
);
const response = ReactServerDOMClient.createFromReadableStream(
destination.stream,
{
moduleMap: {
resolveClientReference(metadata) {
return {
getModuleId() {
return metadata.moduleId;
},
load() {
return Promise.resolve(Component);
},
};
},
},
},
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App response={response} />);
});
expect(container.innerHTML).toBe('<p>Hello World</p>');
});
it('should render long strings', async () => {
// Model
const longString = 'Lorem Ipsum ❤️ '.repeat(100);
function RootModel() {
return {text: longString};
}
// View
function Message({response}) {
return <p>{use(response).text}</p>;
}
function App({response}) {
return (
<Suspense fallback={<h1>Loading...</h1>}>
<Message response={response} />
</Suspense>
);
}
const destination = new Destination();
ReactServerDOMServer.renderToDestination(destination, <RootModel />);
const response = ReactServerDOMClient.createFromReadableStream(
destination.stream,
{
moduleMap,
},
);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App response={response} />);
});
expect(container.innerHTML).toBe('<p>' + longString + '</p>');
});
// TODO: `registerClientComponent` need to be able to support this
it.skip('throws when accessing a member below the client exports', () => {
const ClientModule = clientExports({
Component: {deep: 'thing'},
});
function dotting() {
return ClientModule.Component.deep;
}
expect(dotting).toThrowError(
'Cannot access Component.deep on the server. ' +
'You cannot dot into a client module from a server component. ' +
'You can only pass the imported name through.',
);
});
});

View File

@ -18,10 +18,6 @@ export opaque type PrecomputedChunk = string;
export opaque type Chunk = string; export opaque type Chunk = string;
export opaque type BinaryChunk = string; export opaque type BinaryChunk = string;
export function scheduleWork(callback: () => void) {
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
}
export function flushBuffered(destination: Destination) {} export function flushBuffered(destination: Destination) {}
export const supportsRequestStorage = false; export const supportsRequestStorage = false;

View File

@ -0,0 +1,16 @@
/**
* 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} from 'react-server/src/ReactFlightServer';
export * from 'react-server-dom-fb/src/ReactFlightServerConfigFBBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

View File

@ -0,0 +1,83 @@
/**
* 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 '../ReactServerStreamConfigFB';
import type {
PrecomputedChunk,
Chunk,
BinaryChunk,
} from '../ReactServerStreamConfigFB';
let byteLengthImpl: null | ((chunk: Chunk | PrecomputedChunk) => number) = null;
export function setByteLengthOfChunkImplementation(
impl: (chunk: Chunk | PrecomputedChunk) => number,
): void {
byteLengthImpl = impl;
}
export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
if (byteLengthImpl == null) {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'byteLengthOfChunk implementation is not configured. Please, provide the implementation via ReactFlightDOMServer.setConfig(...);',
);
}
return byteLengthImpl(chunk);
}
export interface Destination {
beginWriting(): void;
write(chunk: Chunk | PrecomputedChunk | BinaryChunk): void;
completeWriting(): void;
flushBuffered(): void;
close(): void;
onError(error: mixed): void;
}
export function scheduleWork(callback: () => void) {
callback();
}
export function beginWriting(destination: Destination) {
destination.beginWriting();
}
export function writeChunk(
destination: Destination,
chunk: Chunk | PrecomputedChunk | BinaryChunk,
): void {
destination.write(chunk);
}
export function writeChunkAndReturn(
destination: Destination,
chunk: Chunk | PrecomputedChunk | BinaryChunk,
): boolean {
destination.write(chunk);
return true;
}
export function completeWriting(destination: Destination) {
destination.completeWriting();
}
export function flushBuffered(destination: Destination) {
destination.flushBuffered();
}
export function close(destination: Destination) {
destination.close();
}
export function closeWithError(destination: Destination, error: mixed): void {
destination.onError(error);
destination.close();
}

View File

@ -8,3 +8,7 @@
*/ */
export * from '../ReactServerStreamConfigFB'; export * from '../ReactServerStreamConfigFB';
export function scheduleWork(callback: () => void) {
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
}

View File

@ -0,0 +1,11 @@
/**
* 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 './ReactSharedSubset';
export {jsx, jsxs, jsxDEV} from './jsx/ReactJSX';

View File

@ -75,7 +75,7 @@ export const enableFetchInstrumentation = false;
export const enableFormActions = false; export const enableFormActions = false;
export const enableBinaryFlight = true; export const enableBinaryFlight = false;
export const enableTaint = false; export const enableTaint = false;
export const enablePostpone = false; export const enablePostpone = false;

View File

@ -104,6 +104,17 @@ const bundles = [
externals: [], externals: [],
}, },
/******* Isomorphic Shared Subset for FB *******/
{
bundleTypes: __EXPERIMENTAL__ ? [FB_WWW_DEV, FB_WWW_PROD] : [],
moduleType: ISOMORPHIC,
entry: 'react/src/ReactSharedSubsetFB.js',
global: 'ReactSharedSubset',
minifyWithProdErrorCodes: true,
wrapWithModuleBoundaries: false,
externals: [],
},
/******* React JSX Runtime *******/ /******* React JSX Runtime *******/
{ {
bundleTypes: [ bundleTypes: [
@ -574,6 +585,28 @@ const bundles = [
externals: ['acorn'], externals: ['acorn'],
}, },
/******* React Server DOM FB Server *******/
{
bundleTypes: __EXPERIMENTAL__ ? [FB_WWW_DEV, FB_WWW_PROD] : [],
moduleType: RENDERER,
entry: 'react-server-dom-fb/src/ReactFlightDOMServerFB.js',
global: 'ReactFlightDOMServer',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom'],
},
/******* React Server DOM FB Client *******/
{
bundleTypes: __EXPERIMENTAL__ ? [FB_WWW_DEV, FB_WWW_PROD] : [],
moduleType: RENDERER,
entry: 'react-server-dom-fb/src/ReactFlightDOMClientFB.js',
global: 'ReactFlightDOMClient',
minifyWithProdErrorCodes: false,
wrapWithModuleBoundaries: false,
externals: ['react', 'react-dom'],
},
/******* React Suspense Test Utils *******/ /******* React Suspense Test Utils *******/
{ {
bundleTypes: [NODE_ES2015], bundleTypes: [NODE_ES2015],

View File

@ -63,7 +63,10 @@ const forks = Object.freeze({
if (entry === 'react') { if (entry === 'react') {
return './packages/react/src/ReactSharedInternalsClient.js'; return './packages/react/src/ReactSharedInternalsClient.js';
} }
if (entry === 'react/src/ReactSharedSubset.js') { if (
entry === 'react/src/ReactSharedSubset.js' ||
entry === 'react/src/ReactSharedSubsetFB.js'
) {
return './packages/react/src/ReactSharedInternalsServer.js'; return './packages/react/src/ReactSharedInternalsServer.js';
} }
if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) { if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) {

View File

@ -396,13 +396,34 @@ module.exports = [
'react-dom', 'react-dom',
'react-dom/src/ReactDOMSharedSubset.js', 'react-dom/src/ReactDOMSharedSubset.js',
'react-dom-bindings', 'react-dom-bindings',
'react-server-dom-fb', 'react-server-dom-fb/src/ReactDOMServerFB.js',
'shared/ReactDOMSharedInternals', 'shared/ReactDOMSharedInternals',
], ],
isFlowTyped: true, isFlowTyped: true,
isServerSupported: true, isServerSupported: true,
isFlightSupported: false, isFlightSupported: false,
}, },
{
shortName: 'dom-fb-experimental',
entryPoints: [
'react-server-dom-fb/src/ReactFlightDOMClientFB.js',
'react-server-dom-fb/src/ReactFlightDOMServerFB.js',
],
paths: [
'react-dom',
'react-dom-bindings',
'react-server-dom-fb/src/ReactFlightClientConfigFBBundler.js',
'react-server-dom-fb/src/ReactFlightClientConfigFBBundler.js',
'react-server-dom-fb/src/ReactFlightReferencesFB.js',
'react-server-dom-fb/src/ReactFlightServerConfigFBBundler.js',
'react-server-dom-fb/src/ReactFlightDOMClientFB.js',
'react-server-dom-fb/src/ReactFlightDOMServerFB.js',
'shared/ReactDOMSharedInternals',
],
isFlowTyped: true,
isServerSupported: true,
isFlightSupported: true,
},
{ {
shortName: 'native', shortName: 'native',
entryPoints: ['react-native-renderer'], entryPoints: ['react-native-renderer'],