Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 37 commits into from Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a04412c
[not ready for review] Attempt to create flight config for Meta
alunyov Oct 25, 2023
9bf0006
add fb flight configs to bundles
alunyov Oct 25, 2023
ad3123c
Fix build for Meta-only flight config
alunyov Oct 25, 2023
2140757
update config
alunyov Oct 25, 2023
3edeec4
moar changes to inlineHostConfig
alunyov Oct 25, 2023
c992b7a
update inlineHostConfig for dom-fb
alunyov Oct 27, 2023
cb25bf3
Build ReactSharedSubset for FB
alunyov Oct 27, 2023
1271ac0
Update scheduleWork
alunyov Oct 30, 2023
42bc05d
update snapshot
alunyov Oct 30, 2023
f0cd159
Update ReactSharedSubset for FB
alunyov Oct 31, 2023
e87b2b7
add client config
alunyov Nov 1, 2023
ec1160f
do not use `import`
alunyov Nov 1, 2023
8a9f150
a little more clean-up. and reduce the public API to one that we can …
alunyov Nov 5, 2023
e14b1b4
update to resolver clientreference metada
alunyov Nov 5, 2023
8e1e005
more updates to configs
alunyov Nov 5, 2023
95b2c10
Add TODO for ServerReferences impl for now
alunyov Nov 6, 2023
6fc1aa4
renaming of some of the load/require modules types
alunyov Nov 10, 2023
455b835
fix configs
alunyov Nov 10, 2023
c867c58
update client reference types
alunyov Nov 11, 2023
bcdbece
update bundles config
alunyov Nov 11, 2023
b62b35c
keep only minimal tests that covers supported cases
alunyov Nov 12, 2023
6f0e516
rename test to internal
alunyov Nov 12, 2023
87462eb
small refeactoring
alunyov Nov 12, 2023
242d607
moving things around, adding experimental config for dom-fb
alunyov Nov 13, 2023
36eca50
update inlineHostConfigs
alunyov Nov 13, 2023
1399881
update ReactSharedSubsetFB
alunyov Nov 13, 2023
3d6466c
more details in error messages
alunyov Nov 13, 2023
d6fc44b
handle feature flags
alunyov Nov 13, 2023
cec5500
WIP with new destination interface
alunyov Nov 13, 2023
6f3e799
fix flow/expose requested reference keys
alunyov Nov 14, 2023
456eb39
small refactoring
alunyov Nov 14, 2023
53e2503
Destination interface
alunyov Nov 14, 2023
bcaed1d
expose seen client components
alunyov Nov 15, 2023
89d6e15
renaming things for consistency
alunyov Nov 15, 2023
32c0389
Implement a polyfilled version of byteLengthOfChunk
alunyov Nov 17, 2023
0ebf40c
remove console.log
alunyov Nov 17, 2023
faa9d19
provide impl for byteLengthOfChunk via config
alunyov Nov 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Expand Up @@ -327,6 +327,7 @@ module.exports = {
'packages/react-server-dom-esm/**/*.js',
'packages/react-server-dom-webpack/**/*.js',
'packages/react-server-dom-turbopack/**/*.js',
'packages/react-server-dom-fb/**/*.js',
'packages/react-test-renderer/**/*.js',
'packages/react-debug-tools/**/*.js',
'packages/react-devtools-extensions/**/*.js',
Expand Down
@@ -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;
Expand Up @@ -8,7 +8,7 @@
*/

// 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';

Expand Down Expand Up @@ -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.
function refineModel<T>(code: T, model: HintModel<any>): HintModel<T> {
return model;
Expand Down
112 changes: 112 additions & 0 deletions packages/react-server-dom-fb/src/ReactFlightClientConfigFBBundler.js
@@ -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 changes: 91 additions & 0 deletions packages/react-server-dom-fb/src/ReactFlightDOMClientFB.js
@@ -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 changes: 68 additions & 0 deletions packages/react-server-dom-fb/src/ReactFlightDOMServerFB.js
@@ -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};