Merge 5bb47ef2f6 into sapling-pr-archive-josephsavona

This commit is contained in:
Joseph Savona
2025-06-13 15:29:41 -07:00
committed by GitHub
56 changed files with 2378 additions and 686 deletions
+39 -2
View File
@@ -17,6 +17,17 @@ on:
description: 'Whether to notify the team on Discord when the release fails. Useful if this workflow is called from an automation.'
required: false
type: boolean
only_packages:
description: Packages to publish (space separated)
type: string
skip_packages:
description: Packages to NOT publish (space separated)
type: string
dry:
required: true
description: Dry run instead of publish?
type: boolean
default: true
secrets:
DISCORD_WEBHOOK_URL:
description: 'Discord webhook URL to notify on failure. Only required if enableFailureNotification is true.'
@@ -61,10 +72,36 @@ jobs:
if: steps.node_modules.outputs.cache-hit != 'true'
- run: yarn --cwd scripts/release install --frozen-lockfile
if: steps.node_modules.outputs.cache-hit != 'true'
- run: cp ./scripts/release/ci-npmrc ~/.npmrc
- run: |
GH_TOKEN=${{ secrets.GH_TOKEN }} scripts/release/prepare-release-from-ci.js --skipTests -r ${{ inputs.release_channel }} --commit=${{ inputs.commit_sha }}
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags ${{ inputs.dist_tag }}
- name: Check prepared files
run: ls -R build/node_modules
- if: '${{ inputs.only_packages }}'
name: 'Publish ${{ inputs.only_packages }}'
run: |
scripts/release/publish.js \
--ci \
--skipTests \
--tags=${{ inputs.dist_tag }} \
--onlyPackages=${{ inputs.only_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
- if: '${{ inputs.skip_packages }}'
name: 'Publish all packages EXCEPT ${{ inputs.skip_packages }}'
run: |
scripts/release/publish.js \
--ci \
--skipTests \
--tags=${{ inputs.dist_tag }} \
--skipPackages=${{ inputs.skip_packages }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
- if: '${{ !(inputs.skip_packages && inputs.only_packages) }}'
name: 'Publish all packages'
run: |
scripts/release/publish.js \
--ci \
--tags=${{ inputs.dist_tag }} ${{ (inputs.dry && '') || '\'}}
${{ inputs.dry && '--dry'}}
- name: Notify Discord on failure
if: failure() && inputs.enableFailureNotification == true
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
@@ -5,6 +5,25 @@ on:
inputs:
prerelease_commit_sha:
required: true
only_packages:
description: Packages to publish (space separated)
type: string
skip_packages:
description: Packages to NOT publish (space separated)
type: string
dry:
required: true
description: Dry run instead of publish?
type: boolean
default: true
experimental_only:
type: boolean
description: Only publish to the experimental tag
default: false
force_notify:
description: Force a Discord notification?
type: boolean
default: false
permissions: {}
@@ -12,8 +31,26 @@ env:
TZ: /usr/share/zoneinfo/America/Los_Angeles
jobs:
notify:
if: ${{ inputs.force_notify || inputs.dry == false || inputs.dry == 'false' }}
runs-on: ubuntu-latest
steps:
- name: Discord Webhook Action
uses: tsickert/discord-webhook@86dc739f3f165f16dadc5666051c367efa1692f4
with:
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
embed-author-name: ${{ github.event.sender.login }}
embed-author-url: ${{ github.event.sender.html_url }}
embed-author-icon-url: ${{ github.event.sender.avatar_url }}
embed-title: "⚠️ Publishing ${{ inputs.experimental_only && 'EXPERIMENTAL' || 'CANARY & EXPERIMENTAL' }} release ${{ (inputs.dry && ' (dry run)') || '' }}"
embed-description: |
```json
${{ toJson(inputs) }}
```
embed-url: https://github.com/facebook/react/actions/runs/${{ github.run_id }}
publish_prerelease_canary:
if: ${{ !inputs.experimental_only }}
name: Publish to Canary channel
uses: facebook/react/.github/workflows/runtime_prereleases.yml@main
permissions:
@@ -33,6 +70,9 @@ jobs:
# downstream consumers might still expect that tag. We can remove this
# after some time has elapsed and the change has been communicated.
dist_tag: canary,next
only_packages: ${{ inputs.only_packages }}
skip_packages: ${{ inputs.skip_packages }}
dry: ${{ inputs.dry }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -48,10 +88,15 @@ jobs:
# different versions of the same package, even if they use different
# dist tags.
needs: publish_prerelease_canary
# Ensures the job runs even if canary is skipped
if: always()
with:
commit_sha: ${{ inputs.prerelease_commit_sha }}
release_channel: experimental
dist_tag: experimental
only_packages: ${{ inputs.only_packages }}
skip_packages: ${{ inputs.skip_packages }}
dry: ${{ inputs.dry }}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -22,6 +22,7 @@ jobs:
release_channel: stable
dist_tag: canary,next
enableFailureNotification: true
dry: false
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -43,6 +44,7 @@ jobs:
release_channel: experimental
dist_tag: experimental
enableFailureNotification: true
dry: false
secrets:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,132 @@
## Input
```javascript
import {useRef, useEffect} from 'react';
/**
* The postfix increment operator should return the value before incrementing.
* ```js
* const id = count.current; // 0
* count.current = count.current + 1; // 1
* return id;
* ```
* The bug is that we currently increment the value before the expression is evaluated.
* This bug does not trigger when the incremented value is a plain primitive.
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) {"count":{"current":0},"updateCountPostfix":"[[ function params=0 ]]","updateCountPrefix":"[[ function params=0 ]]"}
* logs: ['id = 0','count = 1']
* Forget:
* (kind: ok) {"count":{"current":0},"updateCountPostfix":"[[ function params=0 ]]","updateCountPrefix":"[[ function params=0 ]]"}
* logs: ['id = 1','count = 1']
*/
function useFoo() {
const count = useRef(0);
const updateCountPostfix = () => {
const id = count.current++;
return id;
};
const updateCountPrefix = () => {
const id = ++count.current;
return id;
};
useEffect(() => {
const id = updateCountPostfix();
console.log(`id = ${id}`);
console.log(`count = ${count.current}`);
}, []);
return {count, updateCountPostfix, updateCountPrefix};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef, useEffect } from "react";
/**
* The postfix increment operator should return the value before incrementing.
* ```js
* const id = count.current; // 0
* count.current = count.current + 1; // 1
* return id;
* ```
* The bug is that we currently increment the value before the expression is evaluated.
* This bug does not trigger when the incremented value is a plain primitive.
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) {"count":{"current":0},"updateCountPostfix":"[[ function params=0 ]]","updateCountPrefix":"[[ function params=0 ]]"}
* logs: ['id = 0','count = 1']
* Forget:
* (kind: ok) {"count":{"current":0},"updateCountPostfix":"[[ function params=0 ]]","updateCountPrefix":"[[ function params=0 ]]"}
* logs: ['id = 1','count = 1']
*/
function useFoo() {
const $ = _c(5);
const count = useRef(0);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
count.current = count.current + 1;
const id = count.current;
return id;
};
$[0] = t0;
} else {
t0 = $[0];
}
const updateCountPostfix = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
const id_0 = (count.current = count.current + 1);
return id_0;
};
$[1] = t1;
} else {
t1 = $[1];
}
const updateCountPrefix = t1;
let t2;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
const id_1 = updateCountPostfix();
console.log(`id = ${id_1}`);
console.log(`count = ${count.current}`);
};
t3 = [];
$[2] = t2;
$[3] = t3;
} else {
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
let t4;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t4 = { count, updateCountPostfix, updateCountPrefix };
$[4] = t4;
} else {
t4 = $[4];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
@@ -0,0 +1,42 @@
import {useRef, useEffect} from 'react';
/**
* The postfix increment operator should return the value before incrementing.
* ```js
* const id = count.current; // 0
* count.current = count.current + 1; // 1
* return id;
* ```
* The bug is that we currently increment the value before the expression is evaluated.
* This bug does not trigger when the incremented value is a plain primitive.
*
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) {"count":{"current":0},"updateCountPostfix":"[[ function params=0 ]]","updateCountPrefix":"[[ function params=0 ]]"}
* logs: ['id = 0','count = 1']
* Forget:
* (kind: ok) {"count":{"current":0},"updateCountPostfix":"[[ function params=0 ]]","updateCountPrefix":"[[ function params=0 ]]"}
* logs: ['id = 1','count = 1']
*/
function useFoo() {
const count = useRef(0);
const updateCountPostfix = () => {
const id = count.current++;
return id;
};
const updateCountPrefix = () => {
const id = ++count.current;
return id;
};
useEffect(() => {
const id = updateCountPostfix();
console.log(`id = ${id}`);
console.log(`count = ${count.current}`);
}, []);
return {count, updateCountPostfix, updateCountPrefix};
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
@@ -460,6 +460,7 @@ const skipFilter = new Set([
'fbt/bug-fbt-plural-multiple-function-calls',
'fbt/bug-fbt-plural-multiple-mixed-call-tag',
'bug-invalid-phi-as-dependency',
'bug-ref-prefix-postfix-operator',
// 'react-compiler-runtime' not yet supported
'flag-enable-emit-hook-guards',
+12 -1
View File
@@ -37,8 +37,19 @@ async function delay(text, ms) {
return new Promise(resolve => setTimeout(() => resolve(text), ms));
}
async function delayTwice() {
await delay('', 20);
await delay('', 10);
}
async function delayTrice() {
const p = delayTwice();
await delay('', 40);
return p;
}
async function Bar({children}) {
await delay('deferred text', 10);
await delayTrice();
return <div>{children}</div>;
}
@@ -4,6 +4,7 @@ import React, {
useEffect,
useState,
unstable_addTransitionType as addTransitionType,
use,
} from 'react';
import Chrome from './Chrome';
@@ -0,0 +1,36 @@
import React, {Suspense, use} from 'react';
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function Use({useable}) {
use(useable);
return null;
}
let delay1;
let delay2;
export default function NestedReveal({}) {
if (!delay1) {
delay1 = sleep(100);
// Needs to happen before the throttled reveal of delay 1
delay2 = sleep(200);
}
return (
<div className="swipe-recognizer">
Shell
<Suspense fallback="Loading level 1">
<div>Level 1</div>
<Use useable={delay1} />
<Suspense fallback="Loading level 2">
<div>Level 2</div>
<Use useable={delay2} />
</Suspense>
</Suspense>
</div>
);
}
@@ -18,6 +18,7 @@ import SwipeRecognizer from './SwipeRecognizer';
import './Page.css';
import transitions from './Transitions.module.css';
import NestedReveal from './NestedReveal';
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
@@ -241,6 +242,7 @@ export default function Page({url, navigate}) {
</div>
</ViewTransition>
</SwipeRecognizer>
<NestedReveal />
</div>
);
}
+97 -70
View File
@@ -2902,6 +2902,46 @@ function resolveTypedArray(
resolveBuffer(response, id, view);
}
function logComponentInfo(
response: Response,
root: SomeChunk<any>,
componentInfo: ReactComponentInfo,
trackIdx: number,
startTime: number,
componentEndTime: number,
childrenEndTime: number,
isLastComponent: boolean,
): void {
// $FlowFixMe: Refined.
if (
isLastComponent &&
root.status === ERRORED &&
root.reason !== response._closedReason
) {
// If this is the last component to render before this chunk rejected, then conceptually
// this component errored. If this was a cancellation then it wasn't this component that
// errored.
logComponentErrored(
componentInfo,
trackIdx,
startTime,
componentEndTime,
childrenEndTime,
response._rootEnvironmentName,
root.reason,
);
} else {
logComponentRender(
componentInfo,
trackIdx,
startTime,
componentEndTime,
childrenEndTime,
response._rootEnvironmentName,
);
}
}
function flushComponentPerformance(
response: Response,
root: SomeChunk<any>,
@@ -2957,21 +2997,20 @@ function flushComponentPerformance(
// in parallel with the previous.
const debugInfo = __DEV__ && root._debugInfo;
if (debugInfo) {
for (let i = 1; i < debugInfo.length; i++) {
let startTime = 0;
for (let i = 0; i < debugInfo.length; i++) {
const info = debugInfo[i];
if (typeof info.time === 'number') {
startTime = info.time;
}
if (typeof info.name === 'string') {
// $FlowFixMe: Refined.
const startTimeInfo = debugInfo[i - 1];
if (typeof startTimeInfo.time === 'number') {
const startTime = startTimeInfo.time;
if (startTime < trackTime) {
// The start time of this component is before the end time of the previous
// component on this track so we need to bump the next one to a parallel track.
trackIdx++;
}
trackTime = startTime;
break;
if (startTime < trackTime) {
// The start time of this component is before the end time of the previous
// component on this track so we need to bump the next one to a parallel track.
trackIdx++;
}
trackTime = startTime;
break;
}
}
for (let i = debugInfo.length - 1; i >= 0; i--) {
@@ -2979,6 +3018,7 @@ function flushComponentPerformance(
if (typeof info.time === 'number') {
if (info.time > parentEndTime) {
parentEndTime = info.time;
break; // We assume the highest number is at the end.
}
}
}
@@ -3006,85 +3046,72 @@ function flushComponentPerformance(
}
childTrackIdx = childResult.track;
const childEndTime = childResult.endTime;
childTrackTime = childEndTime;
if (childEndTime > childTrackTime) {
childTrackTime = childEndTime;
}
if (childEndTime > childrenEndTime) {
childrenEndTime = childEndTime;
}
}
if (debugInfo) {
let endTime = 0;
// Write debug info in reverse order (just like stack traces).
let componentEndTime = 0;
let isLastComponent = true;
let endTime = -1;
let endTimeIdx = -1;
for (let i = debugInfo.length - 1; i >= 0; i--) {
const info = debugInfo[i];
if (typeof info.time === 'number') {
if (info.time > childrenEndTime) {
childrenEndTime = info.time;
}
if (endTime === 0) {
// Last timestamp is the end of the last component.
endTime = info.time;
}
if (typeof info.time !== 'number') {
continue;
}
if (typeof info.name === 'string' && i > 0) {
// $FlowFixMe: Refined.
const componentInfo: ReactComponentInfo = info;
const startTimeInfo = debugInfo[i - 1];
if (typeof startTimeInfo.time === 'number') {
const startTime = startTimeInfo.time;
if (
isLastComponent &&
root.status === ERRORED &&
root.reason !== response._closedReason
) {
// If this is the last component to render before this chunk rejected, then conceptually
// this component errored. If this was a cancellation then it wasn't this component that
// errored.
logComponentErrored(
if (componentEndTime === 0) {
// Last timestamp is the end of the last component.
componentEndTime = info.time;
}
const time = info.time;
if (endTimeIdx > -1) {
// Now that we know the start and end time, we can emit the entries between.
for (let j = endTimeIdx - 1; j > i; j--) {
const candidateInfo = debugInfo[j];
if (typeof candidateInfo.name === 'string') {
if (componentEndTime > childrenEndTime) {
childrenEndTime = componentEndTime;
}
// $FlowFixMe: Refined.
const componentInfo: ReactComponentInfo = candidateInfo;
logComponentInfo(
response,
root,
componentInfo,
trackIdx,
startTime,
endTime,
time,
componentEndTime,
childrenEndTime,
response._rootEnvironmentName,
root.reason,
isLastComponent,
);
} else {
logComponentRender(
componentInfo,
componentEndTime = time; // The end time of previous component is the start time of the next.
// Track the root most component of the result for deduping logging.
result.component = componentInfo;
isLastComponent = false;
} else if (candidateInfo.awaited) {
if (endTime > childrenEndTime) {
childrenEndTime = endTime;
}
// $FlowFixMe: Refined.
const asyncInfo: ReactAsyncInfo = candidateInfo;
logComponentAwait(
asyncInfo,
trackIdx,
startTime,
time,
endTime,
childrenEndTime,
response._rootEnvironmentName,
);
}
// Track the root most component of the result for deduping logging.
result.component = componentInfo;
// Set the end time of the previous component to the start of the previous.
endTime = startTime;
}
isLastComponent = false;
} else if (info.awaited && i > 0 && i < debugInfo.length - 2) {
// $FlowFixMe: Refined.
const asyncInfo: ReactAsyncInfo = info;
const startTimeInfo = debugInfo[i - 1];
const endTimeInfo = debugInfo[i + 1];
if (
typeof startTimeInfo.time === 'number' &&
typeof endTimeInfo.time === 'number'
) {
const awaitStartTime = startTimeInfo.time;
const awaitEndTime = endTimeInfo.time;
logComponentAwait(
asyncInfo,
trackIdx,
awaitStartTime,
awaitEndTime,
response._rootEnvironmentName,
);
}
}
endTime = time; // The end time of the next entry is this time.
endTimeIdx = i;
}
}
result.endTime = childrenEndTime;
+1 -7
View File
@@ -18,13 +18,10 @@ import type {
import type {LazyComponent} from 'react/src/ReactLazy';
import type {TemporaryReferenceSet} from './ReactFlightTemporaryReferences';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
import {
REACT_ELEMENT_TYPE,
REACT_LAZY_TYPE,
REACT_CONTEXT_TYPE,
REACT_PROVIDER_TYPE,
getIteratorFn,
ASYNC_ITERATOR,
} from 'shared/ReactSymbols';
@@ -699,10 +696,7 @@ export function processReply(
return serializeTemporaryReferenceMarker();
}
if (__DEV__) {
if (
(value: any).$$typeof ===
(enableRenderableContext ? REACT_CONTEXT_TYPE : REACT_PROVIDER_TYPE)
) {
if ((value: any).$$typeof === REACT_CONTEXT_TYPE) {
console.error(
'React Context Providers cannot be passed to Server Functions from the Client.%s',
describeObjectForErrorMessage(parent, key),
+58
View File
@@ -2991,6 +2991,64 @@ describe('ReactFlight', () => {
);
});
// @gate !__DEV__ || enableComponentPerformanceTrack
it('preserves debug info for server-to-server through use()', async () => {
function ThirdPartyComponent() {
return 'hi';
}
function ServerComponent({transport}) {
// This is a Server Component that receives other Server Components from a third party.
const text = ReactServer.use(ReactNoopFlightClient.read(transport));
return <div>{text.toUpperCase()}</div>;
}
const thirdPartyTransport = ReactNoopFlightServer.render(
<ThirdPartyComponent />,
{
environmentName: 'third-party',
},
);
const transport = ReactNoopFlightServer.render(
<ServerComponent transport={thirdPartyTransport} />,
);
await act(async () => {
const promise = ReactNoopFlightClient.read(transport);
expect(getDebugInfo(promise)).toEqual(
__DEV__
? [
{time: 16},
{
name: 'ServerComponent',
env: 'Server',
key: null,
stack: ' in Object.<anonymous> (at **)',
props: {
transport: expect.arrayContaining([]),
},
},
{time: 16},
{
name: 'ThirdPartyComponent',
env: 'third-party',
key: null,
stack: ' in Object.<anonymous> (at **)',
props: {},
},
{time: 16},
{time: 17},
]
: undefined,
);
const result = await promise;
ReactNoop.render(result);
});
expect(ReactNoop).toMatchRenderedOutput(<div>HI</div>);
});
it('preserves error stacks passed through server-to-server with source maps', async () => {
async function ServerComponent({transport}) {
// This is a Server Component that receives other Server Components from a third party.
@@ -41,7 +41,8 @@ import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/f
import {useChangeOwnerAction} from './OwnersListContext';
// Never indent more than this number of pixels (even if we have the room).
const DEFAULT_INDENTATION_SIZE = 12;
const MAX_INDENTATION_SIZE = 12;
const MIN_INDENTATION_SIZE = 4;
export type ItemData = {
isNavigatingWithKeyboard: boolean,
@@ -490,11 +491,11 @@ function updateIndentationSizeVar(
// Reset the max indentation size if the width of the tree has increased.
if (listWidth > prevListWidthRef.current) {
indentationSizeRef.current = DEFAULT_INDENTATION_SIZE;
indentationSizeRef.current = MAX_INDENTATION_SIZE;
}
prevListWidthRef.current = listWidth;
let maxIndentationSize: number = indentationSizeRef.current;
let indentationSize: number = indentationSizeRef.current;
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const child of innerDiv.children) {
@@ -517,12 +518,13 @@ function updateIndentationSizeVar(
const remainingWidth = Math.max(0, listWidth - childWidth);
maxIndentationSize = Math.min(maxIndentationSize, remainingWidth / depth);
indentationSize = Math.min(indentationSize, remainingWidth / depth);
}
indentationSizeRef.current = maxIndentationSize;
indentationSize = Math.max(indentationSize, MIN_INDENTATION_SIZE);
indentationSizeRef.current = indentationSize;
list.style.setProperty('--indentation-size', `${maxIndentationSize}px`);
list.style.setProperty('--indentation-size', `${indentationSize}px`);
}
// $FlowFixMe[missing-local-annot]
@@ -545,7 +547,7 @@ function InnerElementType({children, style}) {
// The user may have resized the window specifically to make more room for DevTools.
// In either case, this should reset our max indentation size logic.
// 2. The second is when the user enters or exits an owner tree.
const indentationSizeRef = useRef<number>(DEFAULT_INDENTATION_SIZE);
const indentationSizeRef = useRef<number>(MAX_INDENTATION_SIZE);
const prevListWidthRef = useRef<number>(0);
const prevOwnerIDRef = useRef<number | null>(ownerID);
const divRef = useRef<HTMLDivElement | null>(null);
@@ -554,7 +556,7 @@ function InnerElementType({children, style}) {
// so when the user opens the "owners tree" view, we should discard the previous width.
if (ownerID !== prevOwnerIDRef.current) {
prevOwnerIDRef.current = ownerID;
indentationSizeRef.current = DEFAULT_INDENTATION_SIZE;
indentationSizeRef.current = MAX_INDENTATION_SIZE;
}
// When we render new content, measure to see if we need to shrink indentation to fit it.
+5 -11
View File
@@ -19,14 +19,12 @@ import {
REACT_MEMO_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
REACT_SUSPENSE_TYPE,
REACT_TRACING_MARKER_TYPE,
REACT_VIEW_TRANSITION_TYPE,
} from 'shared/ReactSymbols';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
import {
TREE_OPERATION_ADD,
TREE_OPERATION_REMOVE,
@@ -87,6 +85,9 @@ const encodedStringCache: LRUCache<string, Array<number>> = new LRU({
max: 1000,
});
// Previously, the type of `Context.Provider`.
const LEGACY_REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider');
export function alphaSortKeys(
a: string | number | symbol,
b: string | number | symbol,
@@ -712,14 +713,7 @@ function typeOfWithLegacyElementSymbol(object: any): mixed {
case REACT_MEMO_TYPE:
return $$typeofType;
case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
return $$typeofType;
}
// Fall through
case REACT_PROVIDER_TYPE:
if (!enableRenderableContext) {
return $$typeofType;
}
return $$typeofType;
// Fall through
default:
return $$typeof;
@@ -740,7 +734,7 @@ export function getDisplayNameForReactElement(
switch (elementType) {
case REACT_CONSUMER_TYPE:
return 'ContextConsumer';
case REACT_PROVIDER_TYPE:
case LEGACY_REACT_PROVIDER_TYPE:
return 'ContextProvider';
case REACT_CONTEXT_TYPE:
return 'Context';
+202 -63
View File
@@ -1,74 +1,213 @@
<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>React DevTools</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#target {
flex: 1;
border: none;
}
#devtools {
height: 400px;
max-height: 50%;
overflow: hidden;
z-index: 10000001;
}
body {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-size: 12px;
line-height: 1.5;
}
.optionsRow {
width: 100%;
display: flex;
padding: 0.25rem;
background: aliceblue;
border-bottom: 1px solid lightblue;
box-sizing: border-box;
}
.optionsRowSpacer {
flex: 1;
}
</style>
</head>
<body>
<div class="optionsRow">
<button id="mountButton">Unmount test app</button>
<div class="optionsRowSpacer">&nbsp;</div>
<span>
<a href="/multi.html">multi DevTools</a>
|
<a href="/e2e.html">e2e tests</a>
|
<a href="/e2e-regression.html">e2e regression tests</a>
|
<a href="/perf-regression.html">perf regression tests</a>
</span>
</div>
<head>
<meta charset="utf8">
<title>React DevTools</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#panes {
display: grid;
height: 100%;
width: 100%;
position: relative;
}
#divider {
position: absolute;
z-index: 10000002;
background-color: #ccc;
transition: background-color 0.2s;
}
#divider:hover,
#divider.dragging {
background-color: #aaa;
}
#divider.horizontal-divider {
width: 100%;
height: 5px;
cursor: row-resize;
}
#divider.vertical-divider {
width: 5px;
height: 100%;
cursor: col-resize;
}
#target {
height: 100%;
width: 100%;
border: none;
}
#devtools {
height: 100%;
width: 100%;
overflow: hidden;
z-index: 10000001;
}
body {
display: flex;
height: 100vh;
width: 100vw;
contain: strict;
flex-direction: column;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-size: 12px;
line-height: 1.5;
}
.optionsRow {
width: 100%;
display: flex;
padding: 0.25rem;
background: aliceblue;
border-bottom: 1px solid lightblue;
box-sizing: border-box;
}
.optionsRowSpacer {
flex: 1;
}
</style>
</head>
<body>
<div class="optionsRow">
<button id="mountButton">Unmount test app</button>
<div class="optionsRowSpacer">&nbsp;</div>
<span>
<a href="/multi.html">multi DevTools</a>
|
<a href="/e2e.html">e2e tests</a>
|
<a href="/e2e-regression.html">e2e regression tests</a>
|
<a href="/perf-regression.html">perf regression tests</a>
</span>
<label style="margin-left: 4px">
Layout:
<select id="layout">
<option value="leftright">Left/Right Split</option>
<option value="topbottom">Top/Bottom Split</option>
</select></label>
</div>
<div id="panes">
<!-- React test app (shells/dev/app) is injected here -->
<!-- DevTools backend (shells/dev/src) is injected here -->
<!-- global "hook" is defined on the iframe's contentWindow -->
<iframe id="target"></iframe>
<!-- Draggable divider between panes -->
<div id="divider"></div>
<!-- DevTools frontend UI (shells/dev/src) renders here -->
<div id="devtools"></div>
</div>
<!-- This script installs the hook, injects the backend, and renders the DevTools UI -->
<!-- In DEV mode, this file is served by the Webpack dev server -->
<!-- For production builds, it's built by Webpack and uploaded from the local file system -->
<script src="dist/app-devtools.js"></script>
</body>
</html>
<!-- This script installs the hook, injects the backend, and renders the DevTools UI -->
<!-- In DEV mode, this file is served by the Webpack dev server -->
<!-- For production builds, it's built by Webpack and uploaded from the local file system -->
<script src="dist/app-devtools.js"></script>
<script type="module">
let layoutType = 'leftright';
let splitRatio = 0.5;
let isDragging = false;
// handle layout changes
const layout = document.getElementById('layout');
function setLayout(layoutType, splitRatio) {
const panes = document.getElementById('panes');
if (layoutType === 'topbottom') {
panes.style.gridTemplateColumns = '100%'; // Full width for each row
panes.style.gridTemplateRows = `${splitRatio * 100}% ${(1 - splitRatio) * 100}%`;
} else if (layoutType === 'leftright') {
panes.style.gridTemplateRows = '100%'; // Full height for each column
panes.style.gridTemplateColumns = `${splitRatio * 100}% ${(1 - splitRatio) * 100}%`;
}
}
layout.addEventListener('change', () => {
layoutType = layout.value;
setLayout(layoutType, splitRatio);
updateDividerPosition(); // Ensure divider updates when layout changes
});
// handle changing the split ratio
const divider = document.getElementById('divider');
function updateDividerPosition() {
if (layoutType === 'topbottom') {
// For top/bottom layout, divider should be horizontal (spanning across)
divider.className = 'horizontal-divider';
divider.style.top = `calc(${splitRatio * 100}% - 2.5px)`;
divider.style.left = '0';
} else {
// For left/right layout, divider should be vertical (spanning down)
divider.className = 'vertical-divider';
divider.style.left = `calc(${splitRatio * 100}% - 2.5px)`;
divider.style.top = '0';
}
}
// Add event listeners for dragging
divider.addEventListener('mousedown', (e) => {
isDragging = true;
divider.classList.add('dragging');
// Disable pointer events on the iframe to prevent it from capturing mouse events
const iframe = document.getElementById('target');
iframe.style.pointerEvents = 'none';
e.preventDefault(); // Prevent text selection during drag
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const panes = document.getElementById('panes');
const rect = panes.getBoundingClientRect();
if (layoutType === 'topbottom') {
// Calculate new split ratio based on vertical position
const newRatio = Math.max(0.1, Math.min(0.9, (e.clientY - rect.top) / rect.height));
splitRatio = newRatio;
} else {
// Calculate new split ratio based on horizontal position
const newRatio = Math.max(0.1, Math.min(0.9, (e.clientX - rect.left) / rect.width));
splitRatio = newRatio;
}
// Update layout and divider position
setLayout(layoutType, splitRatio);
updateDividerPosition();
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
divider.classList.remove('dragging');
// Re-enable pointer events on the iframe
const iframe = document.getElementById('target');
iframe.style.pointerEvents = 'auto';
}
});
// Initialize
setLayout(
layoutType,
splitRatio,
);
updateDividerPosition();
</script>
</body>
</html>
@@ -6,7 +6,7 @@ export const markShellTime =
export const clientRenderBoundary =
'$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};';
export const completeBoundary =
'$RB=[];$RV=function(b){$RT=performance.now();for(var a=0;a<b.length;a+=2){var c=b[a],h=b[a+1],e=c.parentNode;if(e){var f=c.previousSibling,g=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d||"/&"===d)if(0===g)break;else g--;else"$"!==d&&"$?"!==d&&"$~"!==d&&"$!"!==d&&"&"!==d||g++}d=c.nextSibling;e.removeChild(c);c=d}while(c);for(;h.firstChild;)e.insertBefore(h.firstChild,c);f.data="$";f._reactRetry&&f._reactRetry()}}b.length=0};$RC=function(b,a){if(a=document.getElementById(a))if(a.parentNode.removeChild(a),b=document.getElementById(b))b.previousSibling.data="$~",$RB.push(b,a),2===$RB.length&&(b="number"!==typeof $RT?0:$RT,a=performance.now(),setTimeout($RV.bind(null,$RB),2300>a&&2E3<a?2300-a:b+300-a))};';
'$RB=[];$RV=function(b){$RT=performance.now();for(var a=0;a<b.length;a+=2){var c=b[a],e=b[a+1];e.parentNode.removeChild(e);var f=c.parentNode;if(f){var g=c.previousSibling,h=0;do{if(c&&8===c.nodeType){var d=c.data;if("/$"===d||"/&"===d)if(0===h)break;else h--;else"$"!==d&&"$?"!==d&&"$~"!==d&&"$!"!==d&&"&"!==d||h++}d=c.nextSibling;f.removeChild(c);c=d}while(c);for(;e.firstChild;)f.insertBefore(e.firstChild,c);g.data="$";g._reactRetry&&g._reactRetry()}}b.length=0};$RC=function(b,a){if(a=document.getElementById(a))(b=document.getElementById(b))?(b.previousSibling.data="$~",$RB.push(b,a),2===$RB.length&&(b="number"!==typeof $RT?0:$RT,a=performance.now(),setTimeout($RV.bind(null,$RB),2300>a&&2E3<a?2300-a:b+300-a))):a.parentNode.removeChild(a)};';
export const completeBoundaryUpgradeToViewTransitions =
'$RV=function(A,g){function k(a,b){var e=a.getAttribute(b);e&&(b=a.style,l.push(a,b.viewTransitionName,b.viewTransitionClass),"auto"!==e&&(b.viewTransitionClass=e),(a=a.getAttribute("vt-name"))||(a="_T_"+K++ +"_"),b.viewTransitionName=a,B=!0)}var B=!1,K=0,l=[];try{var f=document.__reactViewTransition;if(f){f.finished.finally($RV.bind(null,g));return}var m=new Map;for(f=1;f<g.length;f+=2)for(var h=g[f].querySelectorAll("[vt-share]"),d=0;d<h.length;d++){var c=h[d];m.set(c.getAttribute("vt-name"),c)}var u=[];for(h=0;h<g.length;h+=2){var C=g[h],x=C.parentNode;if(x){var v=x.getBoundingClientRect();if(v.left||v.top||v.width||v.height){c=C;for(f=0;c;){if(8===c.nodeType){var r=c.data;if("/$"===r)if(0===f)break;else f--;else"$"!==r&&"$?"!==r&&"$~"!==r&&"$!"!==r||f++}else if(1===c.nodeType){d=c;var D=d.getAttribute("vt-name"),y=m.get(D);k(d,y?"vt-share":"vt-exit");y&&(k(y,"vt-share"),m.set(D,null));var E=d.querySelectorAll("[vt-share]");for(d=0;d<E.length;d++){var F=E[d],G=F.getAttribute("vt-name"),\nH=m.get(G);H&&(k(F,"vt-share"),k(H,"vt-share"),m.set(G,null))}}c=c.nextSibling}for(var I=g[h+1],t=I.firstElementChild;t;)null!==m.get(t.getAttribute("vt-name"))&&k(t,"vt-enter"),t=t.nextElementSibling;c=x;do for(var n=c.firstElementChild;n;){var J=n.getAttribute("vt-update");J&&"none"!==J&&!l.includes(n)&&k(n,"vt-update");n=n.nextElementSibling}while((c=c.parentNode)&&1===c.nodeType&&"none"!==c.getAttribute("vt-update"));u.push.apply(u,I.querySelectorAll(\'img[src]:not([loading="lazy"])\'))}}}if(B){var z=\ndocument.__reactViewTransition=document.startViewTransition({update:function(){A(g);for(var a=[document.documentElement.clientHeight,document.fonts.ready],b={},e=0;e<u.length;b={g:b.g},e++)if(b.g=u[e],!b.g.complete){var p=b.g.getBoundingClientRect();0<p.bottom&&0<p.right&&p.top<window.innerHeight&&p.left<window.innerWidth&&(p=new Promise(function(w){return function(q){w.g.addEventListener("load",q);w.g.addEventListener("error",q)}}(b)),a.push(p))}return Promise.race([Promise.all(a),new Promise(function(w){var q=\nperformance.now();setTimeout(w,2300>q&&2E3<q?2300-q:500)})])},types:[]});z.ready.finally(function(){for(var a=l.length-3;0<=a;a-=3){var b=l[a],e=b.style;e.viewTransitionName=l[a+1];e.viewTransitionClass=l[a+1];""===b.getAttribute("style")&&b.removeAttribute("style")}});z.finished.finally(function(){document.__reactViewTransition===z&&(document.__reactViewTransition=null)});$RB=[];return}}catch(a){}A(g)}.bind(null,$RV);';
export const completeBoundaryWithStyles =
@@ -34,6 +34,10 @@ export function revealCompletedBoundaries(batch) {
for (let i = 0; i < batch.length; i += 2) {
const suspenseIdNode = batch[i];
const contentNode = batch[i + 1];
// We can detach the content now.
// Completions of boundaries within this contentNode will now find the boundary
// in its designated place.
contentNode.parentNode.removeChild(contentNode);
// Clear all the existing children. This is complicated because
// there can be embedded Suspense boundaries in the fallback.
@@ -385,13 +389,16 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
// the segment. Regardless we can ignore this case.
return;
}
// We'll detach the content node so that regardless of what happens next we don't leave in the tree.
// This might also help by not causing recalcing each time we move a child from here to the target.
contentNodeOuter.parentNode.removeChild(contentNodeOuter);
// Find the fallback's first element.
const suspenseIdNodeOuter = document.getElementById(suspenseBoundaryID);
if (!suspenseIdNodeOuter) {
// We'll never reveal this boundary so we can remove its content immediately.
// Otherwise we'll leave it in until we reveal it.
// This is important in case this specific boundary contains other boundaries
// that may get completed before we reveal this one.
contentNodeOuter.parentNode.removeChild(contentNodeOuter);
// The user must have already navigated away from this tree.
// E.g. because the parent was hydrated. That's fine there's nothing to do
// but we have to make sure that we already deleted the container node.
@@ -295,12 +295,6 @@ describe('ReactDOMServerIntegration', () => {
});
itRenders('should treat Context as Context.Provider', async render => {
// The `itRenders` helpers don't work with the gate pragma, so we have to do
// this instead.
if (gate(flags => !flags.enableRenderableContext)) {
return;
}
const Theme = React.createContext('dark');
const Language = React.createContext('french');
+371 -1
View File
@@ -7,7 +7,6 @@
* @emails react-core
* @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
*/
let JSDOM;
let React;
let ReactDOMClient;
@@ -24,6 +23,8 @@ let buffer = '';
let hasErrored = false;
let fatalError = undefined;
let waitForPaint;
let SuspenseList;
let assertConsoleErrorDev;
describe('useId', () => {
beforeEach(() => {
@@ -32,11 +33,16 @@ describe('useId', () => {
React = require('react');
ReactDOMClient = require('react-dom/client');
clientAct = require('internal-test-utils').act;
assertConsoleErrorDev =
require('internal-test-utils').assertConsoleErrorDev;
ReactDOMFizzServer = require('react-dom/server');
Stream = require('stream');
Suspense = React.Suspense;
useId = React.useId;
useState = React.useState;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.unstable_SuspenseList;
}
const InternalTestUtils = require('internal-test-utils');
waitForPaint = InternalTestUtils.waitForPaint;
@@ -375,6 +381,370 @@ describe('useId', () => {
`);
});
// @gate enableSuspenseList
it('Supports SuspenseList (reveal order independent)', async () => {
function Baz({id, children}) {
return <span id={id}>{children}</span>;
}
function Bar({children}) {
const id = useId();
return <Baz id={id}>{children}</Baz>;
}
function Foo() {
return (
<SuspenseList revealOrder="independent">
<Bar>A</Bar>
<Bar>B</Bar>
</SuspenseList>
);
}
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
pipe(writable);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
await clientAct(async () => {
ReactDOMClient.hydrateRoot(container, <Foo />);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
});
// @gate enableSuspenseList
it('Supports SuspenseList (reveal order "together")', async () => {
function Baz({id, children}) {
return <span id={id}>{children}</span>;
}
function Bar({children}) {
const id = useId();
return <Baz id={id}>{children}</Baz>;
}
function Foo() {
return (
<SuspenseList revealOrder="together">
<Bar>A</Bar>
<Bar>B</Bar>
</SuspenseList>
);
}
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
pipe(writable);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
await clientAct(async () => {
ReactDOMClient.hydrateRoot(container, <Foo />);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
});
// @gate enableSuspenseList
it('Supports SuspenseList (reveal order "forwards")', async () => {
function Baz({id, children}) {
return <span id={id}>{children}</span>;
}
function Bar({children}) {
const id = useId();
return <Baz id={id}>{children}</Baz>;
}
function Foo() {
return (
<SuspenseList revealOrder="forwards" tail="visible">
<Bar>A</Bar>
<Bar>B</Bar>
</SuspenseList>
);
}
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
pipe(writable);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
await clientAct(async () => {
ReactDOMClient.hydrateRoot(container, <Foo />);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
});
// @gate enableSuspenseList
it('Supports SuspenseList (reveal order "backwards") with a single child in a list of many', async () => {
function Baz({id, children}) {
return <span id={id}>{children}</span>;
}
function Bar({children}) {
const id = useId();
return <Baz id={id}>{children}</Baz>;
}
function Foo() {
return (
<SuspenseList revealOrder="unstable_legacy-backwards" tail="visible">
{null}
<Bar>A</Bar>
{null}
</SuspenseList>
);
}
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
pipe(writable);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_2_"
>
A
</span>
<!-- -->
</div>
`);
await clientAct(async () => {
ReactDOMClient.hydrateRoot(container, <Foo />);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_2_"
>
A
</span>
<!-- -->
</div>
`);
});
// @gate enableSuspenseList
it('Supports SuspenseList (reveal order "backwards")', async () => {
function Baz({id, children}) {
return <span id={id}>{children}</span>;
}
function Bar({children}) {
const id = useId();
return <Baz id={id}>{children}</Baz>;
}
function Foo() {
return (
<SuspenseList revealOrder="unstable_legacy-backwards" tail="visible">
<Bar>A</Bar>
<Bar>B</Bar>
</SuspenseList>
);
}
await serverAct(async () => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
pipe(writable);
});
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
if (gate(flags => flags.favorSafetyOverHydrationPerf)) {
// TODO: This is a bug with revealOrder="backwards" in that it hydrates in reverse.
await expect(async () => {
await clientAct(async () => {
ReactDOMClient.hydrateRoot(container, <Foo />);
});
}).rejects.toThrowError(
`Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client.`,
);
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_r_1_"
>
A
</span>
<span
id="_r_0_"
>
B
</span>
</div>
`);
} else {
await clientAct(async () => {
ReactDOMClient.hydrateRoot(container, <Foo />);
});
// TODO: This is a bug with revealOrder="backwards" in that it hydrates in reverse.
assertConsoleErrorDev([
`A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:
- A server/client branch \`if (typeof window !== 'undefined')\`.
- Variable input such as \`Date.now()\` or \`Math.random()\` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
<Foo>
<SuspenseList revealOrder="unstable_l..." tail="visible">
<Bar>
<Bar>
<Baz id="_R_2_">
<span
+ id="_R_2_"
- id="_R_1_"
>
+ B
- A
`,
]);
expect(container).toMatchInlineSnapshot(`
<div
id="container"
>
<span
id="_R_1_"
>
A
</span>
<span
id="_R_2_"
>
B
</span>
</div>
`);
}
});
it('basic incremental hydration', async () => {
function App() {
return (
@@ -932,7 +932,6 @@ describe('ReactDOMServer', () => {
]);
});
// @gate enableRenderableContext || !__DEV__
it('should warn if an invalid contextType is defined', () => {
const Context = React.createContext();
class ComponentA extends React.Component {
+6 -28
View File
@@ -18,7 +18,6 @@ import {
REACT_MEMO_TYPE,
REACT_PORTAL_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONSUMER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
@@ -30,7 +29,6 @@ import {
} from 'shared/ReactSymbols';
import {
enableRenderableContext,
enableScopeAPI,
enableTransitionTracing,
enableLegacyHidden,
@@ -64,14 +62,7 @@ export function typeOf(object: any): mixed {
case REACT_MEMO_TYPE:
return $$typeofType;
case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
return $$typeofType;
}
// Fall through
case REACT_PROVIDER_TYPE:
if (!enableRenderableContext) {
return $$typeofType;
}
return $$typeofType;
// Fall through
default:
return $$typeof;
@@ -85,12 +76,8 @@ export function typeOf(object: any): mixed {
return undefined;
}
export const ContextConsumer: symbol = enableRenderableContext
? REACT_CONSUMER_TYPE
: REACT_CONTEXT_TYPE;
export const ContextProvider: symbol = enableRenderableContext
? REACT_CONTEXT_TYPE
: REACT_PROVIDER_TYPE;
export const ContextConsumer: symbol = REACT_CONSUMER_TYPE;
export const ContextProvider: symbol = REACT_CONTEXT_TYPE;
export const Element = REACT_ELEMENT_TYPE;
export const ForwardRef = REACT_FORWARD_REF_TYPE;
export const Fragment = REACT_FRAGMENT_TYPE;
@@ -127,8 +114,7 @@ export function isValidElementType(type: mixed): boolean {
type.$$typeof === REACT_LAZY_TYPE ||
type.$$typeof === REACT_MEMO_TYPE ||
type.$$typeof === REACT_CONTEXT_TYPE ||
(!enableRenderableContext && type.$$typeof === REACT_PROVIDER_TYPE) ||
(enableRenderableContext && type.$$typeof === REACT_CONSUMER_TYPE) ||
type.$$typeof === REACT_CONSUMER_TYPE ||
type.$$typeof === REACT_FORWARD_REF_TYPE ||
// This needs to include all possible module reference object
// types supported by any Flight configuration anywhere since
@@ -145,18 +131,10 @@ export function isValidElementType(type: mixed): boolean {
}
export function isContextConsumer(object: any): boolean {
if (enableRenderableContext) {
return typeOf(object) === REACT_CONSUMER_TYPE;
} else {
return typeOf(object) === REACT_CONTEXT_TYPE;
}
return typeOf(object) === REACT_CONSUMER_TYPE;
}
export function isContextProvider(object: any): boolean {
if (enableRenderableContext) {
return typeOf(object) === REACT_CONTEXT_TYPE;
} else {
return typeOf(object) === REACT_PROVIDER_TYPE;
}
return typeOf(object) === REACT_CONTEXT_TYPE;
}
export function isElement(object: any): boolean {
return (
+4 -19
View File
@@ -41,7 +41,6 @@ import {
enableLegacyHidden,
enableTransitionTracing,
enableDO_NOT_USE_disableStrictPassiveEffect,
enableRenderableContext,
disableLegacyMode,
enableObjectFiber,
enableViewTransition,
@@ -101,7 +100,6 @@ import {
REACT_FRAGMENT_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_SUSPENSE_TYPE,
@@ -638,25 +636,12 @@ export function createFiberFromTypeAndProps(
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
if (!enableRenderableContext) {
fiberTag = ContextProvider;
break getTag;
}
// Fall through
case REACT_CONTEXT_TYPE:
if (enableRenderableContext) {
fiberTag = ContextProvider;
break getTag;
} else {
fiberTag = ContextConsumer;
break getTag;
}
fiberTag = ContextProvider;
break getTag;
case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
fiberTag = ContextConsumer;
break getTag;
}
fiberTag = ContextConsumer;
break getTag;
// Fall through
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
+12 -25
View File
@@ -116,7 +116,6 @@ import {
enableLegacyHidden,
enableCPUSuspense,
enablePostpone,
enableRenderableContext,
disableLegacyMode,
disableDefaultPropsExceptForClasses,
enableHydrationLaneScheduling,
@@ -3342,6 +3341,7 @@ function initSuspenseListRenderState(
tail: null | Fiber,
lastContentRow: null | Fiber,
tailMode: SuspenseListTailMode,
treeForkCount: number,
): void {
const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;
@@ -3353,6 +3353,7 @@ function initSuspenseListRenderState(
last: lastContentRow,
tail: tail,
tailMode: tailMode,
treeForkCount: treeForkCount,
}: SuspenseListRenderState);
} else {
// We can reuse the existing object from previous renders.
@@ -3362,6 +3363,7 @@ function initSuspenseListRenderState(
renderState.last = lastContentRow;
renderState.tail = tail;
renderState.tailMode = tailMode;
renderState.treeForkCount = treeForkCount;
}
}
@@ -3404,6 +3406,8 @@ function updateSuspenseListComponent(
validateSuspenseListChildren(newChildren, revealOrder);
reconcileChildren(current, workInProgress, newChildren, renderLanes);
// Read how many children forks this set pushed so we can push it every time we retry.
const treeForkCount = getIsHydrating() ? getForksAtLevel(workInProgress) : 0;
if (!shouldForceFallback) {
const didSuspendBefore =
@@ -3446,6 +3450,7 @@ function updateSuspenseListComponent(
tail,
lastContentRow,
tailMode,
treeForkCount,
);
break;
}
@@ -3478,6 +3483,7 @@ function updateSuspenseListComponent(
tail,
null, // last
tailMode,
treeForkCount,
);
break;
}
@@ -3488,6 +3494,7 @@ function updateSuspenseListComponent(
null, // tail
null, // last
undefined,
treeForkCount,
);
break;
}
@@ -3583,12 +3590,7 @@ function updateContextProvider(
workInProgress: Fiber,
renderLanes: Lanes,
) {
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
const context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
const newValue = newProps.value;
@@ -3615,18 +3617,8 @@ function updateContextConsumer(
workInProgress: Fiber,
renderLanes: Lanes,
) {
let context: ReactContext<any>;
if (enableRenderableContext) {
const consumerType: ReactConsumerType<any> = workInProgress.type;
context = consumerType._context;
} else {
context = workInProgress.type;
if (__DEV__) {
if ((context: any)._context !== undefined) {
context = (context: any)._context;
}
}
}
const consumerType: ReactConsumerType<any> = workInProgress.type;
const context: ReactContext<any> = consumerType._context;
const newProps = workInProgress.pendingProps;
const render = newProps.children;
@@ -3870,12 +3862,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
const context: ReactContext<any> = workInProgress.type;
pushProvider(workInProgress, context, newValue);
break;
}
+10 -8
View File
@@ -38,7 +38,6 @@ import {
enablePersistedModeClonedFlag,
enableProfilerTimer,
enableTransitionTracing,
enableRenderableContext,
passChildrenWhenCloningPersistedNodes,
disableLegacyMode,
enableViewTransition,
@@ -184,7 +183,7 @@ import {resetChildFibers} from './ReactChildFiber';
import {createScopeInstance} from './ReactFiberScope';
import {transferActualDuration} from './ReactProfilerTimer';
import {popCacheProvider} from './ReactFiberCacheComponent';
import {popTreeContext} from './ReactFiberTreeContext';
import {popTreeContext, pushTreeFork} from './ReactFiberTreeContext';
import {popRootTransition, popTransition} from './ReactFiberTransition';
import {
popMarkerInstance,
@@ -1667,12 +1666,7 @@ function completeWork(
return null;
case ContextProvider:
// Pop provider fiber
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
const context: ReactContext<any> = workInProgress.type;
popProvider(context, workInProgress);
bubbleProperties(workInProgress);
return null;
@@ -1764,6 +1758,10 @@ function completeWork(
ForceSuspenseFallback,
),
);
if (getIsHydrating()) {
// Re-apply tree fork since we popped the tree fork context in the beginning of this function.
pushTreeFork(workInProgress, renderState.treeForkCount);
}
// Don't bubble properties in this case.
return workInProgress.child;
}
@@ -1890,6 +1888,10 @@ function completeWork(
}
pushSuspenseListContext(workInProgress, suspenseContext);
// Do a pass over the next row.
if (getIsHydrating()) {
// Re-apply tree fork since we popped the tree fork context in the beginning of this function.
pushTreeFork(workInProgress, renderState.treeForkCount);
}
// Don't bubble properties in this case.
return next;
}
+1 -8
View File
@@ -29,7 +29,6 @@ import {
} from './ReactFiberFlags';
import is from 'shared/objectIs';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
import {getHostTransitionProvider} from './ReactFiberHostContext';
const valueCursor: StackCursor<mixed> = createCursor(null);
@@ -389,13 +388,7 @@ function propagateParentContextChanges(
const oldProps = currentParent.memoizedProps;
if (oldProps !== null) {
let context: ReactContext<any>;
if (enableRenderableContext) {
context = parent.type;
} else {
context = parent.type._context;
}
const context: ReactContext<any> = parent.type;
const newProps = parent.pendingProps;
const newValue = newProps.value;
+2 -8
View File
@@ -22,10 +22,7 @@ import {
import {isFiberSuspenseAndTimedOut} from './ReactFiberTreeReflection';
import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags';
import {
enableScopeAPI,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
@@ -116,10 +113,7 @@ function collectNearestContextValues<T>(
context: ReactContext<T>,
childContextValues: Array<T>,
): void {
if (
node.tag === ContextProvider &&
(enableRenderableContext ? node.type : node.type._context) === context
) {
if (node.tag === ContextProvider && node.type === context) {
const contextValue = node.memoizedProps.value;
childContextValues.push(contextValue);
} else {
@@ -54,6 +54,8 @@ export type SuspenseListRenderState = {
tail: null | Fiber,
// Tail insertions setting.
tailMode: SuspenseListTailMode,
// Keep track of total number of forks during multiple passes
treeForkCount: number,
};
export type RetryQueue = Set<Wakeable>;
+2 -13
View File
@@ -36,7 +36,6 @@ import {NoMode, ProfileMode} from './ReactTypeOfMode';
import {
enableProfilerTimer,
enableTransitionTracing,
enableRenderableContext,
} from 'shared/ReactFeatureFlags';
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
@@ -189,12 +188,7 @@ function unwindWork(
popHostContainer(workInProgress);
return null;
case ContextProvider:
let context: ReactContext<any>;
if (enableRenderableContext) {
context = workInProgress.type;
} else {
context = workInProgress.type._context;
}
const context: ReactContext<any> = workInProgress.type;
popProvider(context, workInProgress);
return null;
case OffscreenComponent:
@@ -286,12 +280,7 @@ function unwindInterruptedWork(
popSuspenseListContext(interruptedWork);
break;
case ContextProvider:
let context: ReactContext<any>;
if (enableRenderableContext) {
context = interruptedWork.type;
} else {
context = interruptedWork.type._context;
}
const context: ReactContext<any> = interruptedWork.type;
popProvider(context, interruptedWork);
break;
case OffscreenComponent:
@@ -941,15 +941,11 @@ describe('ReactLazy', () => {
</Suspense>,
);
await waitForThrow(
gate('enableRenderableContext')
? 'Element type is invalid. Received a promise that resolves to: Context.Provider. ' +
'Lazy element type must resolve to a class or function.'
: 'Element type is invalid. Received a promise that resolves to: Context.Consumer. ' +
'Lazy element type must resolve to a class or function.',
'Element type is invalid. Received a promise that resolves to: Context. ' +
'Lazy element type must resolve to a class or function.',
);
});
// @gate enableRenderableContext
it('throws with a useful error when wrapping Context.Consumer with lazy()', async () => {
const Context = React.createContext(null);
const BadLazy = lazy(() => fakeImport(Context.Consumer));
@@ -1358,7 +1358,6 @@ describe('ReactNewContext', () => {
);
});
// @gate enableRenderableContext || !__DEV__
it('warns when passed a consumer', async () => {
const Context = React.createContext(0);
function Foo() {
@@ -1657,7 +1656,6 @@ Context fuzz tester error! Copy and paste the following line into the test suite
});
});
// @gate enableRenderableContext
it('should treat Context as Context.Provider', async () => {
const BarContext = React.createContext({value: 'bar-initial'});
expect(BarContext.Provider).toBe(BarContext);
+4 -15
View File
@@ -13,7 +13,6 @@ import type {Fiber} from './ReactInternalTypes';
import {
disableLegacyMode,
enableLegacyHidden,
enableRenderableContext,
enableViewTransition,
} from 'shared/ReactFeatureFlags';
@@ -91,21 +90,11 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null {
case CacheComponent:
return 'Cache';
case ContextConsumer:
if (enableRenderableContext) {
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
} else {
const context: ReactContext<any> = (type: any);
return getContextName(context) + '.Consumer';
}
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
case ContextProvider:
if (enableRenderableContext) {
const context: ReactContext<any> = (type: any);
return getContextName(context) + '.Provider';
} else {
const provider = (type: any);
return getContextName(provider._context) + '.Provider';
}
const context: ReactContext<any> = (type: any);
return getContextName(context);
case DehydratedFragment:
return 'DehydratedFragment';
case ForwardRef:
@@ -161,6 +161,9 @@ const deepProxyHandlers = {
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
@@ -210,6 +213,9 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe {
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
@@ -162,6 +162,9 @@ const deepProxyHandlers = {
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
@@ -211,6 +214,9 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe {
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
+7 -31
View File
@@ -163,7 +163,6 @@ import {
REACT_FRAGMENT_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_SCOPE_TYPE,
@@ -178,7 +177,6 @@ import {
enableScopeAPI,
enablePostpone,
enableHalt,
enableRenderableContext,
disableDefaultPropsExceptForClasses,
enableAsyncIterableChildren,
enableViewTransition,
@@ -2959,38 +2957,16 @@ function renderElement(
renderMemo(request, task, keyPath, type, props, ref);
return;
}
case REACT_PROVIDER_TYPE: {
if (!enableRenderableContext) {
const context: ReactContext<any> = (type: any)._context;
renderContextProvider(request, task, keyPath, context, props);
return;
}
// Fall through
}
case REACT_CONTEXT_TYPE: {
if (enableRenderableContext) {
const context = type;
renderContextProvider(request, task, keyPath, context, props);
return;
} else {
let context: ReactContext<any> = (type: any);
if (__DEV__) {
if ((context: any)._context !== undefined) {
context = (context: any)._context;
}
}
renderContextConsumer(request, task, keyPath, context, props);
return;
}
const context = type;
renderContextProvider(request, task, keyPath, context, props);
return;
}
case REACT_CONSUMER_TYPE: {
if (enableRenderableContext) {
const context: ReactContext<any> = (type: ReactConsumerType<any>)
._context;
renderContextConsumer(request, task, keyPath, context, props);
return;
}
// Fall through
const context: ReactContext<any> = (type: ReactConsumerType<any>)
._context;
renderContextConsumer(request, task, keyPath, context, props);
return;
}
case REACT_LAZY_TYPE: {
renderLazyComponent(request, task, keyPath, type, props, ref);
+1 -1
View File
@@ -38,7 +38,7 @@ export type PromiseNode = {
start: number, // start time when the Promise was created
end: number, // end time when the Promise was resolved.
awaited: null | AsyncSequence, // the thing that ended up resolving this promise
previous: null, // where we created the promise is not interesting since creating it doesn't mean waiting.
previous: null | AsyncSequence, // represents what the last return of an async function depended on before returning
};
export type AwaitNode = {
+6
View File
@@ -58,6 +58,12 @@ export function getThenableStateAfterSuspending(): ThenableState {
return state;
}
export function getTrackedThenablesAfterRendering(): null | Array<
Thenable<any>,
> {
return thenableState;
}
export const HooksDispatcher: Dispatcher = {
readContext: (unsupportedContext: any),
+190 -133
View File
@@ -91,6 +91,7 @@ import {
initAsyncDebugInfo,
markAsyncSequenceRootTask,
getCurrentAsyncSequence,
getAsyncSequenceFromPromise,
parseStackTrace,
supportsComponentStorage,
componentStorage,
@@ -106,6 +107,7 @@ import {
prepareToUseHooksForRequest,
prepareToUseHooksForComponent,
getThenableStateAfterSuspending,
getTrackedThenablesAfterRendering,
resetHooksForRequest,
} from './ReactFlightHooks';
import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';
@@ -690,26 +692,14 @@ function serializeThenable(
switch (thenable.status) {
case 'fulfilled': {
if (__DEV__) {
// If this came from Flight, forward any debug info into this new row.
const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
if (debugInfo) {
forwardDebugInfo(request, newTask, debugInfo);
}
}
forwardDebugInfoFromThenable(request, newTask, thenable, null, null);
// We have the resolved value, we can go ahead and schedule it for serialization.
newTask.model = thenable.value;
pingTask(request, newTask);
return newTask.id;
}
case 'rejected': {
if (__DEV__) {
// If this came from Flight, forward any debug info into this new row.
const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
if (debugInfo) {
forwardDebugInfo(request, newTask, debugInfo);
}
}
forwardDebugInfoFromThenable(request, newTask, thenable, null, null);
const x = thenable.reason;
erroredTask(request, newTask, x);
return newTask.id;
@@ -758,25 +748,16 @@ function serializeThenable(
thenable.then(
value => {
if (__DEV__) {
// If this came from Flight, forward any debug info into this new row.
const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
if (debugInfo) {
forwardDebugInfo(request, newTask, debugInfo);
}
}
forwardDebugInfoFromCurrentContext(request, newTask, thenable);
newTask.model = value;
pingTask(request, newTask);
},
reason => {
if (__DEV__) {
// If this came from Flight, forward any debug info into this new row.
const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
if (debugInfo) {
forwardDebugInfo(request, newTask, debugInfo);
}
}
if (newTask.status === PENDING) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// If this is async we need to time when this task finishes.
newTask.timed = true;
}
// We expect that the only status it might be otherwise is ABORTED.
// When we abort we emit chunks in each pending task slot and don't need
// to do so again here.
@@ -786,11 +767,6 @@ function serializeThenable(
},
);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// If this is async we need to time when this task finishes.
newTask.timed = true;
}
return newTask.id;
}
@@ -1056,13 +1032,21 @@ function readThenable<T>(thenable: Thenable<T>): T {
throw thenable;
}
function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
function createLazyWrapperAroundWakeable(
request: Request,
task: Task,
wakeable: Wakeable,
) {
// This is a temporary fork of the `use` implementation until we accept
// promises everywhere.
const thenable: Thenable<mixed> = (wakeable: any);
switch (thenable.status) {
case 'fulfilled':
case 'fulfilled': {
forwardDebugInfoFromThenable(request, task, thenable, null, null);
return thenable.value;
}
case 'rejected':
forwardDebugInfoFromThenable(request, task, thenable, null, null);
break;
default: {
if (typeof thenable.status === 'string') {
@@ -1075,6 +1059,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
pendingThenable.status = 'pending';
pendingThenable.then(
fulfilledValue => {
forwardDebugInfoFromCurrentContext(request, task, thenable);
if (thenable.status === 'pending') {
const fulfilledThenable: FulfilledThenable<mixed> = (thenable: any);
fulfilledThenable.status = 'fulfilled';
@@ -1082,6 +1067,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
}
},
(error: mixed) => {
forwardDebugInfoFromCurrentContext(request, task, thenable);
if (thenable.status === 'pending') {
const rejectedThenable: RejectedThenable<mixed> = (thenable: any);
rejectedThenable.status = 'rejected';
@@ -1097,10 +1083,6 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
_payload: thenable,
_init: readThenable,
};
if (__DEV__) {
// If this came from React, transfer the debug info.
lazyType._debugInfo = (thenable: any)._debugInfo || [];
}
return lazyType;
}
@@ -1179,12 +1161,9 @@ function processServerComponentReturnValue(
}
}, voidHandler);
}
if (thenable.status === 'fulfilled') {
return thenable.value;
}
// TODO: Once we accept Promises as children on the client, we can just return
// the thenable here.
return createLazyWrapperAroundWakeable(result);
return createLazyWrapperAroundWakeable(request, task, result);
}
if (__DEV__) {
@@ -1341,12 +1320,7 @@ function renderFunctionComponent<Props>(
// Track when we started rendering this component.
if (enableProfilerTimer && enableComponentPerformanceTrack) {
task.timed = true;
emitTimingChunk(
request,
componentDebugID,
(task.time = performance.now()),
);
advanceTaskTime(request, task, performance.now());
}
emitDebugChunk(request, componentDebugID, componentDebugInfo);
@@ -1392,6 +1366,7 @@ function renderFunctionComponent<Props>(
}
}
} else {
componentDebugInfo = (null: any);
prepareToUseHooksForComponent(prevThenableState, null);
// The secondArg is always undefined in Server Components since refs error early.
const secondArg = undefined;
@@ -1414,6 +1389,34 @@ function renderFunctionComponent<Props>(
throw null;
}
if (
__DEV__ ||
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
enableAsyncDebugInfo)
) {
// Forward any debug information for any Promises that we use():ed during the render.
// We do this at the end so that we don't keep doing this for each retry.
const trackedThenables = getTrackedThenablesAfterRendering();
if (trackedThenables !== null) {
const stacks: Array<Error> =
__DEV__ && enableAsyncDebugInfo
? (trackedThenables: any)._stacks ||
((trackedThenables: any)._stacks = [])
: (null: any);
for (let i = 0; i < trackedThenables.length; i++) {
const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null;
forwardDebugInfoFromThenable(
request,
task,
trackedThenables[i],
__DEV__ ? componentDebugInfo : null,
stack,
);
}
}
}
// Apply special cases.
result = processServerComponentReturnValue(request, task, Component, result);
@@ -1890,8 +1893,8 @@ function visitAsyncNode(
request: Request,
task: Task,
node: AsyncSequence,
visited: Set<AsyncSequence | ReactDebugInfo>,
cutOff: number,
visited: Set<AsyncSequence>,
): null | PromiseNode | IONode {
if (visited.has(node)) {
// It's possible to visit them same node twice when it's part of both an "awaited" path
@@ -1900,11 +1903,11 @@ function visitAsyncNode(
}
visited.add(node);
// First visit anything that blocked this sequence to start in the first place.
if (node.previous !== null) {
if (node.previous !== null && node.end > request.timeOrigin) {
// We ignore the return value here because if it wasn't awaited in user space, then we don't log it.
// It also means that it can just have been part of a previous component's render.
// TODO: This means that some I/O can get lost that was still blocking the sequence.
visitAsyncNode(request, task, node.previous, cutOff, visited);
visitAsyncNode(request, task, node.previous, visited, cutOff);
}
switch (node.tag) {
case IO_NODE: {
@@ -1923,24 +1926,23 @@ function visitAsyncNode(
const awaited = node.awaited;
let match = null;
if (awaited !== null) {
const ioNode = visitAsyncNode(request, task, awaited, cutOff, visited);
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
if (ioNode !== null) {
// This Promise was blocked on I/O. That's a signal that this Promise is interesting to log.
// We don't log it yet though. We return it to be logged by the point where it's awaited.
// The ioNode might be another PromiseNode in the case where none of the AwaitNode had
// unfiltered stacks.
if (
if (ioNode.tag === PROMISE_NODE) {
// If the ioNode was a Promise, then that means we found one in user space since otherwise
// we would've returned an IO node. We assume this has the best stack.
match = ioNode;
} else if (
filterStackTrace(request, parseStackTrace(node.stack, 1)).length ===
0
) {
// Typically we assume that the outer most Promise that was awaited in user space has the
// most actionable stack trace for the start of the operation. However, if this Promise
// was created inside only third party code, then try to use the inner node instead.
// This could happen if you pass a first party Promise into a third party to be awaited there.
if (ioNode.end < 0) {
// If we haven't defined an end time, use the resolve of the outer Promise.
ioNode.end = node.end;
}
// If this Promise was created inside only third party code, then try to use
// the inner I/O node instead. This could happen if third party calls into first
// party to perform some I/O.
match = ioNode;
} else {
match = node;
@@ -1950,35 +1952,23 @@ function visitAsyncNode(
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
// the thing that generated this node and its virtual children.
const debugInfo = node.debugInfo;
if (debugInfo !== null) {
if (debugInfo !== null && !visited.has(debugInfo)) {
visited.add(debugInfo);
forwardDebugInfo(request, task, debugInfo);
}
return match;
}
case UNRESOLVED_AWAIT_NODE:
// We could be inside the .then() which is about to resolve this node.
// TODO: We could call emitAsyncSequence in a microtask to avoid this issue.
// Fallthrough to the resolved path.
case UNRESOLVED_AWAIT_NODE: {
return null;
}
case AWAIT_NODE: {
const awaited = node.awaited;
let match = null;
if (awaited !== null) {
const ioNode = visitAsyncNode(request, task, awaited, cutOff, visited);
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
if (ioNode !== null) {
const startTime: number = node.start;
let endTime: number;
if (node.tag === UNRESOLVED_AWAIT_NODE) {
// If we haven't defined an end time, use the resolve of the inner Promise.
// This can happen because the ping gets invoked before the await gets resolved.
if (ioNode.end < node.start) {
// If we're awaiting a resolved Promise it could have finished before we started.
endTime = node.start;
} else {
endTime = ioNode.end;
}
} else {
endTime = node.end;
}
const endTime: number = node.end;
if (endTime <= request.timeOrigin) {
// This was already resolved when we started this render. It must have been either something
// that's part of a start up sequence or externally cached data. We exclude that information.
@@ -2002,14 +1992,12 @@ function visitAsyncNode(
match = ioNode;
} else {
// Outline the IO node.
if (ioNode.end < 0) {
ioNode.end = endTime;
}
serializeIONode(request, ioNode);
// We log the environment at the time when the last promise pigned ping which may
// be later than what the environment was when we actually started awaiting.
const env = (0, request.environmentName)();
emitTimingChunk(request, task.id, startTime);
advanceTaskTime(request, task, startTime);
// Then emit a reference to us awaiting it in the current task.
request.pendingChunks++;
emitDebugChunk(request, task.id, {
@@ -2018,24 +2006,16 @@ function visitAsyncNode(
owner: node.owner,
stack: stack,
});
emitTimingChunk(request, task.id, endTime);
markOperationEndTime(request, task, endTime);
}
}
}
}
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
// the thing that generated this node and its virtual children.
let debugInfo: null | ReactDebugInfo;
if (node.tag === UNRESOLVED_AWAIT_NODE) {
const promise = node.debugInfo.deref();
debugInfo =
promise === undefined || promise._debugInfo === undefined
? null
: promise._debugInfo;
} else {
debugInfo = node.debugInfo;
}
if (debugInfo !== null) {
const debugInfo = node.debugInfo;
if (debugInfo !== null && !visited.has(debugInfo)) {
visited.add(debugInfo);
forwardDebugInfo(request, task, debugInfo);
}
return match;
@@ -2051,37 +2031,40 @@ function emitAsyncSequence(
request: Request,
task: Task,
node: AsyncSequence,
cutOff: number,
alreadyForwardedDebugInfo: ?ReactDebugInfo,
owner: null | ReactComponentInfo,
stack: null | Error,
): void {
const visited: Set<AsyncSequence> = new Set();
const awaitedNode = visitAsyncNode(request, task, node, cutOff, visited);
const visited: Set<AsyncSequence | ReactDebugInfo> = new Set();
if (__DEV__ && alreadyForwardedDebugInfo) {
visited.add(alreadyForwardedDebugInfo);
}
const awaitedNode = visitAsyncNode(request, task, node, visited, task.time);
if (awaitedNode !== null) {
// Nothing in user space (unfiltered stack) awaited this.
if (awaitedNode.end < 0) {
// If this was I/O directly without a Promise, then it means that some custom Thenable
// called our ping directly and not from a native .then(). We use the current ping time
// as the end time and treat it as an await with no stack.
// TODO: If this I/O is recurring then we really should have different entries for
// each occurrence. Right now we'll only track the first time it is invoked.
awaitedNode.end = performance.now();
}
serializeIONode(request, awaitedNode);
request.pendingChunks++;
// We log the environment at the time when we ping which may be later than what the
// environment was when we actually started awaiting.
const env = (0, request.environmentName)();
// If we don't have any thing awaited, the time we started awaiting was internal
// when we yielded after rendering. The cutOff time is basically that.
const awaitStartTime = cutOff;
// If the end time finished before we started, it could've been a cached thing so
// we clamp it to the cutOff time. Effectively leading to a zero-time await.
const awaitEndTime = awaitedNode.end < cutOff ? cutOff : awaitedNode.end;
emitTimingChunk(request, task.id, awaitStartTime);
emitDebugChunk(request, task.id, {
// when we yielded after rendering. The current task time is basically that.
const debugInfo: ReactAsyncInfo = {
awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference.
env: env,
});
emitTimingChunk(request, task.id, awaitEndTime);
};
if (__DEV__) {
if (owner != null) {
// $FlowFixMe[cannot-write]
debugInfo.owner = owner;
}
if (stack != null) {
// $FlowFixMe[cannot-write]
debugInfo.stack = filterStackTrace(request, parseStackTrace(stack, 1));
}
}
emitDebugChunk(request, task.id, debugInfo);
markOperationEndTime(request, task, awaitedNode.end);
}
}
@@ -2089,12 +2072,6 @@ function pingTask(request: Request, task: Task): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// If this was async we need to emit the time when it completes.
task.timed = true;
if (enableAsyncDebugInfo) {
const sequence = getCurrentAsyncSequence();
if (sequence !== null) {
emitAsyncSequence(request, task, sequence, task.time);
}
}
}
const pingedTasks = request.pingedTasks;
pingedTasks.push(task);
@@ -4295,19 +4272,13 @@ function forwardDebugInfo(
debugInfo: ReactDebugInfo,
) {
const id = task.id;
const minimumTime =
enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0;
for (let i = 0; i < debugInfo.length; i++) {
const info = debugInfo[i];
if (typeof info.time === 'number') {
// When forwarding time we need to ensure to convert it to the time space of the payload.
// We clamp the time to the starting render of the current component. It's as if it took
// no time to render and await if we reuse cached content.
emitTimingChunk(
request,
id,
info.time < minimumTime ? minimumTime : info.time,
);
markOperationEndTime(request, task, info.time);
} else {
if (typeof info.name === 'string') {
// We outline this model eagerly so that we can refer to by reference as an owner.
@@ -4367,6 +4338,58 @@ function forwardDebugInfo(
}
}
function forwardDebugInfoFromThenable(
request: Request,
task: Task,
thenable: Thenable<any>,
owner: null | ReactComponentInfo, // DEV-only
stack: null | Error, // DEV-only
): void {
let debugInfo: ?ReactDebugInfo;
if (__DEV__) {
// If this came from Flight, forward any debug info into this new row.
debugInfo = thenable._debugInfo;
if (debugInfo) {
forwardDebugInfo(request, task, debugInfo);
}
}
if (
enableProfilerTimer &&
enableComponentPerformanceTrack &&
enableAsyncDebugInfo
) {
const sequence = getAsyncSequenceFromPromise(thenable);
if (sequence !== null) {
emitAsyncSequence(request, task, sequence, debugInfo, owner, stack);
}
}
}
function forwardDebugInfoFromCurrentContext(
request: Request,
task: Task,
thenable: Thenable<any>,
): void {
let debugInfo: ?ReactDebugInfo;
if (__DEV__) {
// If this came from Flight, forward any debug info into this new row.
debugInfo = thenable._debugInfo;
if (debugInfo) {
forwardDebugInfo(request, task, debugInfo);
}
}
if (
enableProfilerTimer &&
enableComponentPerformanceTrack &&
enableAsyncDebugInfo
) {
const sequence = getCurrentAsyncSequence();
if (sequence !== null) {
emitAsyncSequence(request, task, sequence, debugInfo, null, null);
}
}
}
function emitTimingChunk(
request: Request,
id: number,
@@ -4384,6 +4407,40 @@ function emitTimingChunk(
request.completedRegularChunks.push(processedChunk);
}
function advanceTaskTime(
request: Request,
task: Task,
timestamp: number,
): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
// Emits a timing chunk, if the new timestamp is higher than the previous timestamp of this task.
if (timestamp > task.time) {
emitTimingChunk(request, task.id, timestamp);
task.time = timestamp;
} else if (!task.timed) {
// If it wasn't timed before, e.g. an outlined object, we need to emit the first timestamp and
// it is now timed.
emitTimingChunk(request, task.id, task.time);
}
task.timed = true;
}
function markOperationEndTime(request: Request, task: Task, timestamp: number) {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
// This is like advanceTaskTime() but always emits a timing chunk even if it doesn't advance.
// This ensures that the end time of the previous entry isn't implied to be the start of the next one.
if (timestamp > task.time) {
emitTimingChunk(request, task.id, timestamp);
task.time = timestamp;
} else {
emitTimingChunk(request, task.id, task.time);
}
}
function emitChunk(
request: Request,
task: Task,
@@ -4475,7 +4532,7 @@ function emitChunk(
function erroredTask(request: Request, task: Task, error: mixed): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (task.timed) {
emitTimingChunk(request, task.id, (task.time = performance.now()));
markOperationEndTime(request, task, performance.now());
}
}
task.status = ERRORED;
@@ -4558,7 +4615,7 @@ function retryTask(request: Request, task: Task): void {
// We've finished rendering. Log the end time.
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (task.timed) {
emitTimingChunk(request, task.id, (task.time = performance.now()));
markOperationEndTime(request, task, performance.now());
}
}
@@ -4685,7 +4742,7 @@ function abortTask(task: Task, request: Request, errorId: number): void {
// Track when we aborted this task as its end time.
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (task.timed) {
emitTimingChunk(request, task.id, (task.time = performance.now()));
markOperationEndTime(request, task, performance.now());
}
}
// Instead of emitting an error per task.id, we emit a model that only
+108 -24
View File
@@ -24,12 +24,36 @@ import {
UNRESOLVED_AWAIT_NODE,
} from './ReactFlightAsyncSequence';
import {resolveOwner} from './flight/ReactFlightCurrentOwner';
import {createHook, executionAsyncId} from 'async_hooks';
import {createHook, executionAsyncId, AsyncResource} from 'async_hooks';
import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
// $FlowFixMe[method-unbinding]
const getAsyncId = AsyncResource.prototype.asyncId;
const pendingOperations: Map<number, AsyncSequence> =
__DEV__ && enableAsyncDebugInfo ? new Map() : (null: any);
// Keep the last resolved await as a workaround for async functions missing data.
let lastRanAwait: null | AwaitNode = null;
function resolvePromiseOrAwaitNode(
unresolvedNode: UnresolvedAwaitNode | UnresolvedPromiseNode,
endTime: number,
): AwaitNode | PromiseNode {
const resolvedNode: AwaitNode | PromiseNode = (unresolvedNode: any);
resolvedNode.tag = ((unresolvedNode.tag === UNRESOLVED_PROMISE_NODE
? PROMISE_NODE
: AWAIT_NODE): any);
// The Promise can be garbage collected after this so we should extract debugInfo first.
const promise = unresolvedNode.debugInfo.deref();
resolvedNode.debugInfo =
promise === undefined || promise._debugInfo === undefined
? null
: promise._debugInfo;
resolvedNode.end = endTime;
return resolvedNode;
}
// Initialize the tracing of async operations.
// We do this globally since the async work can potentially eagerly
// start before the first request and once requests start they can interleave.
@@ -129,42 +153,76 @@ export function initAsyncDebugInfo(): void {
}
pendingOperations.set(asyncId, node);
},
before(asyncId: number): void {
const node = pendingOperations.get(asyncId);
if (node !== undefined) {
switch (node.tag) {
case IO_NODE: {
lastRanAwait = null;
// Log the end time when we resolved the I/O. This can happen
// more than once if it's a recurring resource like a connection.
const ioNode: IONode = (node: any);
ioNode.end = performance.now();
break;
}
case UNRESOLVED_AWAIT_NODE: {
// If we begin before we resolve, that means that this is actually already resolved but
// the promiseResolve hook is called at the end of the execution. So we track the time
// in the before call instead.
// $FlowFixMe
lastRanAwait = resolvePromiseOrAwaitNode(node, performance.now());
break;
}
case AWAIT_NODE: {
lastRanAwait = node;
break;
}
case UNRESOLVED_PROMISE_NODE: {
// We typically don't expected Promises to have an execution scope since only the awaits
// have a then() callback. However, this can happen for native async functions. The last
// piece of code that executes the return after the last await has the execution context
// of the Promise.
const resolvedNode = resolvePromiseOrAwaitNode(
node,
performance.now(),
);
// We are missing information about what this was unblocked by but we can guess that it
// was whatever await we ran last since this will continue in a microtask after that.
// This is not perfect because there could potentially be other microtasks getting in
// between.
resolvedNode.previous = lastRanAwait;
lastRanAwait = null;
break;
}
default: {
lastRanAwait = null;
}
}
}
},
promiseResolve(asyncId: number): void {
const node = pendingOperations.get(asyncId);
if (node !== undefined) {
let resolvedNode: AwaitNode | PromiseNode;
switch (node.tag) {
case UNRESOLVED_AWAIT_NODE: {
const awaitNode: AwaitNode = (node: any);
awaitNode.tag = AWAIT_NODE;
resolvedNode = awaitNode;
break;
}
case UNRESOLVED_AWAIT_NODE:
case UNRESOLVED_PROMISE_NODE: {
const promiseNode: PromiseNode = (node: any);
promiseNode.tag = PROMISE_NODE;
resolvedNode = promiseNode;
resolvedNode = resolvePromiseOrAwaitNode(node, performance.now());
break;
}
case IO_NODE:
case AWAIT_NODE:
case PROMISE_NODE: {
// We already resolved this in the before hook.
resolvedNode = node;
break;
}
default:
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'A Promise should never be an IO_NODE. This is a bug in React.',
);
default:
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'A Promise should never be resolved twice. This is a bug in React or Node.js.',
);
}
// Log the end time when we resolved the promise.
resolvedNode.end = performance.now();
// The Promise can be garbage collected after this so we should extract debugInfo first.
const promise = node.debugInfo.deref();
resolvedNode.debugInfo =
promise === undefined || promise._debugInfo === undefined
? null
: promise._debugInfo;
const currentAsyncId = executionAsyncId();
if (asyncId !== currentAsyncId) {
// If the promise was not resolved by itself, then that means that
@@ -205,3 +263,29 @@ export function getCurrentAsyncSequence(): null | AsyncSequence {
}
return currentNode;
}
export function getAsyncSequenceFromPromise(
promise: any,
): null | AsyncSequence {
if (!__DEV__ || !enableAsyncDebugInfo) {
return null;
}
// A Promise is conceptually an AsyncResource but doesn't have its own methods.
// We use this hack to extract the internal asyncId off the Promise.
let asyncId: void | number;
try {
asyncId = getAsyncId.call(promise);
} catch (x) {
// Ignore errors extracting the ID. We treat it as missing.
// This could happen if our hack stops working or in the case where this is
// a Proxy that throws such as our own ClientReference proxies.
}
if (asyncId === undefined) {
return null;
}
const node = pendingOperations.get(asyncId);
if (node === undefined) {
return null;
}
return node;
}
@@ -15,3 +15,8 @@ export function markAsyncSequenceRootTask(): void {}
export function getCurrentAsyncSequence(): null | AsyncSequence {
return null;
}
export function getAsyncSequenceFromPromise(
promise: any,
): null | AsyncSequence {
return null;
}
@@ -52,6 +52,9 @@ const proxyHandlers = {
// reference.
case 'defaultProps':
return undefined;
// React looks for debugInfo on thenables.
case '_debugInfo':
return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
+8 -1
View File
@@ -20,9 +20,11 @@ import type {
RejectedThenable,
} from 'shared/ReactTypes';
import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
import noop from 'shared/noop';
export opaque type ThenableState = Array<Thenable<any>>;
export type ThenableState = Array<Thenable<any>>;
// An error that is thrown (e.g. by `use`) to trigger Suspense. If we
// detect this is caught by userspace, we'll log a warning in development.
@@ -50,6 +52,11 @@ export function trackUsedThenable<T>(
const previous = thenableState[index];
if (previous === undefined) {
thenableState.push(thenable);
if (__DEV__ && enableAsyncDebugInfo) {
const stacks: Array<Error> =
(thenableState: any)._stacks || ((thenableState: any)._stacks = []);
stacks.push(new Error());
}
} else {
if (previous !== thenable) {
// Reuse the previous thenable, and drop the new one. We can assume
File diff suppressed because it is too large Load Diff
+6 -73
View File
@@ -7,14 +7,9 @@
* @flow
*/
import {
REACT_PROVIDER_TYPE,
REACT_CONSUMER_TYPE,
REACT_CONTEXT_TYPE,
} from 'shared/ReactSymbols';
import {REACT_CONSUMER_TYPE, REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import type {ReactContext} from 'shared/ReactTypes';
import {enableRenderableContext} from 'shared/ReactFeatureFlags';
export function createContext<T>(defaultValue: T): ReactContext<T> {
// TODO: Second argument used to be an optional `calculateChangedBits`
@@ -37,73 +32,11 @@ export function createContext<T>(defaultValue: T): ReactContext<T> {
Consumer: (null: any),
};
if (enableRenderableContext) {
context.Provider = context;
context.Consumer = {
$$typeof: REACT_CONSUMER_TYPE,
_context: context,
};
} else {
(context: any).Provider = {
$$typeof: REACT_PROVIDER_TYPE,
_context: context,
};
if (__DEV__) {
const Consumer: any = {
$$typeof: REACT_CONTEXT_TYPE,
_context: context,
};
Object.defineProperties(Consumer, {
Provider: {
get() {
return context.Provider;
},
set(_Provider: any) {
context.Provider = _Provider;
},
},
_currentValue: {
get() {
return context._currentValue;
},
set(_currentValue: T) {
context._currentValue = _currentValue;
},
},
_currentValue2: {
get() {
return context._currentValue2;
},
set(_currentValue2: T) {
context._currentValue2 = _currentValue2;
},
},
_threadCount: {
get() {
return context._threadCount;
},
set(_threadCount: number) {
context._threadCount = _threadCount;
},
},
Consumer: {
get() {
return context.Consumer;
},
},
displayName: {
get() {
return context.displayName;
},
set(displayName: void | string) {},
},
});
(context: any).Consumer = Consumer;
} else {
(context: any).Consumer = context;
}
}
context.Provider = context;
context.Consumer = {
$$typeof: REACT_CONSUMER_TYPE,
_context: context,
};
if (__DEV__) {
context._currentRenderer = null;
context._currentRenderer2 = null;
@@ -490,7 +490,6 @@ describe('ReactContextValidator', () => {
]);
});
// @gate enableRenderableContext || !__DEV__
it('should warn if an invalid contextType is defined', async () => {
const Context = React.createContext();
class ComponentA extends React.Component {
-3
View File
@@ -204,9 +204,6 @@ export const enableReactTestRendererWarning = true;
// before removing them in stable in the next Major
export const disableLegacyMode = true;
// Make <Context> equivalent to <Context.Provider> instead of <Context.Consumer>
export const enableRenderableContext = true;
// -----------------------------------------------------------------------------
// Chopping Block
//
-1
View File
@@ -22,7 +22,6 @@ export const REACT_PORTAL_TYPE: symbol = Symbol.for('react.portal');
export const REACT_FRAGMENT_TYPE: symbol = Symbol.for('react.fragment');
export const REACT_STRICT_MODE_TYPE: symbol = Symbol.for('react.strict_mode');
export const REACT_PROFILER_TYPE: symbol = Symbol.for('react.profiler');
export const REACT_PROVIDER_TYPE: symbol = Symbol.for('react.provider'); // TODO: Delete with enableRenderableContext
export const REACT_CONSUMER_TYPE: symbol = Symbol.for('react.consumer');
export const REACT_CONTEXT_TYPE: symbol = Symbol.for('react.context');
export const REACT_FORWARD_REF_TYPE: symbol = Symbol.for('react.forward_ref');
@@ -58,7 +58,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableProfilerTimer = __PROFILE__;
export const enableReactTestRendererWarning = false;
export const enableRenderableContext = true;
export const enableRetryLaneExpiration = false;
export const enableSchedulingProfiler = __PROFILE__;
export const enableComponentPerformanceTrack = false;
@@ -43,7 +43,6 @@ export const enableObjectFiber = false;
export const enablePersistedModeClonedFlag = false;
export const enablePostpone = false;
export const enableReactTestRendererWarning = false;
export const enableRenderableContext = true;
export const enableRetryLaneExpiration = false;
export const enableSchedulingProfiler = __PROFILE__;
export const enableComponentPerformanceTrack = false;
@@ -89,7 +89,6 @@ export const enableFragmentRefs = false;
export const disableLegacyMode = true;
export const disableLegacyContext = true;
export const disableLegacyContextForFunctionComponents = true;
export const enableRenderableContext = true;
export const enableReactTestRendererWarning = true;
export const disableDefaultPropsExceptForClasses = true;
@@ -41,7 +41,6 @@ export const enableProfilerCommitHooks = __PROFILE__;
export const enableProfilerNestedUpdatePhase = __PROFILE__;
export const enableProfilerTimer = __PROFILE__;
export const enableReactTestRendererWarning = false;
export const enableRenderableContext = true;
export const enableRetryLaneExpiration = false;
export const enableSchedulingProfiler = __PROFILE__;
export const enableComponentPerformanceTrack = false;
@@ -38,7 +38,6 @@ export const enableUseEffectEventHook = false;
export const favorSafetyOverHydrationPerf = true;
export const enableLegacyFBSupport = false;
export const enableMoveBefore = false;
export const enableRenderableContext = false;
export const enableHiddenSubtreeInsertionEffectCleanup = true;
export const enableRetryLaneExpiration = false;
@@ -21,7 +21,6 @@ export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__;
export const enableHiddenSubtreeInsertionEffectCleanup = __VARIANT__;
export const enableNoCloningMemoCache = __VARIANT__;
export const enableObjectFiber = __VARIANT__;
export const enableRenderableContext = __VARIANT__;
export const enableRetryLaneExpiration = __VARIANT__;
export const enableTransitionTracing = __VARIANT__;
export const favorSafetyOverHydrationPerf = __VARIANT__;
@@ -24,7 +24,6 @@ export const {
enableInfiniteRenderLoopDetection,
enableNoCloningMemoCache,
enableObjectFiber,
enableRenderableContext,
enableRetryLaneExpiration,
enableTransitionTracing,
enableTrustedTypesIntegration,
+3 -20
View File
@@ -18,7 +18,6 @@ import {
REACT_PORTAL_TYPE,
REACT_MEMO_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
@@ -30,7 +29,6 @@ import {
import {
enableTransitionTracing,
enableRenderableContext,
enableViewTransition,
} from './ReactFeatureFlags';
@@ -106,27 +104,12 @@ export default function getComponentNameFromType(type: mixed): string | null {
switch (type.$$typeof) {
case REACT_PORTAL_TYPE:
return 'Portal';
case REACT_PROVIDER_TYPE:
if (enableRenderableContext) {
return null;
} else {
const provider = (type: any);
return getContextName(provider._context) + '.Provider';
}
case REACT_CONTEXT_TYPE:
const context: ReactContext<any> = (type: any);
if (enableRenderableContext) {
return getContextName(context) + '.Provider';
} else {
return getContextName(context) + '.Consumer';
}
return getContextName(context);
case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
} else {
return null;
}
const consumer: ReactConsumerType<any> = (type: any);
return getContextName(consumer._context) + '.Consumer';
case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, 'ForwardRef');
case REACT_MEMO_TYPE:
+3 -1
View File
@@ -356,7 +356,9 @@ declare module 'async_hooks' {
run<R>(store: T, callback: (...args: any[]) => R, ...args: any[]): R;
enterWith(store: T): void;
}
declare interface AsyncResource {}
declare class AsyncResource {
asyncId(): number;
}
declare function executionAsyncId(): number;
declare function executionAsyncResource(): AsyncResource;
declare function triggerAsyncId(): number;