Compare commits

...

17 Commits
token ... main

Author SHA1 Message Date
Dennis Moradkhani 4b877b6c66
Updated copyright text to Copyright (c) Meta Platforms, Inc. and its … (#26830)
…affiliates.

## Summary

There were 8 different places where the copyright comment was wrong.
Rewrote from "Copyright (c) Facebook, Inc. and its affiliates." to
"Copyright (c) Meta Platforms, Inc. and its affiliates."

## How did you test this change?
No code was changed. Comment was still a comment after changes.

Co-authored-by: Dennis Moradkhani <denmo530@student.liu.se>
2023-05-19 17:52:17 -07:00
Sanket Singh 7bd330e0b0
Updated Copyright comment from Facebook to Meta (#26833)
## Summary
Changed the comment in react/packages/react
/react.shared-subset.js saying 
```
Copyright (c) Facebook, Inc. and affiliates ..
```
To 
```
Copyright (c) Meta Platforms, Inc. and affiliates ..
```
as raised in the following issues:
https://github.com/facebook/react/issues/26829

Files Changed: 
react/packages/react/react.shared-subset.js

## How did you test this change?

Tests Required: No
2023-05-19 17:51:44 -07:00
Sebastian Markbåge 5309f10285
Remove Flight Relay DOM/Native (#26828)
The bindings upstream in Relay has been removed so we don't need these
builds anymore. The idea is to revisit an FB integration of Flight but
it wouldn't use the Relay specific bindings. It's a bit unclear how it
would look but likely more like the OSS version so not worth keeping
these around.

The `dom-relay` name also included the FB specific Fizz implementation
of the streaming config so I renamed that to `dom-fb`. There's no Fizz
implementation for Native yet so I just removed `native-relay`.

We created a configurable fork for how to encode the output of Flight
and the Relay implementation encoded it as JSON objects instead of
strings/streams. The new implementation would likely be more stream-like
and just encode it directly as string/binary chunks. So I removed those
indirections so that this can just be declared inline in
ReactFlightServer/Client.
2023-05-17 20:33:25 -04:00
Ibrahem Mahyob d7a98a5e97
Fix strict mode badge URL (#26825)
## Summary
Closes https://github.com/facebook/react/issues/26821

[[Fix #26821]](https://github.com/facebook/react/issues/26821) Update
strict mode badge URL


Updated the URL in the strict mode badge to point to the correct React
documentation for StrictMode. The previous URL was outdated. Now, when a
component is not running in StrictMode, the badge links to
https://react.dev/reference/react/StrictMode for more information.


## How did you test this change?

I verified that the strict mode badge now correctly links to the updated
URL. Previously, it pointed to the outdated URL
(https://fb.me/devtools-strict-mode). After the update, it correctly
points to the React Dev documentation for StrictMode
(https://react.dev/reference/react/StrictMode).


_Since its my first contribution here, i have completed the CLA_
2023-05-17 13:34:34 +01:00
Ruslan Lesiutin 2468a87358
React DevTools 4.27.7 -> 4.27.8 (#26823)
Closes https://github.com/facebook/react/issues/26787,
https://github.com/facebook/react/issues/26793

Includes these changes:
* fix[devtools]: fixed duplicated backend activation with multiple
renderers ([hoxyq](https://github.com/hoxyq) in
[#26807](https://github.com/facebook/react/pull/26807))
2023-05-17 11:40:50 +01:00
Andrew Clark f8de255e94
Lower Suspense throttling heuristic to 300ms (#26803)
Now that the throttling mechanism applies more often, we've decided to
lower this a tad to ensure it's not noticeable. The idea is it should be
just large enough to prevent jank when lots of different parts of the UI
load in rapid succession, but not large enough to make the UI feel
sluggish. There's no perfect number, it's just a heuristic.
2023-05-16 15:59:15 -04:00
Andrew Clark 4bfcd02b2c
Fix Suspense throttling mechanism (#26802)
The throttling mechanism for fallbacks should apply to both their
appearance _and_ disappearance.

This was mostly addressed by #26611. See that PR for additional context.

However, a flaw in the implementation is that we only update the the
timestamp used for throttling when the fallback initially appears. We
don't update it when the real content pops in. If lots of content in
separate Suspense trees loads around the same time, you can still get
jank.

The issue is fixed by updating the throttling timestamp whenever the
visibility of a fallback changes. Not just when it appears.
2023-05-16 15:03:30 -04:00
Sophie Alpert 4cd7065665
Fix uSES hydration in strict mode (#26791)
Previously, we'd call and use getSnapshot on the second render resulting
in `Warning: Text content did not match. Server: "Nay!" Client: "Yay!"`
and then `Error: Text content does not match server-rendered HTML.`.

Fixes #26095. Closes #26113. Closes #25650.

---------

Co-authored-by: eps1lon <silbermann.sebastian@gmail.com>
2023-05-12 14:18:03 -07:00
Pieter De Baets a389046a52
[react-native] Always set RN$stopSurface (#26808)
## Summary
To support incremental adoption of bridgeless logic we want to default
to using these globals whenever they're available.

## How did you test this change?
https://github.com/facebook/react-native/pull/37410
2023-05-12 16:14:45 +01:00
Ruslan Lesiutin 67a05d03e3
fix[devtools]: fixed duplicated backend activation with multiple renderers (#26807)
## Summary
Initially reported in https://github.com/facebook/react/issues/26797.
Was not able to reproduce the exact same problem, but found this case:

1. Open corresponding codepen from the issue in debug mode
2. Open components tab of the extension
3. Refresh the page

Received multiple errors:
- Warning in the Console tab: Invalid renderer id "2".
- Error in the Components tab: Uncaught Error: Cannot add node "3"
because a node with that id is already in the Store.

This problem has occurred after landing a fix in
https://github.com/facebook/react/pull/26779. Looks like Chrome is
keeping the injected scripts (the backend in this case) and we start
backend twice.
2023-05-12 14:59:53 +01:00
Jan Kassens df12d7eac4
[www] reduce dynamic SchedulerFeatureFlags (#26617)
For these values we're not using dynamic values. We can statically
compile in the values we're running.
2023-05-10 17:26:05 -04:00
Andrew Clark 7cd98ef2bc
Fix nightly job to publish to "canary" channel (#26799)
When I was renaming the next channel to canary, I updated the
`publish_preleases` workflow correctly, but I skipped over
`publish_preleases_nightly`. Oops.
2023-05-09 22:27:35 -04:00
Andrew Clark b5810163e9
Change `yarn test` to default to experimental (#26741)
The idea is that the default `yarn test` command should be the one that
includes the most bleeding edge features, because during development you
probably want as many features enabled as possible.

That used to be `www-modern` but nowadays it's `experimental` because
we've landed a bunch of async actions stuff in experimental but it isn't
yet being tested at Meta.

So this switches the default to `experimental`.
2023-05-09 21:50:14 -04:00
Jan Kassens fda1f0b902
Flow upgrade to 0.205.1 (#26796)
Just a small upgrade to keep us current and remove unused suppressions
(probably fixed by some upgrade since).

- `*` is no longer allowed and has been an alias for `any` for a while
now.
2023-05-09 10:45:50 -04:00
Jan Kassens 7ac5e9a602
Small flowconfig fixes (#26784)
- The whole project root is included by default anyway, the include
section should be redundant and just misleading.
- The generated ignore paths ignore more than intended as they didn't
escape the `.` for regex.

Test Plan:
- wait for CI
- tested the ignore pattern change with renaming files and seeing the
expected files ignored for flow
2023-05-08 12:07:48 -04:00
Andrew Clark 16d053d592
Add useFormStatus to server rendering stub (#26788)
This was an oversight when I set up the hook in #26719.
2023-05-06 20:39:08 -04:00
dan efb381bbf9
[Release Script] Print a hint where to get the token (#26783)
I always forget where to get it.
2023-05-05 20:08:14 +01:00
124 changed files with 660 additions and 2751 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,33 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type StringDecoder = void;
export const supportsBinaryStreams = false;
export function createStringDecoder(): void {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}
export function readPartialStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}
export function readFinalStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error('Should never be called');
}

View File

@ -1,23 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ResponseBase} from './ReactFlightClient';
import type {StringDecoder} from './ReactFlightClientConfig';
export type Response = ResponseBase & {
_partialRow: string,
_fromJSON: (key: string, value: JSONValue) => any,
_stringDecoder: StringDecoder,
};
export type UninitializedModel = string;
export function parseModel<T>(response: Response, json: UninitializedModel): T {
return JSON.parse(json, response._fromJSON);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-server-dom-relay/src/ReactFlightClientConfigDOMRelay';
export * from '../ReactFlightClientConfigNoStream';
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';

View File

@ -1,11 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-server-native-relay/src/ReactFlightClientConfigNativeRelay';
export * from '../ReactFlightClientConfigNoStream';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@
/* eslint-disable no-unused-vars */
type JestMockFn<TArguments: $ReadOnlyArray<*>, TReturn> = {
type JestMockFn<TArguments: $ReadOnlyArray<any>, 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<*>): void;
toBeInstanceOf(cls: Class<any>): 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<*>, TReturn>(
fn<TArguments: $ReadOnlyArray<any>, TReturn>(
implementation?: (...args: TArguments) => TReturn
): JestMockFn<TArguments, TReturn>,
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "react-devtools",
"version": "4.27.7",
"version": "4.27.8",
"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.7",
"react-devtools-core": "4.27.8",
"update-notifier": "^2.1.0"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export const isPrimaryRenderer = true;
export type Hints = null;
export type HintModel = '';
export function createHints(): null {
return null;
}
export function prepareHostDispatcher() {}

View File

@ -55,6 +55,7 @@ import {
enableLegacyHidden,
enableHostSingletons,
diffInCommitPhase,
alwaysThrottleRetries,
} from 'shared/ReactFeatureFlags';
import {
FunctionComponent,
@ -2905,17 +2906,35 @@ 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) {
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
// 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.
markCommitTimeOfFallback();
}
}

View File

@ -1776,8 +1776,6 @@ 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,
@ -1799,15 +1797,28 @@ 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.
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;
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;
}
}
}
}
@ -1830,7 +1841,7 @@ function updateSyncExternalStore<T>(
if (
inst.getSnapshot !== getSnapshot ||
snapshotChanged ||
// Check if the susbcribe function changed. We can save some memory by
// Check if the subscribe function changed. We can save some memory by
// checking whether we scheduled a subscription effect above.
(workInProgressHook !== null &&
workInProgressHook.memoizedState.tag & HookHasEffect)
@ -1854,7 +1865,7 @@ function updateSyncExternalStore<T>(
);
}
if (!includesBlockingLane(root, renderLanes)) {
if (!isHydrating && !includesBlockingLane(root, renderLanes)) {
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot);
}
}
@ -2217,7 +2228,8 @@ function updateEffectImpl(
const effect: Effect = hook.memoizedState;
const inst = effect.inst;
// currentHook is null when rerendering after a render phase state update.
// currentHook is null on initial mount when rerendering after a render phase
// state update or for strict mode.
if (currentHook !== null) {
if (nextDeps !== null) {
const prevEffect: Effect = currentHook.memoizedState;

View File

@ -370,10 +370,12 @@ let workInProgressRootConcurrentErrors: Array<CapturedValue<mixed>> | null =
let workInProgressRootRecoverableErrors: Array<CapturedValue<mixed>> | null =
null;
// 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.
// 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?
let globalMostRecentFallbackTime: number = 0;
const FALLBACK_THROTTLE_MS: number = 500;
const FALLBACK_THROTTLE_MS: number = 300;
// The absolute time for when we should start giving up on rendering
// more and prefer CPU suspense heuristics instead.

View File

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

View File

@ -1,10 +0,0 @@
/**
* 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-native-renderer/src/ReactFiberConfigFabric';

View File

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

View File

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

View File

@ -1,10 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from './src/ReactFlightDOMRelayClient';

View File

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

View File

@ -1,10 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from './src/ReactFlightDOMRelayServer';

View File

@ -1,97 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {JSONValue, ResponseBase} from 'react-client/src/ReactFlightClient';
import type {JSResourceReference} from 'JSResourceReference';
import type {ClientReferenceMetadata} from 'ReactFlightDOMRelayClientIntegration';
export type ClientReference<T> = JSResourceReference<T>;
import {
parseModelString,
parseModelTuple,
} from 'react-client/src/ReactFlightClient';
export {
preloadModule,
requireModule,
} from 'ReactFlightDOMRelayClientIntegration';
import {resolveClientReference as resolveClientReferenceImpl} from 'ReactFlightDOMRelayClientIntegration';
import isArray from 'shared/isArray';
export type {ClientReferenceMetadata} from 'ReactFlightDOMRelayClientIntegration';
export type SSRManifest = null;
export type ServerManifest = null;
export type ServerReferenceId = string;
export type UninitializedModel = JSONValue;
export type Response = ResponseBase;
export function resolveClientReference<T>(
bundlerConfig: SSRManifest,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
return resolveClientReferenceImpl(metadata);
}
export function resolveServerReference<T>(
bundlerConfig: ServerManifest,
id: ServerReferenceId,
): ClientReference<T> {
throw new Error('Not implemented.');
}
function parseModelRecursively(
response: Response,
parentObj: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
key: string,
value: JSONValue,
): $FlowFixMe {
if (typeof value === 'string') {
return parseModelString(response, parentObj, key, value);
}
if (typeof value === 'object' && value !== null) {
if (isArray(value)) {
const parsedValue: Array<$FlowFixMe> = [];
for (let i = 0; i < value.length; i++) {
(parsedValue: any)[i] = parseModelRecursively(
response,
value,
'' + i,
value[i],
);
}
return parseModelTuple(response, parsedValue);
} else {
const parsedValue = {};
for (const innerKey in value) {
(parsedValue: any)[innerKey] = parseModelRecursively(
response,
value,
innerKey,
value[innerKey],
);
}
return parsedValue;
}
}
return value;
}
const dummy = {};
export function parseModel<T>(response: Response, json: UninitializedModel): T {
return (parseModelRecursively(response, dummy, '', json): any);
}

View File

@ -1,61 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {RowEncoding} from './ReactFlightDOMRelayProtocol';
import type {Response} from 'react-client/src/ReactFlightClient';
import {
createResponse,
resolveModel,
resolveModule,
resolveErrorDev,
resolveErrorProd,
resolveHint,
close,
getRoot,
} from 'react-client/src/ReactFlightClient';
export {createResponse, close, getRoot};
export function resolveRow(response: Response, chunk: RowEncoding): void {
if (chunk[0] === 'O') {
// $FlowFixMe[incompatible-call] unable to refine on array indices
resolveModel(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'I') {
// $FlowFixMe[incompatible-call] unable to refine on array indices
resolveModule(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'H') {
// $FlowFixMe[incompatible-call] unable to refine on array indices
resolveHint(response, chunk[1], chunk[2]);
} else {
if (__DEV__) {
resolveErrorDev(
response,
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
chunk[1],
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
chunk[2].digest,
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].message || '',
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].stack || '',
);
} else {
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
resolveErrorProd(response, chunk[1], chunk[2].digest);
}
}
}

View File

@ -1,36 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {HintModel} from 'react-server/src/ReactFlightServerConfig';
import type {ClientReferenceMetadata} from 'ReactFlightDOMRelayServerIntegration';
export type JSONValue =
| string
| number
| boolean
| null
| {+[key: string]: JSONValue}
| $ReadOnlyArray<JSONValue>;
export type RowEncoding =
| ['O', number, JSONValue]
| ['I', number, ClientReferenceMetadata]
| ['H', string, HintModel]
| ['P', number, string]
| ['S', number, string]
| [
'E',
number,
{
digest: string,
message?: string,
stack?: string,
...
},
];

View File

@ -1,44 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {
ClientManifest,
Destination,
} from './ReactFlightServerConfigDOMRelay';
import {
createRequest,
startWork,
startFlowing,
} from 'react-server/src/ReactFlightServer';
type Options = {
onError?: (error: mixed) => void,
identifierPrefix?: string,
};
function render(
model: ReactClientValue,
destination: Destination,
config: ClientManifest,
options?: Options,
): void {
const request = createRequest(
model,
config,
options ? options.onError : undefined,
undefined, // not currently set up to supply context overrides
options ? options.identifierPrefix : undefined,
);
startWork(request);
startFlowing(request, destination);
}
export {render};

View File

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

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
class JSResourceReferenceImpl {
constructor(moduleId) {
this._moduleId = moduleId;
}
getModuleId() {
return this._moduleId;
}
}
module.exports = JSResourceReferenceImpl;

View File

@ -1,22 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import JSResourceReferenceImpl from 'JSResourceReferenceImpl';
const ReactFlightDOMRelayClientIntegration = {
resolveClientReference(metadata) {
return new JSResourceReferenceImpl(metadata);
},
preloadModule(clientReference) {},
requireModule(clientReference) {
return clientReference._moduleId;
},
};
module.exports = ReactFlightDOMRelayClientIntegration;

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const ReactFlightDOMRelayServerIntegration = {
emitRow(destination, json) {
destination.push(json);
},
close(destination) {},
resolveClientReferenceMetadata(config, resource) {
return resource._moduleId;
},
};
module.exports = ReactFlightDOMRelayServerIntegration;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from './src/ReactFlightNativeRelayServer';

View File

@ -1,99 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {JSONValue, ResponseBase} from 'react-client/src/ReactFlightClient';
import type {JSResourceReference} from 'JSResourceReference';
import type {ClientReferenceMetadata} from 'ReactFlightNativeRelayClientIntegration';
export type ClientReference<T> = JSResourceReference<T>;
import {
parseModelString,
parseModelTuple,
} from 'react-client/src/ReactFlightClient';
export {
preloadModule,
requireModule,
} from 'ReactFlightNativeRelayClientIntegration';
import {resolveClientReference as resolveClientReferenceImpl} from 'ReactFlightNativeRelayClientIntegration';
import isArray from 'shared/isArray';
export type {ClientReferenceMetadata} from 'ReactFlightNativeRelayClientIntegration';
export type SSRManifest = null;
export type ServerManifest = null;
export type ServerReferenceId = string;
export type UninitializedModel = JSONValue;
export type Response = ResponseBase;
export function resolveClientReference<T>(
bundlerConfig: SSRManifest,
metadata: ClientReferenceMetadata,
): ClientReference<T> {
return resolveClientReferenceImpl(metadata);
}
export function resolveServerReference<T>(
bundlerConfig: ServerManifest,
id: ServerReferenceId,
): ClientReference<T> {
throw new Error('Not implemented.');
}
function parseModelRecursively(
response: Response,
parentObj: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
key: string,
value: JSONValue,
): $FlowFixMe {
if (typeof value === 'string') {
return parseModelString(response, parentObj, key, value);
}
if (typeof value === 'object' && value !== null) {
if (isArray(value)) {
const parsedValue: Array<$FlowFixMe> = [];
for (let i = 0; i < value.length; i++) {
(parsedValue: any)[i] = parseModelRecursively(
response,
value,
'' + i,
value[i],
);
}
return parseModelTuple(response, parsedValue);
} else {
const parsedValue = {};
for (const innerKey in value) {
(parsedValue: any)[innerKey] = parseModelRecursively(
response,
value,
innerKey,
value[innerKey],
);
}
return parsedValue;
}
}
return value;
}
const dummy = {};
export function parseModel<T>(response: Response, json: UninitializedModel): T {
return (parseModelRecursively(response, dummy, '', json): any);
}
export function dispatchHint(code: string, model: mixed) {}

View File

@ -1,56 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {RowEncoding} from './ReactFlightNativeRelayProtocol';
import type {Response} from 'react-client/src/ReactFlightClient';
import {
createResponse,
resolveModel,
resolveModule,
resolveErrorDev,
resolveErrorProd,
close,
getRoot,
} from 'react-client/src/ReactFlightClient';
export {createResponse, close, getRoot};
export function resolveRow(response: Response, chunk: RowEncoding): void {
if (chunk[0] === 'O') {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
resolveModel(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'I') {
// $FlowFixMe[incompatible-call] `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
resolveModule(response, chunk[1], chunk[2]);
} else {
if (__DEV__) {
resolveErrorDev(
response,
chunk[1],
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
chunk[2].digest,
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].message || '',
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
chunk[2].stack || '',
);
} else {
// $FlowFixMe[incompatible-call]: Flow doesn't support disjoint unions on tuples.
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
resolveErrorProd(response, chunk[1], chunk[2].digest);
}
}
}

View File

@ -1,34 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ClientReferenceMetadata} from 'ReactFlightNativeRelayServerIntegration';
export type JSONValue =
| string
| number
| boolean
| null
| {+[key: string]: JSONValue}
| Array<JSONValue>;
export type RowEncoding =
| ['O', number, JSONValue]
| ['I', number, ClientReferenceMetadata]
| ['P', number, string]
| ['S', number, string]
| [
'E',
number,
{
digest: string,
message?: string,
stack?: string,
...
},
];

View File

@ -1,32 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactClientValue} from 'react-server/src/ReactFlightServer';
import type {
ClientManifest,
Destination,
} from './ReactFlightServerConfigNativeRelay';
import {
createRequest,
startWork,
startFlowing,
} from 'react-server/src/ReactFlightServer';
function render(
model: ReactClientValue,
destination: Destination,
config: ClientManifest,
): void {
const request = createRequest(model, config);
startWork(request);
startFlowing(request, destination);
}
export {render};

View File

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

View File

@ -1,22 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
import JSResourceReferenceImpl from 'JSResourceReferenceImpl';
const ReactFlightNativeRelayClientIntegration = {
resolveClientReference(metadata) {
return new JSResourceReferenceImpl(metadata);
},
preloadModule(clientReference) {},
requireModule(clientReference) {
return clientReference._moduleId;
},
};
module.exports = ReactFlightNativeRelayClientIntegration;

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const ReactFlightNativeRelayServerIntegration = {
emitRow(destination, json) {
destination.push(json);
},
close(destination) {},
resolveClientReferenceMetadata(config, resource) {
return resource._moduleId;
},
};
module.exports = ReactFlightNativeRelayServerIntegration;

View File

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

View File

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

View File

@ -7,9 +7,22 @@
* @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,
@ -34,19 +47,6 @@ 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,6 +99,16 @@ 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
@ -723,7 +733,7 @@ function escapeStringValue(value: string): string {
let insideContextProps = null;
let isInsideContextValue = false;
export function resolveModelToJSON(
function resolveModelToJSON(
request: Request,
parent:
| {+[key: string | number]: ReactClientValue}
@ -946,7 +956,6 @@ export 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);
}
@ -1451,3 +1460,88 @@ function importServerContexts(
}
return rootContextSnapshot;
}
function serializeRowHeader(tag: string, id: number) {
return id.toString(16) + ':' + tag;
}
function processErrorChunkProd(
request: Request,
id: number,
digest: string,
): Chunk {
if (__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkProd should never be called while in development mode. Use processErrorChunkDev instead. This is a bug in React.',
);
}
const errorInfo: any = {digest};
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
return stringToChunk(row);
}
function processErrorChunkDev(
request: Request,
id: number,
digest: string,
message: string,
stack: string,
): Chunk {
if (!__DEV__) {
// These errors should never make it into a build so we don't need to encode them in codes.json
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'processErrorChunkDev should never be called while in production mode. Use processErrorChunkProd instead. This is a bug in React.',
);
}
const errorInfo: any = {digest, message, stack};
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
return stringToChunk(row);
}
function processModelChunk(
request: Request,
id: number,
model: ReactClientValue,
): Chunk {
// $FlowFixMe[incompatible-type] stringify can return null
const json: string = stringify(model, request.toJSON);
const row = id.toString(16) + ':' + json + '\n';
return stringToChunk(row);
}
function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
const json = stringify(reference);
const row = id.toString(16) + ':' + json + '\n';
return stringToChunk(row);
}
function processImportChunk(
request: Request,
id: number,
clientReferenceMetadata: ReactClientValue,
): Chunk {
// $FlowFixMe[incompatible-type] stringify can return null
const json: string = stringify(clientReferenceMetadata);
const row = serializeRowHeader('I', id) + json + '\n';
return stringToChunk(row);
}
function processHintChunk(
request: Request,
id: number,
code: string,
model: JSONValue,
): Chunk {
const json: string = stringify(model);
const row = serializeRowHeader('H' + code, id) + json + '\n';
return stringToChunk(row);
}

View File

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

View File

@ -1,14 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Request} from 'react-server/src/ReactFizzServer';
export * from 'react-native-renderer/src/server/ReactFizzConfigNative';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request> = (null: any);

View File

@ -6,13 +6,13 @@
*
* @flow
*/
import type {Request} from 'react-server/src/ReactFlightServer';
export * from '../ReactFlightServerConfigStream';
export * from '../ReactFlightServerConfigBundlerCustom';
export type Hints = null;
export type HintModel = '';
export type Hints = any;
export type HintModel = any;
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(): null {
export function createHints(): any {
return null;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-server-dom-relay/src/ReactFlightServerConfigDOMRelay';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';

View File

@ -1,11 +0,0 @@
/**
* 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';

View File

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

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