Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
523f2f0d48 |
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -416,6 +416,7 @@ module.exports = {
|
|||
{
|
||||
files: [
|
||||
'packages/react-native-renderer/**/*.js',
|
||||
'packages/react-server-native-relay/**/*.js',
|
||||
],
|
||||
globals: {
|
||||
nativeFabricUIManager: 'readonly',
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
export * from './src/ReactFlightClient';
|
||||
export * from './src/ReactFlightClientStream';
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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';
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ function onDisconnected() {
|
|||
disconnectedCallback();
|
||||
}
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
function onError({code, message}: $FlowFixMe) {
|
||||
safeUnmount();
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -29,6 +29,5 @@ function setup(hook: ?DevToolsHook) {
|
|||
initBackend,
|
||||
setupNativeStyleEditor,
|
||||
});
|
||||
|
||||
hook.emit('devtools-backend-installed', COMPACT_VERSION_NAME);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -472,6 +472,7 @@ function addTrappedEventListener(
|
|||
if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
|
||||
const originalListener = listener;
|
||||
// $FlowFixMe[missing-this-annot]
|
||||
// $FlowFixMe[definition-cycle]
|
||||
listener = function (...p) {
|
||||
removeEventListener(
|
||||
targetContainer,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ if (document.body != null) {
|
|||
}
|
||||
});
|
||||
// documentElement must already exist at this point
|
||||
// $FlowFixMe[incompatible-call]
|
||||
domBodyObserver.observe(document.documentElement, {childList: true});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,5 +22,4 @@ export {
|
|||
preconnect,
|
||||
preload,
|
||||
preinit,
|
||||
experimental_useFormStatus,
|
||||
} from './src/server/ReactDOMServerRenderingStub';
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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() {}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
export * from '../../../react-server-dom-fb/src/ReactServerStreamConfigFB';
|
||||
export * from 'react-native-renderer/src/ReactFiberConfigFabric';
|
||||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
...
|
||||
},
|
||||
];
|
||||
|
|
@ -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};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
22
packages/react-server-dom-relay/src/__mocks__/ReactFlightDOMRelayClientIntegration.js
vendored
Normal file
22
packages/react-server-dom-relay/src/__mocks__/ReactFlightDOMRelayClientIntegration.js
vendored
Normal 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;
|
||||
20
packages/react-server-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js
vendored
Normal file
20
packages/react-server-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js
vendored
Normal 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;
|
||||
|
|
@ -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},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
99
packages/react-server-native-relay/src/ReactFlightClientConfigNativeRelay.js
vendored
Normal file
99
packages/react-server-native-relay/src/ReactFlightClientConfigNativeRelay.js
vendored
Normal 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) {}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
...
|
||||
},
|
||||
];
|
||||
|
|
@ -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};
|
||||
232
packages/react-server-native-relay/src/ReactFlightServerConfigNativeRelay.js
vendored
Normal file
232
packages/react-server-native-relay/src/ReactFlightServerConfigNativeRelay.js
vendored
Normal 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);
|
||||
}
|
||||
22
packages/react-server-native-relay/src/__mocks__/ReactFlightNativeRelayClientIntegration.js
vendored
Normal file
22
packages/react-server-native-relay/src/__mocks__/ReactFlightNativeRelayClientIntegration.js
vendored
Normal 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;
|
||||
20
packages/react-server-native-relay/src/__mocks__/ReactFlightNativeRelayServerIntegration.js
vendored
Normal file
20
packages/react-server-native-relay/src/__mocks__/ReactFlightNativeRelayServerIntegration.js
vendored
Normal 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;
|
||||
|
|
@ -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},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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>,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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
Loading…
Reference in New Issue