diff --git a/.github/workflows/runtime_prereleases.yml b/.github/workflows/runtime_prereleases.yml index ee8dd72ce9..a97add8cbc 100644 --- a/.github/workflows/runtime_prereleases.yml +++ b/.github/workflows/runtime_prereleases.yml @@ -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 diff --git a/.github/workflows/runtime_prereleases_manual.yml b/.github/workflows/runtime_prereleases_manual.yml index 71e25ba073..407d931e90 100644 --- a/.github/workflows/runtime_prereleases_manual.yml +++ b/.github/workflows/runtime_prereleases_manual.yml @@ -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 }} diff --git a/.github/workflows/runtime_prereleases_nightly.yml b/.github/workflows/runtime_prereleases_nightly.yml index a38e241d53..f13a92e46f 100644 --- a/.github/workflows/runtime_prereleases_nightly.yml +++ b/.github/workflows/runtime_prereleases_nightly.yml @@ -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 }} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-ref-prefix-postfix-operator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-ref-prefix-postfix-operator.expect.md new file mode 100644 index 0000000000..ccfc451750 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-ref-prefix-postfix-operator.expect.md @@ -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: [], +}; + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-ref-prefix-postfix-operator.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-ref-prefix-postfix-operator.js new file mode 100644 index 0000000000..a7c1fad8bf --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-ref-prefix-postfix-operator.js @@ -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: [], +}; diff --git a/compiler/packages/snap/src/SproutTodoFilter.ts b/compiler/packages/snap/src/SproutTodoFilter.ts index 3db3210a99..02cb3775cb 100644 --- a/compiler/packages/snap/src/SproutTodoFilter.ts +++ b/compiler/packages/snap/src/SproutTodoFilter.ts @@ -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', diff --git a/fixtures/flight/src/App.js b/fixtures/flight/src/App.js index 833c655cbf..2f29de7aba 100644 --- a/fixtures/flight/src/App.js +++ b/fixtures/flight/src/App.js @@ -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
{children}
; } diff --git a/fixtures/view-transition/src/components/App.js b/fixtures/view-transition/src/components/App.js index 275e594d87..dd8dcb73a2 100644 --- a/fixtures/view-transition/src/components/App.js +++ b/fixtures/view-transition/src/components/App.js @@ -4,6 +4,7 @@ import React, { useEffect, useState, unstable_addTransitionType as addTransitionType, + use, } from 'react'; import Chrome from './Chrome'; diff --git a/fixtures/view-transition/src/components/NestedReveal.js b/fixtures/view-transition/src/components/NestedReveal.js new file mode 100644 index 0000000000..497f4430f6 --- /dev/null +++ b/fixtures/view-transition/src/components/NestedReveal.js @@ -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 ( +
+ Shell + +
Level 1
+ + + +
Level 2
+ +
+
+
+ ); +} diff --git a/fixtures/view-transition/src/components/Page.js b/fixtures/view-transition/src/components/Page.js index 39d0803af7..c0d6f7a0a2 100644 --- a/fixtures/view-transition/src/components/Page.js +++ b/fixtures/view-transition/src/components/Page.js @@ -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}) { + ); } diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index a69ede9efd..1171f933d2 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -2902,6 +2902,46 @@ function resolveTypedArray( resolveBuffer(response, id, view); } +function logComponentInfo( + response: Response, + root: SomeChunk, + 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, @@ -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; diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index 6a0a37b787..40de7ca51e 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -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), diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index eb354aba58..49968b3359 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -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
{text.toUpperCase()}
; + } + + const thirdPartyTransport = ReactNoopFlightServer.render( + , + { + environmentName: 'third-party', + }, + ); + + const transport = ReactNoopFlightServer.render( + , + ); + + await act(async () => { + const promise = ReactNoopFlightClient.read(transport); + expect(getDebugInfo(promise)).toEqual( + __DEV__ + ? [ + {time: 16}, + { + name: 'ServerComponent', + env: 'Server', + key: null, + stack: ' in Object. (at **)', + props: { + transport: expect.arrayContaining([]), + }, + }, + {time: 16}, + { + name: 'ThirdPartyComponent', + env: 'third-party', + key: null, + stack: ' in Object. (at **)', + props: {}, + }, + {time: 16}, + {time: 17}, + ] + : undefined, + ); + const result = await promise; + ReactNoop.render(result); + }); + + expect(ReactNoop).toMatchRenderedOutput(
HI
); + }); + 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. diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js index 1ba61c52dd..67cf50a074 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/Tree.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/Tree.js @@ -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(DEFAULT_INDENTATION_SIZE); + const indentationSizeRef = useRef(MAX_INDENTATION_SIZE); const prevListWidthRef = useRef(0); const prevOwnerIDRef = useRef(ownerID); const divRef = useRef(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. diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index 0536a821c7..5d92a86e2e 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -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> = 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'; diff --git a/packages/react-devtools-shell/index.html b/packages/react-devtools-shell/index.html index 4cde55278a..ce381a7345 100644 --- a/packages/react-devtools-shell/index.html +++ b/packages/react-devtools-shell/index.html @@ -1,74 +1,213 @@ - - - React DevTools - - - - -
- -
 
- - multi DevTools - | - e2e tests - | - e2e regression tests - | - perf regression tests - -
+ + + React DevTools + + + + + +
+ +
 
+ + multi DevTools + | + e2e tests + | + e2e regression tests + | + perf regression tests + + + +
+ +
+ +
+
+
- - - - - - \ No newline at end of file + + + + + + + + diff --git a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js index 6cfb4b61ed..186d9c4c78 100644 --- a/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js +++ b/packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js @@ -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;aa&&2E3a&&2E3q&&2E3 { }); 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'); diff --git a/packages/react-dom/src/__tests__/ReactDOMUseId-test.js b/packages/react-dom/src/__tests__/ReactDOMUseId-test.js index 895c2f9b0d..0654e8c6e6 100644 --- a/packages/react-dom/src/__tests__/ReactDOMUseId-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMUseId-test.js @@ -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 {children}; + } + + function Bar({children}) { + const id = useId(); + return {children}; + } + + function Foo() { + return ( + + A + B + + ); + } + + await serverAct(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + + await clientAct(async () => { + ReactDOMClient.hydrateRoot(container, ); + }); + + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + }); + + // @gate enableSuspenseList + it('Supports SuspenseList (reveal order "together")', async () => { + function Baz({id, children}) { + return {children}; + } + + function Bar({children}) { + const id = useId(); + return {children}; + } + + function Foo() { + return ( + + A + B + + ); + } + + await serverAct(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + + await clientAct(async () => { + ReactDOMClient.hydrateRoot(container, ); + }); + + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + }); + + // @gate enableSuspenseList + it('Supports SuspenseList (reveal order "forwards")', async () => { + function Baz({id, children}) { + return {children}; + } + + function Bar({children}) { + const id = useId(); + return {children}; + } + + function Foo() { + return ( + + A + B + + ); + } + + await serverAct(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + + await clientAct(async () => { + ReactDOMClient.hydrateRoot(container, ); + }); + + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + }); + + // @gate enableSuspenseList + it('Supports SuspenseList (reveal order "backwards") with a single child in a list of many', async () => { + function Baz({id, children}) { + return {children}; + } + + function Bar({children}) { + const id = useId(); + return {children}; + } + + function Foo() { + return ( + + {null} + A + {null} + + ); + } + + await serverAct(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + expect(container).toMatchInlineSnapshot(` +
+ + A + + +
+ `); + + await clientAct(async () => { + ReactDOMClient.hydrateRoot(container, ); + }); + + expect(container).toMatchInlineSnapshot(` +
+ + A + + +
+ `); + }); + + // @gate enableSuspenseList + it('Supports SuspenseList (reveal order "backwards")', async () => { + function Baz({id, children}) { + return {children}; + } + + function Bar({children}) { + const id = useId(); + return {children}; + } + + function Foo() { + return ( + + A + B + + ); + } + + await serverAct(async () => { + const {pipe} = ReactDOMFizzServer.renderToPipeableStream(); + pipe(writable); + }); + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + + 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, ); + }); + }).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(` +
+ + A + + + B + +
+ `); + } else { + await clientAct(async () => { + ReactDOMClient.hydrateRoot(container, ); + }); + + // 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 + + + + + + + ++ B +- A +`, + ]); + + expect(container).toMatchInlineSnapshot(` +
+ + A + + + B + +
+ `); + } + }); + it('basic incremental hydration', async () => { function App() { return ( diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js index 2bf917d3c3..86bdb1633f 100644 --- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js +++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js @@ -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 { diff --git a/packages/react-is/src/ReactIs.js b/packages/react-is/src/ReactIs.js index a433e91bf1..ed70f0e68e 100644 --- a/packages/react-is/src/ReactIs.js +++ b/packages/react-is/src/ReactIs.js @@ -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 ( diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 39dde1c593..65feabd8c0 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -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; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index a931616315..10b10a74b9 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -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; - if (enableRenderableContext) { - context = workInProgress.type; - } else { - context = workInProgress.type._context; - } + const context: ReactContext = workInProgress.type; const newProps = workInProgress.pendingProps; const newValue = newProps.value; @@ -3615,18 +3617,8 @@ function updateContextConsumer( workInProgress: Fiber, renderLanes: Lanes, ) { - let context: ReactContext; - if (enableRenderableContext) { - const consumerType: ReactConsumerType = workInProgress.type; - context = consumerType._context; - } else { - context = workInProgress.type; - if (__DEV__) { - if ((context: any)._context !== undefined) { - context = (context: any)._context; - } - } - } + const consumerType: ReactConsumerType = workInProgress.type; + const context: ReactContext = 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; - if (enableRenderableContext) { - context = workInProgress.type; - } else { - context = workInProgress.type._context; - } + const context: ReactContext = workInProgress.type; pushProvider(workInProgress, context, newValue); break; } diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index d62e3c3fea..1e6ddd7595 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -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; - if (enableRenderableContext) { - context = workInProgress.type; - } else { - context = workInProgress.type._context; - } + const context: ReactContext = 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; } diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index b10dc5ce54..02792d863a 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -29,7 +29,6 @@ import { } from './ReactFiberFlags'; import is from 'shared/objectIs'; -import {enableRenderableContext} from 'shared/ReactFeatureFlags'; import {getHostTransitionProvider} from './ReactFiberHostContext'; const valueCursor: StackCursor = createCursor(null); @@ -389,13 +388,7 @@ function propagateParentContextChanges( const oldProps = currentParent.memoizedProps; if (oldProps !== null) { - let context: ReactContext; - if (enableRenderableContext) { - context = parent.type; - } else { - context = parent.type._context; - } - + const context: ReactContext = parent.type; const newProps = parent.pendingProps; const newValue = newProps.value; diff --git a/packages/react-reconciler/src/ReactFiberScope.js b/packages/react-reconciler/src/ReactFiberScope.js index 0cb1c62ba8..8f9f1cdea5 100644 --- a/packages/react-reconciler/src/ReactFiberScope.js +++ b/packages/react-reconciler/src/ReactFiberScope.js @@ -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( context: ReactContext, childContextValues: Array, ): 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 { diff --git a/packages/react-reconciler/src/ReactFiberSuspenseComponent.js b/packages/react-reconciler/src/ReactFiberSuspenseComponent.js index 2542d660c4..64ab4f29fc 100644 --- a/packages/react-reconciler/src/ReactFiberSuspenseComponent.js +++ b/packages/react-reconciler/src/ReactFiberSuspenseComponent.js @@ -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; diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index a1d3797cd1..6de76ee203 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -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; - if (enableRenderableContext) { - context = workInProgress.type; - } else { - context = workInProgress.type._context; - } + const context: ReactContext = workInProgress.type; popProvider(context, workInProgress); return null; case OffscreenComponent: @@ -286,12 +280,7 @@ function unwindInterruptedWork( popSuspenseListContext(interruptedWork); break; case ContextProvider: - let context: ReactContext; - if (enableRenderableContext) { - context = interruptedWork.type; - } else { - context = interruptedWork.type._context; - } + const context: ReactContext = interruptedWork.type; popProvider(context, interruptedWork); break; case OffscreenComponent: diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js index a6306bfcfe..b6ae8e2ba1 100644 --- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js @@ -941,15 +941,11 @@ describe('ReactLazy', () => { , ); 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)); diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js index 58cb2f9e64..2a61b1192d 100644 --- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js +++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js @@ -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); diff --git a/packages/react-reconciler/src/getComponentNameFromFiber.js b/packages/react-reconciler/src/getComponentNameFromFiber.js index 670475cdec..97124bbf5b 100644 --- a/packages/react-reconciler/src/getComponentNameFromFiber.js +++ b/packages/react-reconciler/src/getComponentNameFromFiber.js @@ -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 = (type: any); - return getContextName(consumer._context) + '.Consumer'; - } else { - const context: ReactContext = (type: any); - return getContextName(context) + '.Consumer'; - } + const consumer: ReactConsumerType = (type: any); + return getContextName(consumer._context) + '.Consumer'; case ContextProvider: - if (enableRenderableContext) { - const context: ReactContext = (type: any); - return getContextName(context) + '.Provider'; - } else { - const provider = (type: any); - return getContextName(provider._context) + '.Provider'; - } + const context: ReactContext = (type: any); + return getContextName(context); case DehydratedFragment: return 'DehydratedFragment'; case ForwardRef: diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js index 9d00b39efa..baa297cf33 100644 --- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js +++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js @@ -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; diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js index 60fe34b1c0..c06e52a578 100644 --- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js +++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js @@ -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; diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 2995b498f4..2bcc14cfa4 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -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 = (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 = (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 = (type: ReactConsumerType) - ._context; - renderContextConsumer(request, task, keyPath, context, props); - return; - } - // Fall through + const context: ReactContext = (type: ReactConsumerType) + ._context; + renderContextConsumer(request, task, keyPath, context, props); + return; } case REACT_LAZY_TYPE: { renderLazyComponent(request, task, keyPath, type, props, ref); diff --git a/packages/react-server/src/ReactFlightAsyncSequence.js b/packages/react-server/src/ReactFlightAsyncSequence.js index 215af7f795..5785ba072c 100644 --- a/packages/react-server/src/ReactFlightAsyncSequence.js +++ b/packages/react-server/src/ReactFlightAsyncSequence.js @@ -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 = { diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js index c5ffc5dd70..ed369be0e9 100644 --- a/packages/react-server/src/ReactFlightHooks.js +++ b/packages/react-server/src/ReactFlightHooks.js @@ -58,6 +58,12 @@ export function getThenableStateAfterSuspending(): ThenableState { return state; } +export function getTrackedThenablesAfterRendering(): null | Array< + Thenable, +> { + return thenableState; +} + export const HooksDispatcher: Dispatcher = { readContext: (unsupportedContext: any), diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 5a9d0082ad..e51197c5b2 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -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(thenable: Thenable): 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 = (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 = (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 = (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( // 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( } } } 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( 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 = + __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, cutOff: number, - visited: Set, ): 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 = new Set(); - const awaitedNode = visitAsyncNode(request, task, node, cutOff, visited); + const visited: Set = 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, + 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, +): 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 diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js index 3bf84962bb..46a29fb3cb 100644 --- a/packages/react-server/src/ReactFlightServerConfigDebugNode.js +++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js @@ -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 = __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; +} diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js index 7418aaef18..e435929114 100644 --- a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js +++ b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js @@ -15,3 +15,8 @@ export function markAsyncSequenceRootTask(): void {} export function getCurrentAsyncSequence(): null | AsyncSequence { return null; } +export function getAsyncSequenceFromPromise( + promise: any, +): null | AsyncSequence { + return null; +} diff --git a/packages/react-server/src/ReactFlightServerTemporaryReferences.js b/packages/react-server/src/ReactFlightServerTemporaryReferences.js index 1f6b9f8ee3..e368b2e800 100644 --- a/packages/react-server/src/ReactFlightServerTemporaryReferences.js +++ b/packages/react-server/src/ReactFlightServerTemporaryReferences.js @@ -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; diff --git a/packages/react-server/src/ReactFlightThenable.js b/packages/react-server/src/ReactFlightThenable.js index 47e7b914da..99ddb36fa5 100644 --- a/packages/react-server/src/ReactFlightThenable.js +++ b/packages/react-server/src/ReactFlightThenable.js @@ -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>; +export type ThenableState = Array>; // 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( const previous = thenableState[index]; if (previous === undefined) { thenableState.push(thenable); + if (__DEV__ && enableAsyncDebugInfo) { + const stacks: Array = + (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 diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 989dbdf19c..42fa56a836 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -419,7 +419,7 @@ describe('ReactFlightAsyncDebugInfo', () => { "awaited": { "end": 0, "env": "Server", - "name": "getData", + "name": "delay", "owner": { "env": "Server", "key": null, @@ -438,19 +438,19 @@ describe('ReactFlightAsyncDebugInfo', () => { }, "stack": [ [ - "getData", + "delay", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 156, - 27, - 156, - 5, + 133, + 12, + 132, + 3, ], [ - "Component", + "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 165, - 22, - 163, + 158, + 21, + 156, 5, ], ], @@ -495,6 +495,267 @@ describe('ReactFlightAsyncDebugInfo', () => { } }); + it('can track async information when use()d', async () => { + async function getData(text) { + await delay(1); + return text.toUpperCase(); + } + + function Component() { + const result = ReactServer.use(getData('hi')); + const moreData = getData('seb'); + return ; + } + + function InnerComponent({text, promise}) { + // This async function depends on the I/O in parent components but it should not + // include that I/O as part of its own meta data. + return text + ', ' + ReactServer.use(promise); + } + + const stream = ReactServerDOMServer.renderToPipeableStream( + , + {}, + { + filterStackFrame, + }, + ); + + const readable = new Stream.PassThrough(streamOptions); + + const result = ReactServerDOMClient.createFromNodeStream(readable, { + moduleMap: {}, + moduleLoading: {}, + }); + stream.pipe(readable); + + expect(await result).toBe('HI, SEB'); + if ( + __DEV__ && + gate( + flags => + flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, + ) + ) { + expect(getDebugInfo(result)).toMatchInlineSnapshot(` + [ + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 500, + 13, + 499, + 5, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 505, + 36, + 504, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + "stack": [ + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 500, + 13, + 499, + 5, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 505, + 36, + 504, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "InnerComponent", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 507, + 60, + 504, + 5, + ], + ], + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 517, + 40, + 498, + 49, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 500, + 13, + 499, + 5, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 506, + 22, + 504, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "InnerComponent", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 507, + 60, + 504, + 5, + ], + ], + }, + "stack": [ + [ + "InnerComponent", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 513, + 40, + 510, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + ] + `); + } + }); + it('can track the start of I/O when no native promise is used', async () => { function Component() { const callbacks = []; @@ -540,16 +801,13 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 511, + 772, 109, - 498, + 759, 67, ], ], }, - { - "time": 0, - }, { "awaited": { "end": 0, @@ -564,9 +822,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 511, + 772, 109, - 498, + 759, 67, ], ], @@ -575,9 +833,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 501, + 762, 7, - 499, + 760, 5, ], ], @@ -637,9 +895,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 608, + 866, 109, - 599, + 857, 94, ], ], @@ -708,9 +966,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 679, + 937, 109, - 655, + 913, 50, ], ], @@ -790,9 +1048,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 761, + 1019, 109, - 744, + 1002, 63, ], ], @@ -817,9 +1075,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 757, + 1015, 24, - 756, + 1014, 5, ], ], @@ -849,9 +1107,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 757, + 1015, 24, - 756, + 1014, 5, ], ], @@ -868,17 +1126,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 746, + 1004, 13, - 745, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 752, + 1010, 24, - 751, + 1009, 5, ], ], @@ -902,9 +1160,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 757, + 1015, 24, - 756, + 1014, 5, ], ], @@ -913,17 +1171,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 746, + 1004, 13, - 745, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 752, + 1010, 24, - 751, + 1009, 5, ], ], @@ -956,9 +1214,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 757, + 1015, 24, - 756, + 1014, 5, ], ], @@ -975,17 +1233,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 747, + 1005, 13, - 745, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 752, + 1010, 18, - 751, + 1009, 5, ], ], @@ -1009,9 +1267,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 757, + 1015, 24, - 756, + 1014, 5, ], ], @@ -1020,17 +1278,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 747, + 1005, 13, - 745, + 1003, 5, ], [ "ThirdPartyComponent", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 752, + 1010, 18, - 751, + 1009, 5, ], ], @@ -1050,12 +1308,7 @@ describe('ReactFlightAsyncDebugInfo', () => { }); it('can track cached entries awaited in later components', async () => { - let cacheKey; - let cacheValue; const getData = cache(async function getData(text) { - if (cacheKey === text) { - return cacheValue; - } await delay(1); return text.toUpperCase(); }); @@ -1108,9 +1361,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1074, + 1327, 40, - 1052, + 1310, 62, ], ], @@ -1132,9 +1385,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1074, + 1327, 40, - 1052, + 1310, 62, ], ], @@ -1151,17 +1404,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1059, + 1312, 13, - 1055, + 1311, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1069, + 1322, 13, - 1068, + 1321, 5, ], ], @@ -1177,9 +1430,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1074, + 1327, 40, - 1052, + 1310, 62, ], ], @@ -1188,17 +1441,17 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1059, + 1312, 13, - 1055, + 1311, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1069, + 1322, 13, - 1068, + 1321, 5, ], ], @@ -1218,9 +1471,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1070, + 1323, 60, - 1068, + 1321, 5, ], ], @@ -1232,7 +1485,7 @@ describe('ReactFlightAsyncDebugInfo', () => { "awaited": { "end": 0, "env": "Server", - "name": "getData", + "name": "delay", "owner": { "env": "Server", "key": null, @@ -1242,28 +1495,36 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Object.", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1074, + 1327, 40, - 1052, + 1310, 62, ], ], }, "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], [ "getData", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1055, - 47, - 1055, + 1312, + 13, + 1311, 25, ], [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1069, + 1322, 13, - 1068, + 1321, 5, ], ], @@ -1279,9 +1540,9 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Component", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1070, + 1323, 60, - 1068, + 1321, 5, ], ], @@ -1290,9 +1551,578 @@ describe('ReactFlightAsyncDebugInfo', () => { [ "Child", "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", - 1064, + 1317, 28, - 1063, + 1316, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + ] + `); + } + }); + + it('can track cached entries used in child position', async () => { + const getData = cache(async function getData(text) { + await delay(1); + return text.toUpperCase(); + }); + + function Child() { + return getData('hi'); + } + + function Component() { + ReactServer.use(getData('hi')); + return ; + } + + const stream = ReactServerDOMServer.renderToPipeableStream( + , + {}, + { + filterStackFrame, + }, + ); + + const readable = new Stream.PassThrough(streamOptions); + + const result = ReactServerDOMClient.createFromNodeStream(readable, { + moduleMap: {}, + moduleLoading: {}, + }); + stream.pipe(readable); + + expect(await result).toBe('HI'); + if ( + __DEV__ && + gate( + flags => + flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, + ) + ) { + expect(getDebugInfo(result)).toMatchInlineSnapshot(` + [ + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1588, + 40, + 1572, + 57, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1588, + 40, + 1572, + 57, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1574, + 13, + 1573, + 25, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1583, + 23, + 1582, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1588, + 40, + 1572, + 57, + ], + ], + }, + "stack": [ + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1574, + 13, + 1573, + 25, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1583, + 23, + 1582, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Child", + "props": {}, + "stack": [ + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1584, + 60, + 1582, + 5, + ], + ], + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Component", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1588, + 40, + 1572, + 57, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "getData", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1574, + 13, + 1573, + 25, + ], + [ + "Component", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1583, + 23, + 1582, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + }, + { + "time": 0, + }, + { + "time": 0, + }, + ] + `); + } + }); + + it('can track implicit returned promises that are blocked by previous data', async () => { + 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 delayTrice(); + return 'hi'; + } + + const stream = ReactServerDOMServer.renderToPipeableStream( + , + {}, + { + filterStackFrame, + }, + ); + + const readable = new Stream.PassThrough(streamOptions); + + const result = ReactServerDOMClient.createFromNodeStream(readable, { + moduleMap: {}, + moduleLoading: {}, + }); + stream.pipe(readable); + + expect(await result).toBe('hi'); + if ( + __DEV__ && + gate( + flags => + flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo, + ) + ) { + expect(getDebugInfo(result)).toMatchInlineSnapshot(` + [ + { + "time": 0, + }, + { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "delayTrice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1812, + 13, + 1810, + 5, + ], + [ + "Bar", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1817, + 13, + 1816, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + "stack": [ + [ + "delayTrice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1812, + 13, + 1810, + 5, + ], + [ + "Bar", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1817, + 13, + 1816, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "delayTwice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1806, + 13, + 1805, + 5, + ], + [ + "delayTrice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1811, + 15, + 1810, + 5, + ], + [ + "Bar", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1817, + 13, + 1816, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + "stack": [ + [ + "delayTwice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1806, + 13, + 1805, + 5, + ], + [ + "delayTrice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1811, + 15, + 1810, + 5, + ], + [ + "Bar", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1817, + 13, + 1816, + 5, + ], + ], + }, + { + "time": 0, + }, + { + "awaited": { + "end": 0, + "env": "Server", + "name": "delay", + "owner": { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + "stack": [ + [ + "delay", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 133, + 12, + 132, + 3, + ], + [ + "delayTwice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1807, + 13, + 1805, + 5, + ], + ], + "start": 0, + }, + "env": "Server", + "owner": { + "env": "Server", + "key": null, + "name": "Bar", + "props": {}, + "stack": [ + [ + "Object.", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1822, + 40, + 1804, + 80, + ], + ], + }, + "stack": [ + [ + "delayTwice", + "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js", + 1807, + 13, + 1805, 5, ], ], diff --git a/packages/react/src/ReactContext.js b/packages/react/src/ReactContext.js index 24461ebfbb..d5dbb433df 100644 --- a/packages/react/src/ReactContext.js +++ b/packages/react/src/ReactContext.js @@ -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(defaultValue: T): ReactContext { // TODO: Second argument used to be an optional `calculateChangedBits` @@ -37,73 +32,11 @@ export function createContext(defaultValue: T): ReactContext { 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; diff --git a/packages/react/src/__tests__/ReactContextValidator-test.js b/packages/react/src/__tests__/ReactContextValidator-test.js index 96e46fa528..ff8f2b4215 100644 --- a/packages/react/src/__tests__/ReactContextValidator-test.js +++ b/packages/react/src/__tests__/ReactContextValidator-test.js @@ -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 { diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 5b483297ee..a1ea476b56 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -204,9 +204,6 @@ export const enableReactTestRendererWarning = true; // before removing them in stable in the next Major export const disableLegacyMode = true; -// Make equivalent to instead of -export const enableRenderableContext = true; - // ----------------------------------------------------------------------------- // Chopping Block // diff --git a/packages/shared/ReactSymbols.js b/packages/shared/ReactSymbols.js index 937c01cf75..2d478ffc67 100644 --- a/packages/shared/ReactSymbols.js +++ b/packages/shared/ReactSymbols.js @@ -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'); diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 1fb2a24671..3dd11f4af4 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index f514d53195..b1978f05a1 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index e2e2bf1c86..4a49fb7316 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 410eff7f34..c001a68557 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index f5772dd7aa..f58e154d02 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -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; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 700aebb1cc..263d7ca048 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -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__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index afe652ce3e..807e86da2b 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -24,7 +24,6 @@ export const { enableInfiniteRenderLoopDetection, enableNoCloningMemoCache, enableObjectFiber, - enableRenderableContext, enableRetryLaneExpiration, enableTransitionTracing, enableTrustedTypesIntegration, diff --git a/packages/shared/getComponentNameFromType.js b/packages/shared/getComponentNameFromType.js index da5cba301e..d9ed331660 100644 --- a/packages/shared/getComponentNameFromType.js +++ b/packages/shared/getComponentNameFromType.js @@ -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 = (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 = (type: any); - return getContextName(consumer._context) + '.Consumer'; - } else { - return null; - } + const consumer: ReactConsumerType = (type: any); + return getContextName(consumer._context) + '.Consumer'; case REACT_FORWARD_REF_TYPE: return getWrappedName(type, type.render, 'ForwardRef'); case REACT_MEMO_TYPE: diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js index d66ef65d9d..39c792b449 100644 --- a/scripts/flow/environment.js +++ b/scripts/flow/environment.js @@ -356,7 +356,9 @@ declare module 'async_hooks' { run(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;