mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
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:
parent
6c7b41da3d
commit
c17a27ef49
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
112
packages/react-server-dom-fb/src/ReactFlightClientConfigFBBundler.js
vendored
Normal file
112
packages/react-server-dom-fb/src/ReactFlightClientConfigFBBundler.js
vendored
Normal 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;
|
||||||
|
}
|
||||||
91
packages/react-server-dom-fb/src/ReactFlightDOMClientFB.js
vendored
Normal file
91
packages/react-server-dom-fb/src/ReactFlightDOMClientFB.js
vendored
Normal 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};
|
||||||
68
packages/react-server-dom-fb/src/ReactFlightDOMServerFB.js
vendored
Normal file
68
packages/react-server-dom-fb/src/ReactFlightDOMServerFB.js
vendored
Normal 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};
|
||||||
98
packages/react-server-dom-fb/src/ReactFlightReferencesFB.js
vendored
Normal file
98
packages/react-server-dom-fb/src/ReactFlightReferencesFB.js
vendored
Normal 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();
|
||||||
|
}
|
||||||
36
packages/react-server-dom-fb/src/ReactFlightServerConfigFBBundler.js
vendored
Normal file
36
packages/react-server-dom-fb/src/ReactFlightServerConfigFBBundler.js
vendored
Normal 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.');
|
||||||
|
}
|
||||||
|
|
@ -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.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
}
|
||||||
|
|
|
||||||
11
packages/react/src/ReactSharedSubsetFB.js
Normal file
11
packages/react/src/ReactSharedSubsetFB.js
Normal 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';
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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'],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user