Compare commits

..

1 Commits
main ... token

Author SHA1 Message Date
Dan Abramov 523f2f0d48 Print a hint where to get the token 2023-05-05 20:01:46 +01:00
123 changed files with 2750 additions and 656 deletions

View File

@ -646,7 +646,7 @@ workflows:
name: Publish to Canary channel
commit_sha: << pipeline.git.revision >>
release_channel: stable
dist_tag: "canary,next"
dist_tag: "next"
- publish_prerelease:
name: Publish to Experimental channel
requires:

View File

@ -416,6 +416,7 @@ module.exports = {
{
files: [
'packages/react-native-renderer/**/*.js',
'packages/react-server-native-relay/**/*.js',
],
globals: {
nativeFabricUIManager: 'readonly',

View File

@ -65,8 +65,8 @@
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
"fbjs-scripts": "^3.0.1",
"filesize": "^6.0.1",
"flow-bin": "^0.205.1",
"flow-remove-types": "^2.205.1",
"flow-bin": "^0.202.0",
"flow-remove-types": "^2.202.0",
"glob": "^7.1.6",
"glob-stream": "^6.1.0",
"google-closure-compiler": "^20230206.0.0",

View File

@ -7,4 +7,4 @@
* @flow
*/
export * from './src/ReactFlightClient';
export * from './src/ReactFlightClientStream';

View File

@ -13,8 +13,9 @@ import type {LazyComponent} from 'react/src/ReactLazy';
import type {
ClientReference,
ClientReferenceMetadata,
UninitializedModel,
Response,
SSRManifest,
StringDecoder,
} from './ReactFlightClientConfig';
import type {HintModel} from 'react-server/src/ReactFlightServerConfig';
@ -25,11 +26,8 @@ import {
resolveClientReference,
preloadModule,
requireModule,
parseModel,
dispatchHint,
readPartialStringChunk,
readFinalStringChunk,
supportsBinaryStreams,
createStringDecoder,
} from './ReactFlightClientConfig';
import {
@ -43,8 +41,6 @@ import {getOrCreateServerContext} from 'shared/ReactServerContextRegistry';
export type {CallServerCallback};
type UninitializedModel = string;
export type JSONValue =
| number
| null
@ -162,15 +158,15 @@ Chunk.prototype.then = function <T>(
}
};
export type Response = {
export type ResponseBase = {
_bundlerConfig: SSRManifest,
_callServer: CallServerCallback,
_chunks: Map<number, SomeChunk<any>>,
_partialRow: string,
_fromJSON: (key: string, value: JSONValue) => any,
_stringDecoder: StringDecoder,
...
};
export type {Response};
function readChunk<T>(chunk: SomeChunk<T>): T {
// If we have resolved content, we try to initialize it first which
// might put us back into one of the other states.
@ -516,7 +512,7 @@ function createServerReferenceProxy<A: Iterable<any>, T>(
return proxy;
}
function parseModelString(
export function parseModelString(
response: Response,
parentObject: Object,
key: string,
@ -636,7 +632,7 @@ function parseModelString(
return value;
}
function parseModelTuple(
export function parseModelTuple(
response: Response,
value: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
): any {
@ -660,25 +656,17 @@ function missingCall() {
export function createResponse(
bundlerConfig: SSRManifest,
callServer: void | CallServerCallback,
): Response {
): ResponseBase {
const chunks: Map<number, SomeChunk<any>> = new Map();
const response: Response = {
const response = {
_bundlerConfig: bundlerConfig,
_callServer: callServer !== undefined ? callServer : missingCall,
_chunks: chunks,
_partialRow: '',
_stringDecoder: (null: any),
_fromJSON: (null: any),
};
if (supportsBinaryStreams) {
response._stringDecoder = createStringDecoder();
}
// Don't inline this call because it causes closure to outline the call above.
response._fromJSON = createFromJSONCallback(response);
return response;
}
function resolveModel(
export function resolveModel(
response: Response,
id: number,
model: UninitializedModel,
@ -692,7 +680,7 @@ function resolveModel(
}
}
function resolveModule(
export function resolveModule(
response: Response,
id: number,
model: UninitializedModel,
@ -741,7 +729,7 @@ function resolveModule(
}
type ErrorWithDigest = Error & {digest?: string};
function resolveErrorProd(
export function resolveErrorProd(
response: Response,
id: number,
digest: string,
@ -770,7 +758,7 @@ function resolveErrorProd(
}
}
function resolveErrorDev(
export function resolveErrorDev(
response: Response,
id: number,
digest: string,
@ -801,7 +789,7 @@ function resolveErrorDev(
}
}
function resolveHint(
export function resolveHint(
response: Response,
code: string,
model: UninitializedModel,
@ -810,105 +798,6 @@ function resolveHint(
dispatchHint(code, hintModel);
}
function processFullRow(response: Response, row: string): void {
if (row === '') {
return;
}
const colon = row.indexOf(':', 0);
const id = parseInt(row.slice(0, colon), 16);
const tag = row[colon + 1];
// When tags that are not text are added, check them here before
// parsing the row as text.
// switch (tag) {
// }
switch (tag) {
case 'I': {
resolveModule(response, id, row.slice(colon + 2));
return;
}
case 'H': {
const code = row[colon + 2];
resolveHint(response, code, row.slice(colon + 3));
return;
}
case 'E': {
const errorInfo = JSON.parse(row.slice(colon + 2));
if (__DEV__) {
resolveErrorDev(
response,
id,
errorInfo.digest,
errorInfo.message,
errorInfo.stack,
);
} else {
resolveErrorProd(response, id, errorInfo.digest);
}
return;
}
default: {
// We assume anything else is JSON.
resolveModel(response, id, row.slice(colon + 1));
return;
}
}
}
export function processStringChunk(
response: Response,
chunk: string,
offset: number,
): void {
let linebreak = chunk.indexOf('\n', offset);
while (linebreak > -1) {
const fullrow = response._partialRow + chunk.slice(offset, linebreak);
processFullRow(response, fullrow);
response._partialRow = '';
offset = linebreak + 1;
linebreak = chunk.indexOf('\n', offset);
}
response._partialRow += chunk.slice(offset);
}
export function processBinaryChunk(
response: Response,
chunk: Uint8Array,
): void {
if (!supportsBinaryStreams) {
throw new Error("This environment don't support binary chunks.");
}
const stringDecoder = response._stringDecoder;
let linebreak = chunk.indexOf(10); // newline
while (linebreak > -1) {
const fullrow =
response._partialRow +
readFinalStringChunk(stringDecoder, chunk.subarray(0, linebreak));
processFullRow(response, fullrow);
response._partialRow = '';
chunk = chunk.subarray(linebreak + 1);
linebreak = chunk.indexOf(10); // newline
}
response._partialRow += readPartialStringChunk(stringDecoder, chunk);
}
function parseModel<T>(response: Response, json: UninitializedModel): T {
return JSON.parse(json, response._fromJSON);
}
function createFromJSONCallback(response: Response) {
// $FlowFixMe[missing-this-annot]
return function (key: string, value: JSONValue) {
if (typeof value === 'string') {
// We can't use .bind here because we need the "this" value.
return parseModelString(response, this, key, value);
}
if (typeof value === 'object' && value !== null) {
return parseModelTuple(response, value);
}
return value;
};
}
export function close(response: Response): void {
// In case there are any remaining unresolved chunks, they won't
// be resolved now. So we need to issue an error to those.

View File

@ -0,0 +1,33 @@
/**
* 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 StringDecoder = void;
export const supportsBinaryStreams = false;
export function createStringDecoder(): void {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}
export function readPartialStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}
export function readFinalStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}

View File

@ -0,0 +1,23 @@
/**
* 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 {ResponseBase} from './ReactFlightClient';
import type {StringDecoder} from './ReactFlightClientConfig';
export type Response = ResponseBase & {
_partialRow: string,
_fromJSON: (key: string, value: JSONValue) => any,
_stringDecoder: StringDecoder,
};
export type UninitializedModel = string;
export function parseModel<T>(response: Response, json: UninitializedModel): T {
return JSON.parse(json, response._fromJSON);
}

View File

@ -0,0 +1,146 @@
/**
* 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 {CallServerCallback} from './ReactFlightClient';
import type {Response} from './ReactFlightClientConfigStream';
import type {SSRManifest} from './ReactFlightClientConfig';
import {
resolveModule,
resolveModel,
resolveErrorProd,
resolveErrorDev,
resolveHint,
createResponse as createResponseBase,
parseModelString,
parseModelTuple,
} from './ReactFlightClient';
import {
readPartialStringChunk,
readFinalStringChunk,
supportsBinaryStreams,
createStringDecoder,
} from './ReactFlightClientConfig';
export type {Response};
function processFullRow(response: Response, row: string): void {
if (row === '') {
return;
}
const colon = row.indexOf(':', 0);
const id = parseInt(row.slice(0, colon), 16);
const tag = row[colon + 1];
// When tags that are not text are added, check them here before
// parsing the row as text.
// switch (tag) {
// }
switch (tag) {
case 'I': {
resolveModule(response, id, row.slice(colon + 2));
return;
}
case 'H': {
const code = row[colon + 2];
resolveHint(response, code, row.slice(colon + 3));
return;
}
case 'E': {
const errorInfo = JSON.parse(row.slice(colon + 2));
if (__DEV__) {
resolveErrorDev(
response,
id,
errorInfo.digest,
errorInfo.message,
errorInfo.stack,
);
} else {
resolveErrorProd(response, id, errorInfo.digest);
}
return;
}
default: {
// We assume anything else is JSON.
resolveModel(response, id, row.slice(colon + 1));
return;
}
}
}
export function processStringChunk(
response: Response,
chunk: string,
offset: number,
): void {
let linebreak = chunk.indexOf('\n', offset);
while (linebreak > -1) {
const fullrow = response._partialRow + chunk.slice(offset, linebreak);
processFullRow(response, fullrow);
response._partialRow = '';
offset = linebreak + 1;
linebreak = chunk.indexOf('\n', offset);
}
response._partialRow += chunk.slice(offset);
}
export function processBinaryChunk(
response: Response,
chunk: Uint8Array,
): void {
if (!supportsBinaryStreams) {
throw new Error("This environment don't support binary chunks.");
}
const stringDecoder = response._stringDecoder;
let linebreak = chunk.indexOf(10); // newline
while (linebreak > -1) {
const fullrow =
response._partialRow +
readFinalStringChunk(stringDecoder, chunk.subarray(0, linebreak));
processFullRow(response, fullrow);
response._partialRow = '';
chunk = chunk.subarray(linebreak + 1);
linebreak = chunk.indexOf(10); // newline
}
response._partialRow += readPartialStringChunk(stringDecoder, chunk);
}
function createFromJSONCallback(response: Response) {
// $FlowFixMe[missing-this-annot]
return function (key: string, value: JSONValue) {
if (typeof value === 'string') {
// We can't use .bind here because we need the "this" value.
return parseModelString(response, this, key, value);
}
if (typeof value === 'object' && value !== null) {
return parseModelTuple(response, value);
}
return value;
};
}
export function createResponse(
bundlerConfig: SSRManifest,
callServer: void | CallServerCallback,
): Response {
// NOTE: CHECK THE COMPILER OUTPUT EACH TIME YOU CHANGE THIS.
// It should be inlined to one object literal but minor changes can break it.
const stringDecoder = supportsBinaryStreams ? createStringDecoder() : null;
const response: any = createResponseBase(bundlerConfig, callServer);
response._partialRow = '';
if (supportsBinaryStreams) {
response._stringDecoder = stringDecoder;
}
// Don't inline this call because it causes closure to outline the call above.
response._fromJSON = createFromJSONCallback(response);
return response;
}
export {reportGlobalError, getRoot, close} from './ReactFlightClient';

View File

@ -291,6 +291,7 @@ export function processReply(
// Possibly a Date, whose toJSON automatically calls toISOString
// $FlowFixMe[incompatible-use]
const originalValue = parent[key];
// $FlowFixMe[method-unbinding]
if (originalValue instanceof Date) {
return serializeDateFromDateJSON(value);
}

View File

@ -25,6 +25,7 @@
declare var $$$config: any;
export type Response = any;
export opaque type SSRManifest = mixed;
export opaque type ServerManifest = mixed;
export opaque type ServerReferenceId = string;
@ -38,6 +39,9 @@ export const dispatchHint = $$$config.dispatchHint;
export opaque type Source = mixed;
export type UninitializedModel = string;
export const parseModel = $$$config.parseModel;
export opaque type StringDecoder = mixed; // eslint-disable-line no-undef
export const supportsBinaryStreams = $$$config.supportsBinaryStreams;

View File

@ -8,5 +8,6 @@
*/
export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-client/src/ReactFlightClientConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

View File

@ -8,6 +8,7 @@
*/
export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-client/src/ReactFlightClientConfigStream';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
export type Response = any;

View File

@ -8,5 +8,6 @@
*/
export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-client/src/ReactFlightClientConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

View File

@ -8,5 +8,6 @@
*/
export * from 'react-client/src/ReactFlightClientConfigBrowser';
export * from 'react-client/src/ReactFlightClientConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

View File

@ -8,5 +8,6 @@
*/
export * from 'react-client/src/ReactFlightClientConfigNode';
export * from 'react-client/src/ReactFlightClientConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigWebpackBundler';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

View File

@ -8,5 +8,6 @@
*/
export * from 'react-client/src/ReactFlightClientConfigNode';
export * from 'react-client/src/ReactFlightClientConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightClientConfigNodeBundler';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

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
*/
export * from 'react-server-dom-relay/src/ReactFlightClientConfigDOMRelay';
export * from '../ReactFlightClientConfigNoStream';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

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 'react-server-native-relay/src/ReactFlightClientConfigNativeRelay';
export * from '../ReactFlightClientConfigNoStream';

View File

@ -1,6 +1,6 @@
{
"name": "react-devtools-core",
"version": "4.27.8",
"version": "4.27.7",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"main": "./dist/backend.js",

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and its affiliates.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.

View File

@ -167,6 +167,7 @@ function onDisconnected() {
disconnectedCallback();
}
// $FlowFixMe[missing-local-annot]
function onError({code, message}: $FlowFixMe) {
safeUnmount();

View File

@ -2,8 +2,8 @@
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Chrome Developer Tools.",
"version": "4.27.8",
"version_name": "4.27.8",
"version": "4.27.7",
"version_name": "4.27.7",
"minimum_chrome_version": "102",
"icons": {
"16": "icons/16-production.png",

View File

@ -2,8 +2,8 @@
"manifest_version": 3,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Microsoft Edge Developer Tools.",
"version": "4.27.8",
"version_name": "4.27.8",
"version": "4.27.7",
"version_name": "4.27.7",
"minimum_chrome_version": "102",
"icons": {
"16": "icons/16-production.png",

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Firefox Developer Tools.",
"version": "4.27.8",
"version": "4.27.7",
"applications": {
"gecko": {
"id": "@react-devtools",

View File

@ -11,7 +11,7 @@
/* eslint-disable no-unused-vars */
type JestMockFn<TArguments: $ReadOnlyArray<any>, TReturn> = {
type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
(...args: TArguments): TReturn,
/**
* An object for introspecting mock calls
@ -626,7 +626,7 @@ interface JestExpectType {
* Use .toBeInstanceOf(Class) to check that an object is an instance of a
* class.
*/
toBeInstanceOf(cls: Class<any>): void;
toBeInstanceOf(cls: Class<*>): void;
/**
* .toBeNull() is the same as .toBe(null) but the error messages are a bit
* nicer.
@ -815,7 +815,7 @@ type JestObjectType = {
* Returns a new, unused mock function. Optionally takes a mock
* implementation.
*/
fn<TArguments: $ReadOnlyArray<any>, TReturn>(
fn<TArguments: $ReadOnlyArray<*>, TReturn>(
implementation?: (...args: TArguments) => TReturn
): JestMockFn<TArguments, TReturn>,
/**

View File

@ -29,6 +29,5 @@ function setup(hook: ?DevToolsHook) {
initBackend,
setupNativeStyleEditor,
});
hook.emit('devtools-backend-installed', COMPACT_VERSION_NAME);
}

View File

@ -16,6 +16,7 @@ import {COMPACT_VERSION_NAME} from './utils';
let welcomeHasInitialized = false;
// $FlowFixMe[missing-local-annot]
function welcome(event: $FlowFixMe) {
if (
event.source !== window ||
@ -58,7 +59,7 @@ function setup(hook: ?DevToolsHook) {
// register renderers that have already injected themselves.
hook.renderers.forEach(renderer => {
registerRenderer(renderer, hook);
registerRenderer(renderer);
});
// Activate and remove from required all present backends, registered within the hook
@ -71,7 +72,7 @@ function setup(hook: ?DevToolsHook) {
// register renderers that inject themselves later.
hook.sub('renderer', ({renderer}) => {
registerRenderer(renderer, hook);
registerRenderer(renderer);
updateRequiredBackends();
});
@ -84,16 +85,12 @@ function setup(hook: ?DevToolsHook) {
const requiredBackends = new Set<string>();
function registerRenderer(renderer: ReactRenderer, hook: DevToolsHook) {
function registerRenderer(renderer: ReactRenderer) {
let version = renderer.reconcilerVersion || renderer.version;
if (!hasAssignedBackend(version)) {
version = COMPACT_VERSION_NAME;
}
// Check if required backend is already activated, no need to require again
if (!hook.backends.has(version)) {
requiredBackends.add(version);
}
requiredBackends.add(version);
}
function activateBackend(version: string, hook: DevToolsHook) {
@ -101,7 +98,6 @@ function activateBackend(version: string, hook: DevToolsHook) {
if (!backend) {
throw new Error(`Could not find backend for version "${version}"`);
}
const {Agent, Bridge, initBackend, setupNativeStyleEditor} = backend;
const bridge = new Bridge({
listen(fn) {
@ -162,10 +158,6 @@ function activateBackend(version: string, hook: DevToolsHook) {
// tell the service worker which versions of backends are needed for the current page
function updateRequiredBackends() {
if (requiredBackends.size === 0) {
return;
}
window.postMessage(
{
source: 'react-devtools-backend-manager',

View File

@ -1,6 +1,6 @@
{
"name": "react-devtools-inline",
"version": "4.27.8",
"version": "4.27.7",
"description": "Embed react-devtools within a website",
"license": "MIT",
"main": "./dist/backend.js",

View File

@ -244,6 +244,7 @@ export function attach(
parentIDStack.pop();
return result;
} catch (err) {
// $FlowFixMe[incompatible-type] found when upgrading Flow
parentIDStack = [];
throw err;
} finally {
@ -280,6 +281,7 @@ export function attach(
parentIDStack.pop();
return result;
} catch (err) {
// $FlowFixMe[incompatible-type] found when upgrading Flow
parentIDStack = [];
throw err;
} finally {
@ -316,6 +318,7 @@ export function attach(
parentIDStack.pop();
return result;
} catch (err) {
// $FlowFixMe[incompatible-type] found when upgrading Flow
parentIDStack = [];
throw err;
} finally {
@ -347,6 +350,7 @@ export function attach(
return result;
} catch (err) {
// $FlowFixMe[incompatible-type] found when upgrading Flow
parentIDStack = [];
throw err;
} finally {

View File

@ -54,10 +54,7 @@ let supportsUserTiming =
let supportsUserTimingV3 = false;
if (supportsUserTiming) {
const CHECK_V3_MARK = '__v3';
const markOptions: {
detail?: mixed,
startTime?: number,
} = {};
const markOptions = ({}: {startTime?: number});
Object.defineProperty(markOptions, 'startTime', {
get: function () {
supportsUserTimingV3 = true;
@ -67,6 +64,7 @@ if (supportsUserTiming) {
});
try {
// $FlowFixMe[extra-arg]: Flow expects the User Timing level 2 API.
performance.mark(CHECK_V3_MARK, markOptions);
} catch (error) {
// Ignore

View File

@ -237,7 +237,7 @@ export default function InspectedElementWrapper(_: Props): React.Node {
strictModeBadge = (
<a
className={styles.StrictModeNonCompliant}
href="https://react.dev/reference/react/StrictMode"
href="https://fb.me/devtools-strict-mode"
rel="noopener noreferrer"
target="_blank"
title="This component is not running in StrictMode. Click to learn more.">

View File

@ -45,6 +45,7 @@ const resource: Resource<
(element: Element) => {
const request = inProgressRequests.get(element);
if (request != null) {
// $FlowFixMe[incompatible-call] found when upgrading Flow
return request.promise;
}

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and its affiliates.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and its affiliates.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.

View File

@ -1,7 +1,7 @@
{
"private": true,
"name": "react-devtools-timeline",
"version": "4.27.8",
"version": "4.27.7",
"license": "MIT",
"dependencies": {
"@elg/speedscope": "1.9.0-a6f84db",

View File

@ -4,14 +4,6 @@
---
### 4.27.8
May 17, 2023
#### Bugfixes
* Fixed duplicated backend activation with multiple renderers ([hoxyq](https://github.com/hoxyq) in [#26807](https://github.com/facebook/react/pull/26807))
---
### 4.27.7
May 4, 2023

View File

@ -1,6 +1,6 @@
{
"name": "react-devtools",
"version": "4.27.8",
"version": "4.27.7",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"repository": {
@ -27,7 +27,7 @@
"electron": "^23.1.2",
"ip": "^1.1.4",
"minimist": "^1.2.3",
"react-devtools-core": "4.27.8",
"react-devtools-core": "4.27.7",
"update-notifier": "^2.1.0"
}
}

View File

@ -472,6 +472,7 @@ function addTrappedEventListener(
if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
const originalListener = listener;
// $FlowFixMe[missing-this-annot]
// $FlowFixMe[definition-cycle]
listener = function (...p) {
removeEventListener(
targetContainer,

View File

@ -46,6 +46,7 @@ if (document.body != null) {
}
});
// documentElement must already exist at this point
// $FlowFixMe[incompatible-call]
domBodyObserver.observe(document.documentElement, {childList: true});
}

View File

@ -22,5 +22,4 @@ export {
preconnect,
preload,
preinit,
experimental_useFormStatus,
} from './src/server/ReactDOMServerRenderingStub';

View File

@ -20,7 +20,6 @@ let JSDOM;
let Stream;
let Scheduler;
let React;
let ReactDOM;
let ReactDOMClient;
let ReactDOMFizzServer;
let Suspense;
@ -74,7 +73,6 @@ describe('ReactDOMFizzServer', () => {
Scheduler = require('scheduler');
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
ReactDOMFizzServer = require('react-dom/server');
Stream = require('stream');
@ -2509,98 +2507,6 @@ describe('ReactDOMFizzServer', () => {
},
);
it('can hydrate uSES in StrictMode with different client and server snapshot (sync)', async () => {
function subscribe() {
return () => {};
}
function getClientSnapshot() {
return 'Yay!';
}
function getServerSnapshot() {
return 'Nay!';
}
function App() {
const value = useSyncExternalStore(
subscribe,
getClientSnapshot,
getServerSnapshot,
);
Scheduler.log(value);
return value;
}
const element = (
<React.StrictMode>
<App />
</React.StrictMode>
);
await act(async () => {
const {pipe} = renderToPipeableStream(element);
pipe(writable);
});
assertLog(['Nay!']);
expect(getVisibleChildren(container)).toEqual('Nay!');
await clientAct(() => {
ReactDOM.flushSync(() => {
ReactDOMClient.hydrateRoot(container, element);
});
});
expect(getVisibleChildren(container)).toEqual('Yay!');
assertLog(['Nay!', 'Yay!']);
});
it('can hydrate uSES in StrictMode with different client and server snapshot (concurrent)', async () => {
function subscribe() {
return () => {};
}
function getClientSnapshot() {
return 'Yay!';
}
function getServerSnapshot() {
return 'Nay!';
}
function App() {
const value = useSyncExternalStore(
subscribe,
getClientSnapshot,
getServerSnapshot,
);
Scheduler.log(value);
return value;
}
const element = (
<React.StrictMode>
<App />
</React.StrictMode>
);
await act(async () => {
const {pipe} = renderToPipeableStream(element);
pipe(writable);
});
assertLog(['Nay!']);
expect(getVisibleChildren(container)).toEqual('Nay!');
await clientAct(() => {
React.startTransition(() => {
ReactDOMClient.hydrateRoot(container, element);
});
});
expect(getVisibleChildren(container)).toEqual('Yay!');
assertLog(['Nay!', 'Yay!']);
});
it(
'errors during hydration force a client render at the nearest Suspense ' +
'boundary, and during the client render it recovers',

View File

@ -1,5 +1,5 @@
/**
* Copyright (c) Meta Platforms, Inc. and its affiliates.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.

View File

@ -81,16 +81,4 @@ describe('react-dom-server-rendering-stub', () => {
);
expect(x).toBe(false);
});
// @gate enableFormActions
// @gate enableAsyncActions
it('exports experimental_useFormStatus', async () => {
function App() {
const {pending} = ReactDOM.experimental_useFormStatus();
return 'Pending: ' + pending;
}
const result = await ReactDOMFizzServer.renderToStaticMarkup(<App />);
expect(result).toEqual('Pending: false');
});
});

View File

@ -8,7 +8,6 @@
*/
export {preinit, preload, preconnect, prefetchDNS} from '../ReactDOMFloat';
export {useFormStatus as experimental_useFormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
export function createPortal() {
throw new Error(

View File

@ -8,9 +8,9 @@
"directory": "packages/react-native-renderer"
},
"dependencies": {
"scheduler": "^0.23.0"
"scheduler": "^0.11.0"
},
"peerDependencies": {
"react": "^18.0.0"
"react": "^17.0.0"
}
}

View File

@ -13,7 +13,6 @@ import {dispatchEvent} from './ReactFabricEventEmitter';
import {
DefaultEventPriority,
DiscreteEventPriority,
type EventPriority,
} from 'react-reconciler/src/ReactEventPriorities';
import {HostText} from 'react-reconciler/src/ReactWorkTags';
@ -318,7 +317,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
return false;
}
export function getCurrentEventPriority(): EventPriority {
export function getCurrentEventPriority(): * {
const currentEventPriority = fabricGetCurrentEventPriority
? fabricGetCurrentEventPriority()
: null;

View File

@ -24,10 +24,7 @@ import {
} from './ReactNativeComponentTree';
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
import {
DefaultEventPriority,
type EventPriority,
} from 'react-reconciler/src/ReactEventPriorities';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
@ -220,10 +217,9 @@ export function getChildHostContext(
}
}
export function getPublicInstance(instance: Instance): PublicInstance {
export function getPublicInstance(instance: Instance): * {
// $FlowExpectedError[prop-missing] For compatibility with Fabric
if (instance.canonical != null && instance.canonical.publicInstance != null) {
// $FlowFixMe[incompatible-return]
return instance.canonical.publicInstance;
}
@ -266,7 +262,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
return false;
}
export function getCurrentEventPriority(): EventPriority {
export function getCurrentEventPriority(): * {
return DefaultEventPriority;
}

View File

@ -0,0 +1,359 @@
/**
* 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 {ReactNodeList} from 'shared/ReactTypes';
import type {
Destination,
Chunk,
PrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';
import {
writeChunk,
writeChunkAndReturn,
stringToChunk,
stringToPrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';
export const isPrimaryRenderer = true;
// Every list of children or string is null terminated.
const END_TAG = 0;
// Tree node tags.
const INSTANCE_TAG = 1;
const PLACEHOLDER_TAG = 2;
const SUSPENSE_PENDING_TAG = 3;
const SUSPENSE_COMPLETE_TAG = 4;
const SUSPENSE_CLIENT_RENDER_TAG = 5;
// Command tags.
const SEGMENT_TAG = 1;
const SUSPENSE_UPDATE_TO_COMPLETE_TAG = 2;
const SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG = 3;
const END = new Uint8Array(1);
END[0] = END_TAG;
const PLACEHOLDER = new Uint8Array(1);
PLACEHOLDER[0] = PLACEHOLDER_TAG;
const INSTANCE = new Uint8Array(1);
INSTANCE[0] = INSTANCE_TAG;
const SUSPENSE_PENDING = new Uint8Array(1);
SUSPENSE_PENDING[0] = SUSPENSE_PENDING_TAG;
const SUSPENSE_COMPLETE = new Uint8Array(1);
SUSPENSE_COMPLETE[0] = SUSPENSE_COMPLETE_TAG;
const SUSPENSE_CLIENT_RENDER = new Uint8Array(1);
SUSPENSE_CLIENT_RENDER[0] = SUSPENSE_CLIENT_RENDER_TAG;
const SEGMENT = new Uint8Array(1);
SEGMENT[0] = SEGMENT_TAG;
const SUSPENSE_UPDATE_TO_COMPLETE = new Uint8Array(1);
SUSPENSE_UPDATE_TO_COMPLETE[0] = SUSPENSE_UPDATE_TO_COMPLETE_TAG;
const SUSPENSE_UPDATE_TO_CLIENT_RENDER = new Uint8Array(1);
SUSPENSE_UPDATE_TO_CLIENT_RENDER[0] = SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG;
export type Resources = void;
export type BoundaryResources = void;
// Per response,
export type ResponseState = {
nextSuspenseID: number,
};
// Allows us to keep track of what we've already written so we can refer back to it.
export function createResponseState(): ResponseState {
return {
nextSuspenseID: 0,
};
}
// isInAParentText
export type FormatContext = boolean;
export function createRootFormatContext(): FormatContext {
return false;
}
export function getChildFormatContext(
parentContext: FormatContext,
type: string,
props: Object,
): FormatContext {
const prevIsInAParentText = parentContext;
const isInAParentText =
type === 'AndroidTextInput' || // Android
type === 'RCTMultilineTextInputView' || // iOS
type === 'RCTSinglelineTextInputView' || // iOS
type === 'RCTText' ||
type === 'RCTVirtualText';
if (prevIsInAParentText !== isInAParentText) {
return isInAParentText;
} else {
return parentContext;
}
}
// This object is used to lazily reuse the ID of the first generated node, or assign one.
// This is very specific to DOM where we can't assign an ID to.
export type SuspenseBoundaryID = number;
export const UNINITIALIZED_SUSPENSE_BOUNDARY_ID = -1;
export function assignSuspenseBoundaryID(
responseState: ResponseState,
): SuspenseBoundaryID {
return responseState.nextSuspenseID++;
}
export function makeId(
responseState: ResponseState,
treeId: string,
localId: number,
): string {
throw new Error('Not implemented');
}
const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');
export function pushTextInstance(
target: Array<Chunk | PrecomputedChunk>,
text: string,
responseState: ResponseState,
// This Renderer does not use this argument
textEmbedded: boolean,
): boolean {
target.push(
INSTANCE,
RAW_TEXT, // Type
END, // Null terminated type string
// TODO: props { text: text }
END, // End of children
);
return false;
}
export function pushStartInstance(
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
resources: Resources,
responseState: ResponseState,
formatContext: FormatContext,
textEmbedded: boolean,
): ReactNodeList {
target.push(
INSTANCE,
stringToChunk(type),
END, // Null terminated type string
// TODO: props
);
return props.children;
}
export function pushEndInstance(
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
responseState: ResponseState,
formatContext: FormatContext,
): void {
target.push(END);
}
// In this Renderer this is a noop
export function pushSegmentFinale(
target: Array<Chunk | PrecomputedChunk>,
responseState: ResponseState,
lastPushedText: boolean,
textEmbedded: boolean,
): void {}
export function writeCompletedRoot(
destination: Destination,
responseState: ResponseState,
): boolean {
return true;
}
// IDs are formatted as little endian Uint16
function formatID(id: number): Uint8Array {
if (id > 0xffff) {
throw new Error(
'More boundaries or placeholders than we expected to ever emit.',
);
}
const buffer = new Uint8Array(2);
buffer[0] = (id >>> 8) & 0xff;
buffer[1] = id & 0xff;
return buffer;
}
// Structural Nodes
// A placeholder is a node inside a hidden partial tree that can be filled in later, but before
// display. It's never visible to users.
export function writePlaceholder(
destination: Destination,
responseState: ResponseState,
id: number,
): boolean {
writeChunk(destination, PLACEHOLDER);
return writeChunkAndReturn(destination, formatID(id));
}
// Suspense boundaries are encoded as comments.
export function writeStartCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
): boolean {
return writeChunkAndReturn(destination, SUSPENSE_COMPLETE);
}
export function pushStartCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(SUSPENSE_COMPLETE);
}
export function writeStartPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
id: SuspenseBoundaryID,
): boolean {
writeChunk(destination, SUSPENSE_PENDING);
return writeChunkAndReturn(destination, formatID(id));
}
export function writeStartClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
// TODO: encode error for native
errorDigest: ?string,
errorMessage: ?string,
errorComponentStack: ?string,
): boolean {
return writeChunkAndReturn(destination, SUSPENSE_CLIENT_RENDER);
}
export function writeEndCompletedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
): boolean {
return writeChunkAndReturn(destination, END);
}
export function pushEndCompletedSuspenseBoundary(
target: Array<Chunk | PrecomputedChunk>,
): void {
target.push(END);
}
export function writeEndPendingSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
): boolean {
return writeChunkAndReturn(destination, END);
}
export function writeEndClientRenderedSuspenseBoundary(
destination: Destination,
responseState: ResponseState,
): boolean {
return writeChunkAndReturn(destination, END);
}
export function writeStartSegment(
destination: Destination,
responseState: ResponseState,
formatContext: FormatContext,
id: number,
): boolean {
writeChunk(destination, SEGMENT);
return writeChunkAndReturn(destination, formatID(id));
}
export function writeEndSegment(
destination: Destination,
formatContext: FormatContext,
): boolean {
return writeChunkAndReturn(destination, END);
}
// Instruction Set
export function writeCompletedSegmentInstruction(
destination: Destination,
responseState: ResponseState,
contentSegmentID: number,
): boolean {
// We don't need to emit this. Instead the client will keep track of pending placeholders.
// TODO: Returning true here is not correct. Avoid having to call this function at all.
return true;
}
export function writeCompletedBoundaryInstruction(
destination: Destination,
responseState: ResponseState,
boundaryID: SuspenseBoundaryID,
contentSegmentID: number,
resources: BoundaryResources,
): boolean {
writeChunk(destination, SUSPENSE_UPDATE_TO_COMPLETE);
writeChunk(destination, formatID(boundaryID));
return writeChunkAndReturn(destination, formatID(contentSegmentID));
}
export function writeClientRenderBoundaryInstruction(
destination: Destination,
responseState: ResponseState,
boundaryID: SuspenseBoundaryID,
// TODO: encode error for native
errorDigest: ?string,
errorMessage: ?string,
errorComponentStack: ?string,
): boolean {
writeChunk(destination, SUSPENSE_UPDATE_TO_CLIENT_RENDER);
return writeChunkAndReturn(destination, formatID(boundaryID));
}
export function writePreamble(
destination: Destination,
resources: Resources,
responseState: ResponseState,
willFlushAllSegments: boolean,
) {}
export function writeHoistables(
destination: Destination,
resources: Resources,
responseState: ResponseState,
) {}
export function writePostamble(
destination: Destination,
responseState: ResponseState,
) {}
export function hoistResources(
resources: Resources,
boundaryResources: BoundaryResources,
) {}
export function prepareHostDispatcher() {}
export function createResources() {}
export function createBoundaryResources() {}
export function setCurrentlyRenderingBoundaryResourcesTarget(
resources: Resources,
boundaryResources: ?BoundaryResources,
) {}
export function writeResourcesForBoundary(
destination: Destination,
boundaryResources: BoundaryResources,
responseState: ResponseState,
): boolean {
return true;
}
export type TransitionStatus = mixed;
export const NotPendingTransition: TransitionStatus = null;

View File

@ -0,0 +1,19 @@
/**
* 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 const isPrimaryRenderer = true;
export type Hints = null;
export type HintModel = '';
export function createHints(): null {
return null;
}
export function prepareHostDispatcher() {}

View File

@ -55,7 +55,6 @@ import {
enableLegacyHidden,
enableHostSingletons,
diffInCommitPhase,
alwaysThrottleRetries,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
@ -2906,35 +2905,17 @@ function commitMutationEffectsOnFiber(
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
// TODO: We should mark a flag on the Suspense fiber itself, rather than
// relying on the Offscreen fiber having a flag also being marked. The
// reason is that this offscreen fiber might not be part of the work-in-
// progress tree! It could have been reused from a previous render. This
// doesn't lead to incorrect behavior because we don't rely on the flag
// check alone; we also compare the states explicitly below. But for
// modeling purposes, we _should_ be able to rely on the flag check alone.
// So this is a bit fragile.
//
// Also, all this logic could/should move to the passive phase so it
// doesn't block paint.
const offscreenFiber: Fiber = (finishedWork.child: any);
if (offscreenFiber.flags & Visibility) {
// Throttle the appearance and disappearance of Suspense fallbacks.
const isShowingFallback =
(finishedWork.memoizedState: SuspenseState | null) !== null;
const wasShowingFallback =
current !== null &&
(current.memoizedState: SuspenseState | null) !== null;
if (alwaysThrottleRetries) {
if (isShowingFallback !== wasShowingFallback) {
// A fallback is either appearing or disappearing.
markCommitTimeOfFallback();
}
} else {
if (isShowingFallback && !wasShowingFallback) {
// Old behavior. Only mark when a fallback appears, not when
// it disappears.
if (offscreenFiber.flags & Visibility) {
const newState: OffscreenState | null = offscreenFiber.memoizedState;
const isHidden = newState !== null;
if (isHidden) {
const wasHidden =
offscreenFiber.alternate !== null &&
offscreenFiber.alternate.memoizedState !== null;
if (!wasHidden) {
// TODO: Move to passive phase
markCommitTimeOfFallback();
}
}

View File

@ -1776,6 +1776,8 @@ function mountSyncExternalStore<T>(
// clean-up function, and we track the deps correctly, we can call pushEffect
// directly, without storing any additional state. For the same reason, we
// don't need to set a static flag, either.
// TODO: We can move this to the passive phase once we add a pre-commit
// consistency check. See the next comment.
fiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
@ -1797,28 +1799,15 @@ function updateSyncExternalStore<T>(
// Read the current snapshot from the store on every render. This breaks the
// normal rules of React, and only works because store updates are
// always synchronous.
let nextSnapshot;
const isHydrating = getIsHydrating();
if (isHydrating) {
// Needed for strict mode double render
if (getServerSnapshot === undefined) {
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
nextSnapshot = getServerSnapshot();
} else {
nextSnapshot = getSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
const cachedSnapshot = getSnapshot();
if (!is(nextSnapshot, cachedSnapshot)) {
console.error(
'The result of getSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
const nextSnapshot = getSnapshot();
if (__DEV__) {
if (!didWarnUncachedGetSnapshot) {
const cachedSnapshot = getSnapshot();
if (!is(nextSnapshot, cachedSnapshot)) {
console.error(
'The result of getSnapshot should be cached to avoid an infinite loop',
);
didWarnUncachedGetSnapshot = true;
}
}
}
@ -1841,7 +1830,7 @@ function updateSyncExternalStore<T>(
if (
inst.getSnapshot !== getSnapshot ||
snapshotChanged ||
// Check if the subscribe function changed. We can save some memory by
// Check if the susbcribe function changed. We can save some memory by
// checking whether we scheduled a subscription effect above.
(workInProgressHook !== null &&
workInProgressHook.memoizedState.tag & HookHasEffect)
@ -1865,7 +1854,7 @@ function updateSyncExternalStore<T>(
);
}
if (!isHydrating && !includesBlockingLane(root, renderLanes)) {
if (!includesBlockingLane(root, renderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
@ -2228,8 +2217,7 @@ function updateEffectImpl(
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
// currentHook is null on initial mount when rerendering after a render phase
// state update or for strict mode.
// currentHook is null when rerendering after a render phase state update.
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;

View File

@ -370,12 +370,10 @@ let workInProgressRootConcurrentErrors: Array<CapturedValue<mixed>> | null =
let workInProgressRootRecoverableErrors: Array<CapturedValue<mixed>> | null =
null;
// The most recent time we either committed a fallback, or when a fallback was
// filled in with the resolved UI. This lets us throttle the appearance of new
// content as it streams in, to minimize jank.
// TODO: Think of a better name for this variable?
// The most recent time we committed a fallback. This lets us ensure a train
// model where we don't commit new loading states in too quick succession.
let globalMostRecentFallbackTime: number = 0;
const FALLBACK_THROTTLE_MS: number = 300;
const FALLBACK_THROTTLE_MS: number = 500;
// The absolute time for when we should start giving up on rendering
// more and prefer CPU suspense heuristics instead.

View File

@ -1811,102 +1811,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
// @gate enableLegacyCache
it('throttles content from appearing if a fallback was filled in recently', async () => {
function Foo() {
Scheduler.log('Foo');
return (
<>
<Suspense fallback={<Text text="Loading A..." />}>
<AsyncText text="A" />
</Suspense>
<Suspense fallback={<Text text="Loading B..." />}>
<AsyncText text="B" />
</Suspense>
</>
);
}
ReactNoop.render(<Foo />);
// Start rendering
await waitForAll([
'Foo',
'Suspend! [A]',
'Loading A...',
'Suspend! [B]',
'Loading B...',
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="Loading A..." />
<span prop="Loading B..." />
</>,
);
// Resolve only A. B will still be loading.
await act(async () => {
await resolveText('A');
// If we didn't advance the time here, A would not commit; it would
// be throttled because the fallback would have appeared too recently.
Scheduler.unstable_advanceTime(10000);
jest.advanceTimersByTime(10000);
await waitForPaint(['A']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="A" />
<span prop="Loading B..." />
</>,
);
});
// Advance by a small amount of time. For testing purposes, this is meant
// to be just under the throttling interval. It's a heurstic, though, so
// if we adjust the heuristic we might have to update this test, too.
Scheduler.unstable_advanceTime(200);
jest.advanceTimersByTime(200);
// Now resolve B.
await act(async () => {
await resolveText('B');
await waitForPaint(['B']);
if (gate(flags => flags.alwaysThrottleRetries)) {
// B should not commit yet. Even though it's been a long time since its
// fallback was shown, it hasn't been long since A appeared. So B's
// appearance is throttled to reduce jank.
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="A" />
<span prop="Loading B..." />
</>,
);
// Advance time a little bit more. Now it commits because enough time
// has passed.
Scheduler.unstable_advanceTime(100);
jest.advanceTimersByTime(100);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="A" />
<span prop="B" />
</>,
);
} else {
// Old behavior, gated until this rolls out at Meta:
//
// B appears immediately, without being throttled.
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="A" />
<span prop="B" />
</>,
);
}
});
});
// TODO: flip to "warns" when this is implemented again.
// @gate enableLegacyCache
it('does not warn when a low priority update suspends inside a high priority update for functional components', async () => {

View File

@ -7,4 +7,4 @@
* @flow
*/
export * from '../../../react-server-dom-fb/src/ReactServerStreamConfigFB';
export * from 'react-native-renderer/src/ReactFiberConfigFabric';

View File

@ -657,6 +657,7 @@ export function createSignatureFunctionForTransform(): <T>(
// in HOC chains like _s(hoc1(_s(hoc2(_s(actualFunction))))).
if (!savedType) {
// We're in the innermost call, so this is the actual type.
// $FlowFixMe[escaped-generic] discovered when updating Flow
savedType = type;
hasCustomHooks = typeof getCustomHooks === 'function';
}

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

View File

@ -0,0 +1,17 @@
{
"name": "react-server-dom-relay",
"version": "0.1.0",
"private": true,
"repository": {
"type" : "git",
"url" : "https://github.com/facebook/react.git",
"directory": "packages/react-server-dom-relay"
},
"dependencies": {
"scheduler": "^0.11.0"
},
"peerDependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
}
}

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

View File

@ -0,0 +1,97 @@
/**
* 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 {JSONValue, ResponseBase} from 'react-client/src/ReactFlightClient';
import type {JSResourceReference} from 'JSResourceReference';
import type {ClientReferenceMetadata} from 'ReactFlightDOMRelayClientIntegration';
export type ClientReference<T> = JSResourceReference<T>;
import {
parseModelString,
parseModelTuple,
} from 'react-client/src/ReactFlightClient';
export {
preloadModule,
requireModule,
} from 'ReactFlightDOMRelayClientIntegration';
import {resolveClientReference as resolveClientReferenceImpl} from 'ReactFlightDOMRelayClientIntegration';
import isArray from 'shared/isArray';
export type {ClientReferenceMetadata} from 'ReactFlightDOMRelayClientIntegration';
export type SSRManifest = null;
export type ServerManifest = null;
export type ServerReferenceId = string;
export type UninitializedModel = JSONValue;
export type Response = ResponseBase;
export function resolveClientReference<T>(
bundlerConfig: SSRManifest,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
return resolveClientReferenceImpl(metadata);
}
export function resolveServerReference<T>(
bundlerConfig: ServerManifest,
id: ServerReferenceId,
): ClientReference<T> {
throw new Error('Not implemented.');
}
function parseModelRecursively(
response: Response,
parentObj: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
key: string,
value: JSONValue,
): $FlowFixMe {
if (typeof value === 'string') {
return parseModelString(response, parentObj, key, value);
}
if (typeof value === 'object' && value !== null) {
if (isArray(value)) {
const parsedValue: Array<$FlowFixMe> = [];
for (let i = 0; i < value.length; i++) {
(parsedValue: any)[i] = parseModelRecursively(
response,
value,
'' + i,
value[i],
);
}
return parseModelTuple(response, parsedValue);
} else {
const parsedValue = {};
for (const innerKey in value) {
(parsedValue: any)[innerKey] = parseModelRecursively(
response,
value,
innerKey,
value[innerKey],
);
}
return parsedValue;
}
}
return value;
}
const dummy = {};
export function parseModel<T>(response: Response, json: UninitializedModel): T {
return (parseModelRecursively(response, dummy, '', json): any);
}

View File

@ -0,0 +1,61 @@
/**
* 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 {RowEncoding} from './ReactFlightDOMRelayProtocol';
import type {Response} from 'react-client/src/ReactFlightClient';
import {
createResponse,
resolveModel,
resolveModule,
resolveErrorDev,
resolveErrorProd,
resolveHint,
close,
getRoot,
} from 'react-client/src/ReactFlightClient';
export {createResponse, close, getRoot};
export function resolveRow(response: Response, chunk: RowEncoding): void {
if (chunk[0] === 'O') {
// $FlowFixMe[incompatible-call] unable to refine on array indices
resolveModel(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'I') {
// $FlowFixMe[incompatible-call] unable to refine on array indices
resolveModule(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'H') {
// $FlowFixMe[incompatible-call] unable to refine on array indices
resolveHint(response, chunk[1], chunk[2]);
} else {
if (__DEV__) {
resolveErrorDev(
response,
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
chunk[1],
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
chunk[2].digest,
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].message || '',
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].stack || '',
);
} else {
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
resolveErrorProd(response, chunk[1], chunk[2].digest);
}
}
}

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 {HintModel} from 'react-server/src/ReactFlightServerConfig';
import type {ClientReferenceMetadata} from 'ReactFlightDOMRelayServerIntegration';
export type JSONValue =
| string
| number
| boolean
| null
| {+[key: string]: JSONValue}
| $ReadOnlyArray<JSONValue>;
export type RowEncoding =
| ['O', number, JSONValue]
| ['I', number, ClientReferenceMetadata]
| ['H', string, HintModel]
| ['P', number, string]
| ['S', number, string]
| [
'E',
number,
{
digest: string,
message?: string,
stack?: string,
...
},
];

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
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {
ClientManifest,
Destination,
} from './ReactFlightServerConfigDOMRelay';
import {
createRequest,
startWork,
startFlowing,
} from 'react-server/src/ReactFlightServer';
type Options = {
onError?: (error: mixed) => void,
identifierPrefix?: string,
};
function render(
model: ReactClientValue,
destination: Destination,
config: ClientManifest,
options?: Options,
): void {
const request = createRequest(
model,
config,
options ? options.onError : undefined,
undefined, // not currently set up to supply context overrides
options ? options.identifierPrefix : undefined,
);
startWork(request);
startFlowing(request, destination);
}
export {render};

View File

@ -0,0 +1,236 @@
/**
* 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 {HintModel} from 'react-server/src/ReactFlightServerConfig';
import type {RowEncoding, JSONValue} from './ReactFlightDOMRelayProtocol';
import type {
Request,
ReactClientValue,
} from 'react-server/src/ReactFlightServer';
import type {JSResourceReference} from 'JSResourceReference';
import JSResourceReferenceImpl from 'JSResourceReferenceImpl';
import hasOwnProperty from 'shared/hasOwnProperty';
import isArray from 'shared/isArray';
export type ClientReference<T> = JSResourceReference<T>;
export type ServerReference<T> = T;
export type ServerReferenceId = {};
import type {
Destination,
BundlerConfig as ClientManifest,
ClientReferenceMetadata,
} from 'ReactFlightDOMRelayServerIntegration';
import {resolveModelToJSON} from 'react-server/src/ReactFlightServer';
import {
emitRow,
resolveClientReferenceMetadata as resolveClientReferenceMetadataImpl,
close,
} from 'ReactFlightDOMRelayServerIntegration';
export type {
Destination,
BundlerConfig as ClientManifest,
ClientReferenceMetadata,
} from 'ReactFlightDOMRelayServerIntegration';
export function isClientReference(reference: Object): boolean {
return reference instanceof JSResourceReferenceImpl;
}
export function isServerReference(reference: Object): boolean {
return false;
}
export type ClientReferenceKey = ClientReference<any>;
export function getClientReferenceKey(
reference: ClientReference<any>,
): ClientReferenceKey {
// We use the reference object itself as the key because we assume the
// object will be cached by the bundler runtime.
return reference;
}
export function resolveClientReferenceMetadata<T>(
config: ClientManifest,
resource: ClientReference<T>,
): ClientReferenceMetadata {
return resolveClientReferenceMetadataImpl(config, resource);
}
export function getServerReferenceId<T>(
config: ClientManifest,
resource: ServerReference<T>,
): ServerReferenceId {
throw new Error('Not implemented.');
}
export function getServerReferenceBoundArguments<T>(
config: ClientManifest,
resource: ServerReference<T>,
): Array<ReactClientValue> {
throw new Error('Not implemented.');
}
export type Chunk = RowEncoding;
export function processErrorChunkProd(
request: Request,
id: number,
digest: string,
): Chunk {
if (__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkProd should never be called while in development mode. Use processErrorChunkDev instead. This is a bug in React.',
);
}
return [
'E',
id,
{
digest,
},
];
}
export function processErrorChunkDev(
request: Request,
id: number,
digest: string,
message: string,
stack: string,
): Chunk {
if (!__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkDev should never be called while in production mode. Use processErrorChunkProd instead. This is a bug in React.',
);
}
return [
'E',
id,
{
digest,
message,
stack,
},
];
}
function convertModelToJSON(
request: Request,
parent: {+[key: string]: ReactClientValue} | $ReadOnlyArray<ReactClientValue>,
key: string,
model: ReactClientValue,
): JSONValue {
const json = resolveModelToJSON(request, parent, key, model);
if (typeof json === 'object' && json !== null) {
if (isArray(json)) {
const jsonArray: Array<JSONValue> = [];
for (let i = 0; i < json.length; i++) {
jsonArray[i] = convertModelToJSON(request, json, '' + i, json[i]);
}
return jsonArray;
} else {
const jsonObj: {[key: string]: JSONValue} = {};
for (const nextKey in json) {
if (hasOwnProperty.call(json, nextKey)) {
jsonObj[nextKey] = convertModelToJSON(
request,
json,
nextKey,
json[nextKey],
);
}
}
return jsonObj;
}
}
return json;
}
export function processModelChunk(
request: Request,
id: number,
model: ReactClientValue,
): Chunk {
const json = convertModelToJSON(request, {}, '', model);
return ['O', id, json];
}
export function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
return ['O', id, reference];
}
export function processImportChunk(
request: Request,
id: number,
clientReferenceMetadata: ClientReferenceMetadata,
): Chunk {
// The clientReferenceMetadata is already a JSON serializable value.
return ['I', id, clientReferenceMetadata];
}
export function processHintChunk(
request: Request,
id: number,
code: string,
model: HintModel,
): Chunk {
// The hint is already a JSON serializable value.
return ['H', code, model];
}
export function scheduleWork(callback: () => void) {
callback();
}
export function flushBuffered(destination: Destination) {}
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
export function beginWriting(destination: Destination) {}
export function writeChunk(destination: Destination, chunk: Chunk): void {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
emitRow(destination, chunk);
}
export function writeChunkAndReturn(
destination: Destination,
chunk: Chunk,
): boolean {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
emitRow(destination, chunk);
return true;
}
export function completeWriting(destination: Destination) {}
export {close};
export function closeWithError(destination: Destination, error: mixed): void {
close(destination);
}

View File

@ -0,0 +1,19 @@
/**
* 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.
*/
'use strict';
class JSResourceReferenceImpl {
constructor(moduleId) {
this._moduleId = moduleId;
}
getModuleId() {
return this._moduleId;
}
}
module.exports = JSResourceReferenceImpl;

View File

@ -0,0 +1,22 @@
/**
* 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.
*/
'use strict';
import JSResourceReferenceImpl from 'JSResourceReferenceImpl';
const ReactFlightDOMRelayClientIntegration = {
resolveClientReference(metadata) {
return new JSResourceReferenceImpl(metadata);
},
preloadModule(clientReference) {},
requireModule(clientReference) {
return clientReference._moduleId;
},
};
module.exports = ReactFlightDOMRelayClientIntegration;

View File

@ -0,0 +1,20 @@
/**
* 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.
*/
'use strict';
const ReactFlightDOMRelayServerIntegration = {
emitRow(destination, json) {
destination.push(json);
},
close(destination) {},
resolveClientReferenceMetadata(config, resource) {
return resource._moduleId;
},
};
module.exports = ReactFlightDOMRelayServerIntegration;

View File

@ -0,0 +1,240 @@
/**
* 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.
*/
'use strict';
let act;
let React;
let ReactDOMClient;
let JSResourceReferenceImpl;
let ReactDOMFlightRelayServer;
let ReactDOMFlightRelayClient;
let SuspenseList;
describe('ReactFlightDOMRelay', () => {
beforeEach(() => {
jest.resetModules();
act = require('internal-test-utils').act;
React = require('react');
ReactDOMClient = require('react-dom/client');
ReactDOMFlightRelayServer = require('react-server-dom-relay/server');
ReactDOMFlightRelayClient = require('react-server-dom-relay');
JSResourceReferenceImpl = require('JSResourceReferenceImpl');
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
});
function readThrough(data) {
const response = ReactDOMFlightRelayClient.createResponse();
for (let i = 0; i < data.length; i++) {
const chunk = data[i];
ReactDOMFlightRelayClient.resolveRow(response, chunk);
}
ReactDOMFlightRelayClient.close(response);
const promise = ReactDOMFlightRelayClient.getRoot(response);
let model;
let error;
promise.then(
m => (model = m),
e => (error = e),
);
if (error) {
throw error;
}
return model;
}
it('can render a Server Component', () => {
function Bar({text}) {
return text.toUpperCase();
}
function Foo() {
return {
bar: (
<div>
<Bar text="a" />, <Bar text="b" />
</div>
),
};
}
const transport = [];
ReactDOMFlightRelayServer.render(
{
foo: <Foo />,
},
transport,
);
const model = readThrough(transport);
expect(model).toEqual({
foo: {
bar: (
<div>
{'A'}
{', '}
{'B'}
</div>
),
},
});
});
it('can render a Client Component using a module reference and render there', async () => {
function UserClient(props) {
return (
<span>
{props.greeting}, {props.name}
</span>
);
}
const User = new JSResourceReferenceImpl(UserClient);
function Greeting({firstName, lastName}) {
return <User greeting="Hello" name={firstName + ' ' + lastName} />;
}
const model = {
greeting: <Greeting firstName="Seb" lastName="Smith" />,
};
const transport = [];
ReactDOMFlightRelayServer.render(model, transport);
const modelClient = readThrough(transport);
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(modelClient.greeting);
});
expect(container.innerHTML).toEqual('<span>Hello, Seb Smith</span>');
});
// @gate enableSuspenseList
it('can reasonably handle different element types', () => {
const {forwardRef, memo, Fragment, StrictMode, Profiler, Suspense} = React;
const Inner = memo(
forwardRef((props, ref) => {
return <div ref={ref}>{'Hello ' + props.name}</div>;
}),
);
function Foo() {
return {
bar: (
<div>
<Fragment>Fragment child</Fragment>
<Profiler>Profiler child</Profiler>
<StrictMode>StrictMode child</StrictMode>
<Suspense fallback="Loading...">Suspense child</Suspense>
<SuspenseList fallback="Loading...">
{'SuspenseList row 1'}
{'SuspenseList row 2'}
</SuspenseList>
<Inner name="world" />
</div>
),
};
}
const transport = [];
ReactDOMFlightRelayServer.render(
{
foo: <Foo />,
},
transport,
);
const model = readThrough(transport);
expect(model).toEqual({
foo: {
bar: (
<div>
Fragment child
<Profiler>Profiler child</Profiler>
<StrictMode>StrictMode child</StrictMode>
<Suspense fallback="Loading...">Suspense child</Suspense>
<SuspenseList fallback="Loading...">
{'SuspenseList row 1'}
{'SuspenseList row 2'}
</SuspenseList>
<div>Hello world</div>
</div>
),
},
});
});
it('can handle a subset of Hooks', () => {
const {useMemo, useCallback} = React;
function Inner({x}) {
const foo = useMemo(() => x + x, [x]);
const bar = useCallback(() => 10 + foo, [foo]);
return bar();
}
function Foo() {
return {
bar: <Inner x={2} />,
};
}
const transport = [];
ReactDOMFlightRelayServer.render(
{
foo: <Foo />,
},
transport,
);
const model = readThrough(transport);
expect(model).toEqual({
foo: {
bar: 14,
},
});
});
it('can handle a subset of Hooks, with element as root', () => {
const {useMemo, useCallback} = React;
function Inner({x}) {
const foo = useMemo(() => x + x, [x]);
const bar = useCallback(() => 10 + foo, [foo]);
return bar();
}
function Foo() {
return <Inner x={2} />;
}
const transport = [];
ReactDOMFlightRelayServer.render(<Foo />, transport);
const model = readThrough(transport);
expect(model).toEqual(14);
});
it('should warn in DEV if a class instance polyfill is passed to a host component', () => {
function Bar() {}
function Foo() {}
Foo.prototype = Object.create(Bar.prototype);
// This is enumerable which some polyfills do.
Foo.prototype.constructor = Foo;
Foo.prototype.method = function () {};
expect(() => {
const transport = [];
ReactDOMFlightRelayServer.render(<input value={new Foo()} />, transport);
readThrough(transport);
}).toErrorDev(
'Only plain objects can be passed to Client Components from Server Components. ',
{withoutStack: true},
);
});
});

View File

@ -9,7 +9,7 @@
import type {Thenable} from 'shared/ReactTypes.js';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClientStream';
import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
@ -20,7 +20,7 @@ import {
processStringChunk,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
} from 'react-client/src/ReactFlightClientStream';
import {
processReply,

View File

@ -9,7 +9,7 @@
import type {Thenable} from 'shared/ReactTypes.js';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClient';
import type {Response as FlightResponse} from 'react-client/src/ReactFlightClientStream';
import type {SSRManifest} from './ReactFlightClientConfigWebpackBundler';
@ -19,7 +19,7 @@ import {
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
} from 'react-client/src/ReactFlightClientStream';
function noServerCall() {
throw new Error(

View File

@ -9,7 +9,7 @@
import type {Thenable} from 'shared/ReactTypes.js';
import type {Response} from 'react-client/src/ReactFlightClient';
import type {Response} from 'react-client/src/ReactFlightClientStream';
import type {SSRManifest} from 'react-client/src/ReactFlightClientConfig';
@ -21,8 +21,8 @@ import {
reportGlobalError,
processBinaryChunk,
close,
} from 'react-client/src/ReactFlightClient';
import {processStringChunk} from '../../react-client/src/ReactFlightClient';
} from 'react-client/src/ReactFlightClientStream';
import {processStringChunk} from '../../react-client/src/ReactFlightClientStream';
function noServerCall() {
throw new Error(

View File

@ -270,6 +270,7 @@ module.exports = function register() {
$$id: {value: moduleId},
$$async: {value: false},
});
// $FlowFixMe[incompatible-call] found when upgrading Flow
this.exports = new Proxy(clientReference, proxyHandlers);
}

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

View File

@ -1,17 +1,16 @@
{
"name": "react-server-dom-fb",
"name": "react-server-native-relay",
"version": "0.1.0",
"private": true,
"repository": {
"type" : "git",
"url" : "https://github.com/facebook/react.git",
"directory": "packages/react-server-dom-fb"
"directory": "packages/react-server-native-relay"
},
"dependencies": {
"scheduler": "^0.23.0"
"scheduler": "^0.11.0"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
"react": "^17.0.0"
}
}

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

View File

@ -0,0 +1,99 @@
/**
* 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 {JSONValue, ResponseBase} from 'react-client/src/ReactFlightClient';
import type {JSResourceReference} from 'JSResourceReference';
import type {ClientReferenceMetadata} from 'ReactFlightNativeRelayClientIntegration';
export type ClientReference<T> = JSResourceReference<T>;
import {
parseModelString,
parseModelTuple,
} from 'react-client/src/ReactFlightClient';
export {
preloadModule,
requireModule,
} from 'ReactFlightNativeRelayClientIntegration';
import {resolveClientReference as resolveClientReferenceImpl} from 'ReactFlightNativeRelayClientIntegration';
import isArray from 'shared/isArray';
export type {ClientReferenceMetadata} from 'ReactFlightNativeRelayClientIntegration';
export type SSRManifest = null;
export type ServerManifest = null;
export type ServerReferenceId = string;
export type UninitializedModel = JSONValue;
export type Response = ResponseBase;
export function resolveClientReference<T>(
bundlerConfig: SSRManifest,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
return resolveClientReferenceImpl(metadata);
}
export function resolveServerReference<T>(
bundlerConfig: ServerManifest,
id: ServerReferenceId,
): ClientReference<T> {
throw new Error('Not implemented.');
}
function parseModelRecursively(
response: Response,
parentObj: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
key: string,
value: JSONValue,
): $FlowFixMe {
if (typeof value === 'string') {
return parseModelString(response, parentObj, key, value);
}
if (typeof value === 'object' && value !== null) {
if (isArray(value)) {
const parsedValue: Array<$FlowFixMe> = [];
for (let i = 0; i < value.length; i++) {
(parsedValue: any)[i] = parseModelRecursively(
response,
value,
'' + i,
value[i],
);
}
return parseModelTuple(response, parsedValue);
} else {
const parsedValue = {};
for (const innerKey in value) {
(parsedValue: any)[innerKey] = parseModelRecursively(
response,
value,
innerKey,
value[innerKey],
);
}
return parsedValue;
}
}
return value;
}
const dummy = {};
export function parseModel<T>(response: Response, json: UninitializedModel): T {
return (parseModelRecursively(response, dummy, '', json): any);
}
export function dispatchHint(code: string, model: mixed) {}

View File

@ -0,0 +1,56 @@
/**
* 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 {RowEncoding} from './ReactFlightNativeRelayProtocol';
import type {Response} from 'react-client/src/ReactFlightClient';
import {
createResponse,
resolveModel,
resolveModule,
resolveErrorDev,
resolveErrorProd,
close,
getRoot,
} from 'react-client/src/ReactFlightClient';
export {createResponse, close, getRoot};
export function resolveRow(response: Response, chunk: RowEncoding): void {
if (chunk[0] === 'O') {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
resolveModel(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'I') {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
resolveModule(response, chunk[1], chunk[2]);
} else {
if (__DEV__) {
resolveErrorDev(
response,
chunk[1],
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
chunk[2].digest,
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].message || '',
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].stack || '',
);
} else {
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
resolveErrorProd(response, chunk[1], chunk[2].digest);
}
}
}

View File

@ -0,0 +1,34 @@
/**
* 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 {ClientReferenceMetadata} from 'ReactFlightNativeRelayServerIntegration';
export type JSONValue =
| string
| number
| boolean
| null
| {+[key: string]: JSONValue}
| Array<JSONValue>;
export type RowEncoding =
| ['O', number, JSONValue]
| ['I', number, ClientReferenceMetadata]
| ['P', number, string]
| ['S', number, string]
| [
'E',
number,
{
digest: string,
message?: string,
stack?: string,
...
},
];

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 type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {
ClientManifest,
Destination,
} from './ReactFlightServerConfigNativeRelay';
import {
createRequest,
startWork,
startFlowing,
} from 'react-server/src/ReactFlightServer';
function render(
model: ReactClientValue,
destination: Destination,
config: ClientManifest,
): void {
const request = createRequest(model, config);
startWork(request);
startFlowing(request, destination);
}
export {render};

View File

@ -0,0 +1,232 @@
/**
* 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 {RowEncoding, JSONValue} from './ReactFlightNativeRelayProtocol';
import type {
Request,
ReactClientValue,
} from 'react-server/src/ReactFlightServer';
import hasOwnProperty from 'shared/hasOwnProperty';
import isArray from 'shared/isArray';
import type {JSResourceReference} from 'JSResourceReference';
import JSResourceReferenceImpl from 'JSResourceReferenceImpl';
export type ClientReference<T> = JSResourceReference<T>;
export type ServerReference<T> = T;
export type ServerReferenceId = {};
import type {
Destination,
BundlerConfig as ClientManifest,
ClientReferenceMetadata,
} from 'ReactFlightNativeRelayServerIntegration';
import {resolveModelToJSON} from 'react-server/src/ReactFlightServer';
import {
emitRow,
close,
resolveClientReferenceMetadata as resolveClientReferenceMetadataImpl,
} from 'ReactFlightNativeRelayServerIntegration';
export type {
Destination,
BundlerConfig as ClientManifest,
ClientReferenceMetadata,
} from 'ReactFlightNativeRelayServerIntegration';
export function isClientReference(reference: Object): boolean {
return reference instanceof JSResourceReferenceImpl;
}
export function isServerReference(reference: Object): boolean {
return false;
}
export type ClientReferenceKey = ClientReference<any>;
export function getClientReferenceKey(
reference: ClientReference<any>,
): ClientReferenceKey {
// We use the reference object itself as the key because we assume the
// object will be cached by the bundler runtime.
return reference;
}
export function resolveClientReferenceMetadata<T>(
config: ClientManifest,
resource: ClientReference<T>,
): ClientReferenceMetadata {
return resolveClientReferenceMetadataImpl(config, resource);
}
export function getServerReferenceId<T>(
config: ClientManifest,
resource: ServerReference<T>,
): ServerReferenceId {
throw new Error('Not implemented.');
}
export function getServerReferenceBoundArguments<T>(
config: ClientManifest,
resource: ServerReference<T>,
): Array<ReactClientValue> {
throw new Error('Not implemented.');
}
export type Chunk = RowEncoding;
export function processErrorChunkProd(
request: Request,
id: number,
digest: string,
): Chunk {
if (__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkProd should never be called while in development mode. Use processErrorChunkDev instead. This is a bug in React.',
);
}
return [
'E',
id,
{
digest,
},
];
}
export function processErrorChunkDev(
request: Request,
id: number,
digest: string,
message: string,
stack: string,
): Chunk {
if (!__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkDev should never be called while in production mode. Use processErrorChunkProd instead. This is a bug in React.',
);
}
return [
'E',
id,
{
digest,
message,
stack,
},
];
}
function convertModelToJSON(
request: Request,
parent: {+[key: string]: ReactClientValue} | $ReadOnlyArray<ReactClientValue>,
key: string,
model: ReactClientValue,
): JSONValue {
const json = resolveModelToJSON(request, parent, key, model);
if (typeof json === 'object' && json !== null) {
if (isArray(json)) {
const jsonArray: Array<JSONValue> = [];
for (let i = 0; i < json.length; i++) {
jsonArray[i] = convertModelToJSON(request, json, '' + i, json[i]);
}
return jsonArray;
} else {
const jsonObj: {[key: string]: JSONValue} = {};
for (const nextKey in json) {
if (hasOwnProperty.call(json, nextKey)) {
jsonObj[nextKey] = convertModelToJSON(
request,
json,
nextKey,
json[nextKey],
);
}
}
return jsonObj;
}
}
return json;
}
export function processModelChunk(
request: Request,
id: number,
model: ReactClientValue,
): Chunk {
const json = convertModelToJSON(request, {}, '', model);
return ['O', id, json];
}
export function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
return ['O', id, reference];
}
export function processImportChunk(
request: Request,
id: number,
clientReferenceMetadata: ClientReferenceMetadata,
): Chunk {
// The clientReferenceMetadata is already a JSON serializable value.
return ['I', id, clientReferenceMetadata];
}
export function processHintChunk(
request: Request,
id: number,
code: string,
model: JSONValue,
): Chunk {
throw new Error(
'React Internal Error: processHintChunk is not implemented for Native-Relay. The fact that this method was called means there is a bug in React.',
);
}
export function scheduleWork(callback: () => void) {
callback();
}
export function flushBuffered(destination: Destination) {}
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
export function beginWriting(destination: Destination) {}
export function writeChunk(destination: Destination, chunk: Chunk): void {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
emitRow(destination, chunk);
}
export function writeChunkAndReturn(
destination: Destination,
chunk: Chunk,
): boolean {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
emitRow(destination, chunk);
return true;
}
export function completeWriting(destination: Destination) {}
export {close};
export function closeWithError(destination: Destination, error: mixed): void {
close(destination);
}

View File

@ -0,0 +1,22 @@
/**
* 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.
*/
'use strict';
import JSResourceReferenceImpl from 'JSResourceReferenceImpl';
const ReactFlightNativeRelayClientIntegration = {
resolveClientReference(metadata) {
return new JSResourceReferenceImpl(metadata);
},
preloadModule(clientReference) {},
requireModule(clientReference) {
return clientReference._moduleId;
},
};
module.exports = ReactFlightNativeRelayClientIntegration;

View File

@ -0,0 +1,20 @@
/**
* 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.
*/
'use strict';
const ReactFlightNativeRelayServerIntegration = {
emitRow(destination, json) {
destination.push(json);
},
close(destination) {},
resolveClientReferenceMetadata(config, resource) {
return resource._moduleId;
},
};
module.exports = ReactFlightNativeRelayServerIntegration;

View File

@ -0,0 +1,140 @@
/**
* 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.
*/
'use strict';
let React;
let ReactFabric;
let createReactNativeComponentClass;
let View;
let Text;
let JSResourceReferenceImpl;
let ReactNativeFlightRelayServer;
let ReactNativeFlightRelayClient;
describe('ReactFlightNativeRelay', () => {
beforeEach(() => {
jest.resetModules();
require('react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager');
React = require('react');
// TODO: Switch this out to react-native
ReactFabric = require('react-native-renderer/fabric');
createReactNativeComponentClass =
require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
.ReactNativeViewConfigRegistry.register;
View = createReactNativeComponentClass('RCTView', () => ({
validAttributes: {},
uiViewClassName: 'RCTView',
}));
Text = createReactNativeComponentClass('RCTText', () => ({
validAttributes: {},
uiViewClassName: 'RCTText',
}));
ReactNativeFlightRelayServer = require('react-server-native-relay/server');
ReactNativeFlightRelayClient = require('react-server-native-relay');
JSResourceReferenceImpl = require('JSResourceReferenceImpl');
});
function readThrough(data) {
const response = ReactNativeFlightRelayClient.createResponse();
for (let i = 0; i < data.length; i++) {
const chunk = data[i];
ReactNativeFlightRelayClient.resolveRow(response, chunk);
}
ReactNativeFlightRelayClient.close(response);
const promise = ReactNativeFlightRelayClient.getRoot(response);
let model;
let error;
promise.then(
m => (model = m),
e => (error = e),
);
if (error) {
throw error;
}
return model;
}
it('can render a Server Component', () => {
function Bar({text}) {
return <Text>{text.toUpperCase()}</Text>;
}
function Foo() {
return {
bar: (
<View>
<Bar text="a" /> <Bar text="b" />
</View>
),
};
}
const transport = [];
ReactNativeFlightRelayServer.render(
{
foo: <Foo />,
},
transport,
);
const model = readThrough(transport);
expect(model).toMatchSnapshot();
});
it('can render a Client Component using a module reference and render there', () => {
function UserClient(props) {
return (
<Text>
{props.greeting}, {props.name}
</Text>
);
}
const User = new JSResourceReferenceImpl(UserClient);
function Greeting({firstName, lastName}) {
return <User greeting="Hello" name={firstName + ' ' + lastName} />;
}
const model = {
greeting: <Greeting firstName="Seb" lastName="Smith" />,
};
const transport = [];
ReactNativeFlightRelayServer.render(model, transport);
const modelClient = readThrough(transport);
ReactFabric.render(modelClient.greeting, 1);
expect(
nativeFabricUIManager.__dumpHierarchyForJestTestsOnly(),
).toMatchSnapshot();
});
it('should warn in DEV if a class instance polyfill is passed to a host component', () => {
function Bar() {}
function Foo() {}
Foo.prototype = Object.create(Bar.prototype);
// This is enumerable which some polyfills do.
Foo.prototype.constructor = Foo;
Foo.prototype.method = function () {};
expect(() => {
const transport = [];
ReactNativeFlightRelayServer.render(
<input value={new Foo()} />,
transport,
);
readThrough(transport);
}).toErrorDev(
'Only plain objects can be passed to Client Components from Server Components. ',
{withoutStack: true},
);
});
});

View File

@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ReactFlightNativeRelay can render a Client Component using a module reference and render there 1`] = `
"1
RCTText null
RCTRawText {"text":"Hello"}
RCTRawText {"text":", "}
RCTRawText {"text":"Seb Smith"}"
`;
exports[`ReactFlightNativeRelay can render a Server Component 1`] = `
{
"foo": {
"bar": <RCTView>
<RCTText>
A
</RCTText>
<RCTText>
B
</RCTText>
</RCTView>,
},
}
`;

View File

@ -7,22 +7,9 @@
* @flow
*/
import type {Chunk, Destination} from './ReactServerStreamConfig';
import {
scheduleWork,
flushBuffered,
beginWriting,
writeChunkAndReturn,
stringToChunk,
completeWriting,
close,
closeWithError,
} from './ReactServerStreamConfig';
export type {Destination, Chunk} from './ReactServerStreamConfig';
import type {
Destination,
Chunk,
ClientManifest,
ClientReferenceMetadata,
ClientReference,
@ -47,6 +34,19 @@ import type {
import type {LazyComponent} from 'react/src/ReactLazy';
import {
scheduleWork,
beginWriting,
writeChunkAndReturn,
completeWriting,
flushBuffered,
close,
closeWithError,
processModelChunk,
processImportChunk,
processErrorChunkProd,
processErrorChunkDev,
processReferenceChunk,
processHintChunk,
resolveClientReferenceMetadata,
getServerReferenceId,
getServerReferenceBoundArguments,
@ -99,16 +99,6 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import isArray from 'shared/isArray';
import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable';
type JSONValue =
| string
| boolean
| number
| null
| {+[key: string]: JSONValue}
| $ReadOnlyArray<JSONValue>;
const stringify = JSON.stringify;
type ReactJSONValue =
| string
| boolean
@ -733,7 +723,7 @@ function escapeStringValue(value: string): string {
let insideContextProps = null;
let isInsideContextValue = false;
function resolveModelToJSON(
export function resolveModelToJSON(
request: Request,
parent:
| {+[key: string | number]: ReactClientValue}
@ -956,6 +946,7 @@ function resolveModelToJSON(
// Possibly a Date, whose toJSON automatically calls toISOString
// $FlowFixMe[incompatible-use]
const originalValue = parent[key];
// $FlowFixMe[method-unbinding]
if (originalValue instanceof Date) {
return serializeDateFromDateJSON(value);
}
@ -1460,88 +1451,3 @@ function importServerContexts(
}
return rootContextSnapshot;
}
function serializeRowHeader(tag: string, id: number) {
return id.toString(16) + ':' + tag;
}
function processErrorChunkProd(
request: Request,
id: number,
digest: string,
): Chunk {
if (__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkProd should never be called while in development mode. Use processErrorChunkDev instead. This is a bug in React.',
);
}
const errorInfo: any = {digest};
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
return stringToChunk(row);
}
function processErrorChunkDev(
request: Request,
id: number,
digest: string,
message: string,
stack: string,
): Chunk {
if (!__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkDev should never be called while in production mode. Use processErrorChunkProd instead. This is a bug in React.',
);
}
const errorInfo: any = {digest, message, stack};
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
return stringToChunk(row);
}
function processModelChunk(
request: Request,
id: number,
model: ReactClientValue,
): Chunk {
// $FlowFixMe[incompatible-type] stringify can return null
const json: string = stringify(model, request.toJSON);
const row = id.toString(16) + ':' + json + '\n';
return stringToChunk(row);
}
function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
const json = stringify(reference);
const row = id.toString(16) + ':' + json + '\n';
return stringToChunk(row);
}
function processImportChunk(
request: Request,
id: number,
clientReferenceMetadata: ReactClientValue,
): Chunk {
// $FlowFixMe[incompatible-type] stringify can return null
const json: string = stringify(clientReferenceMetadata);
const row = serializeRowHeader('I', id) + json + '\n';
return stringToChunk(row);
}
function processHintChunk(
request: Request,
id: number,
code: string,
model: JSONValue,
): Chunk {
const json: string = stringify(model);
const row = serializeRowHeader('H' + code, id) + json + '\n';
return stringToChunk(row);
}

View File

@ -0,0 +1,174 @@
/**
* 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
*/
// This file is an intermediate layer to translate between Flight
// calls to stream output over a binary stream.
/*
FLIGHT PROTOCOL GRAMMAR
Response
- RowSequence
RowSequence
- Row RowSequence
- Row
Row
- "J" RowID JSONData
- "M" RowID JSONModuleData
- "H" RowID HTMLData
- "B" RowID BlobData
- "U" RowID URLData
- "E" RowID ErrorData
RowID
- HexDigits ":"
HexDigits
- HexDigit HexDigits
- HexDigit
HexDigit
- 0-F
URLData
- (UTF8 encoded URL) "\n"
ErrorData
- (UTF8 encoded JSON: {message: "...", stack: "..."}) "\n"
JSONData
- (UTF8 encoded JSON) "\n"
- String values that begin with $ are escaped with a "$" prefix.
- References to other rows are encoding as JSONReference strings.
JSONReference
- "$" HexDigits
HTMLData
- ByteSize (UTF8 encoded HTML)
BlobData
- ByteSize (Binary Data)
ByteSize
- (unsigned 32-bit integer)
*/
// TODO: Implement HTMLData, BlobData and URLData.
import type {
Request,
ReactClientValue,
} from 'react-server/src/ReactFlightServer';
import {stringToChunk} from './ReactServerStreamConfig';
import type {Chunk} from './ReactServerStreamConfig';
export type {Destination, Chunk} from './ReactServerStreamConfig';
const stringify = JSON.stringify;
function serializeRowHeader(tag: string, id: number) {
return id.toString(16) + ':' + tag;
}
export function processErrorChunkProd(
request: Request,
id: number,
digest: string,
): Chunk {
if (__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkProd should never be called while in development mode. Use processErrorChunkDev instead. This is a bug in React.',
);
}
const errorInfo: any = {digest};
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
return stringToChunk(row);
}
export function processErrorChunkDev(
request: Request,
id: number,
digest: string,
message: string,
stack: string,
): Chunk {
if (!__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkDev should never be called while in production mode. Use processErrorChunkProd instead. This is a bug in React.',
);
}
const errorInfo: any = {digest, message, stack};
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
return stringToChunk(row);
}
export function processModelChunk(
request: Request,
id: number,
model: ReactClientValue,
): Chunk {
// $FlowFixMe[incompatible-type] stringify can return null
const json: string = stringify(model, request.toJSON);
const row = id.toString(16) + ':' + json + '\n';
return stringToChunk(row);
}
export function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
const json = stringify(reference);
const row = id.toString(16) + ':' + json + '\n';
return stringToChunk(row);
}
export function processImportChunk(
request: Request,
id: number,
clientReferenceMetadata: ReactClientValue,
): Chunk {
// $FlowFixMe[incompatible-type] stringify can return null
const json: string = stringify(clientReferenceMetadata);
const row = serializeRowHeader('I', id) + json + '\n';
return stringToChunk(row);
}
export function processHintChunk(
request: Request,
id: number,
code: string,
model: JSONValue,
): Chunk {
const json: string = stringify(model);
const row = serializeRowHeader('H' + code, id) + json + '\n';
return stringToChunk(row);
}
export {
scheduleWork,
flushBuffered,
beginWriting,
writeChunk,
writeChunkAndReturn,
completeWriting,
close,
closeWithError,
} from './ReactServerStreamConfig';

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
*/
import type {Request} from 'react-server/src/ReactFizzServer';
export * from 'react-native-renderer/src/server/ReactFizzConfigNative';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

View File

@ -6,13 +6,13 @@
*
* @flow
*/
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from '../ReactFlightServerConfigBundlerCustom';
export type Hints = any;
export type HintModel = any;
export type Hints = null;
export type HintModel = '';
export const isPrimaryRenderer = false;
@ -21,6 +21,6 @@ export const prepareHostDispatcher = () => {};
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);
export function createHints(): any {
export function createHints(): null {
return null;
}

View File

@ -9,6 +9,7 @@
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

View File

@ -9,6 +9,7 @@
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from '../ReactFlightServerConfigBundlerCustom';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

View File

@ -8,6 +8,7 @@
*/
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

View File

@ -9,6 +9,7 @@
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

View File

@ -10,6 +10,7 @@ import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

View File

@ -11,6 +11,7 @@ import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

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 'react-server-dom-relay/src/ReactFlightServerConfigDOMRelay';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

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 'react-server-native-relay/src/ReactFlightServerConfigNativeRelay';
export * from 'react-native-renderer/src/server/ReactFlightServerConfigNative';

Some files were not shown because too many files have changed in this diff Show More