diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index f249466431d..7bb65fbc04d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -454,6 +454,32 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(entry); } } + } else if ( + fn.env.config.enablePreserveExistingMemoizationGuarantees && + instr.value.kind === 'StartMemoize' && + instr.value.deps != null + ) { + for (const dep of instr.value.deps) { + if (dep.root.kind === 'NamedLocal') { + if ( + !isImmutableAtInstr(dep.root.value.identifier, instr.id, context) + ) { + continue; + } + for (let i = 0; i < dep.path.length; i++) { + const pathEntry = dep.path[i]!; + if (pathEntry.optional) { + break; + } + const depNode = context.registry.getOrCreateProperty({ + identifier: dep.root.value.identifier, + path: dep.path.slice(0, i), + reactive: dep.root.value.reactive, + }); + assumedNonNullObjects.add(depNode); + } + } + } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 57567f325fd..9fa88680ca2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -210,7 +210,7 @@ export const EnvironmentConfigSchema = z.object({ * that if a useEffect or useCallback references a function value, that function value will be * considered frozen, and in turn all of its referenced variables will be considered frozen as well. */ - enablePreserveExistingMemoizationGuarantees: z.boolean().default(false), + enablePreserveExistingMemoizationGuarantees: z.boolean().default(true), /** * Validates that all useMemo/useCallback values are also memoized by Forget. This mode can be diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.expect.md index 3f1f7b3222a..293d9964cc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false // bar(props.b) is an allocating expression that produces a primitive, which means // that Forget should memoize it. // Correctness: @@ -16,7 +17,8 @@ function AllocatingPrimitiveAsDep(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // bar(props.b) is an allocating expression that produces a primitive, which means +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false +// bar(props.b) is an allocating expression that produces a primitive, which means // that Forget should memoize it. // Correctness: // - y depends on either bar(props.b) or bar(props.b) + 1 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.js index c7ef86f7c22..3c0768e7f51 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allocating-primitive-as-dep.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false // bar(props.b) is an allocating expression that produces a primitive, which means // that Forget should memoize it. // Correctness: diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.expect.md index 44d4974f6f8..e9475a070b8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; const someGlobal = {value: 0}; @@ -32,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; const someGlobal = { value: 0 }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.js index 8d89c65f262..5bdeeaee1a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/allow-modify-global-in-callback-jsx.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; const someGlobal = {value: 0}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.expect.md index 6adc3056080..9f147194da1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function Component(props) { let a = foo(); // freeze `a` so we know the next line cannot mutate it @@ -17,7 +18,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const $ = _c(2); const a = foo(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.js index e6d2c795c37..29d6797fdd4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/computed-load-primitive-as-dependency.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false function Component(props) { let a = foo(); // freeze `a` so we know the next line cannot mutate it diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md index 6f13b0cd489..06a7ee3a37f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {Stringify, identity} from 'shared-runtime'; function foo() { @@ -64,7 +65,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { Stringify, identity } from "shared-runtime"; function foo() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js index 656a54db5c7..199284b8caa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {Stringify, identity} from 'shared-runtime'; function foo() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md index 5b8824eca6b..407fdcb0488 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {Stringify} from 'shared-runtime'; @@ -25,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.js index a365800e3ed..5ed1a9157bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/context-variable-as-jsx-element-tag.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.expect.md index c5167994cff..3b2768d350a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function foo(props) { let x, y; ({x, y} = {x: props.a, y: props.b}); @@ -21,6 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function foo(props) { let x; let y; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.js index 4e017cf2f77..ec10d66f26d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructure-direct-reassignment.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false function foo(props) { let x, y; ({x, y} = {x: props.a, y: props.b}); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md index 8fe4602c899..8592ae65e4b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableNewMutationAliasingModel +// @flow @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false /** * This hook returns a function that when called with an input object, * will return the result of mapping that input with the supplied map diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.js index accabed80fc..0fda92c726c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.js @@ -1,4 +1,4 @@ -// @flow @enableNewMutationAliasingModel +// @flow @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false /** * This hook returns a function that when called with an input object, * will return the result of mapping that input with the supplied map diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md index d8223e29498..8d603c629b6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false /** * Repro from https://github.com/facebook/react/issues/34262 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.js index f07d00854cd..086cda353e5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-later-mutation.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false /** * Repro from https://github.com/facebook/react/issues/34262 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.expect.md index 371348a5606..25b11eb38c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {identity, Stringify, useHook} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.js index 06d2945868d..7af080b072f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.repro-preserve-memoization-inner-destructured-value-mistaken-as-dependency-mutated-dep.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {identity, Stringify, useHook} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md new file mode 100644 index 00000000000..e9772e67993 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.expect.md @@ -0,0 +1,91 @@ + +## Input + +```javascript +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + // This is a bug in our dependency inference: we stop capturing dependencies + // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b` + // was non-nullish, then we can access `.c.d?.e`. Thus we should take the + // full property chain, exactly as-is with optionals/non-optionals, as a + // dependency + return identity(x.a.b?.c.d?.e); + }, + }); + }, [x.a.b?.c.d?.e]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +function Inner({x, result}) { + 'use no memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; + +``` + + +## Error + +``` +Found 1 error: + +Compilation Skipped: Existing memoization could not be preserved + +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x.a.b?.c`, but the source dependencies were [x.a.b?.c.d?.e]. Inferred less specific property than source. + +error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.ts:7:25 + 5 | + 6 | function Component({x}) { +> 7 | const object = useMemo(() => { + | ^^^^^^^ +> 8 | return identity({ + | ^^^^^^^^^^^^^^^^^^^^^ +> 9 | callback: () => { + | ^^^^^^^^^^^^^^^^^^^^^ +> 10 | // This is a bug in our dependency inference: we stop capturing dependencies + | ^^^^^^^^^^^^^^^^^^^^^ +> 11 | // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b` + | ^^^^^^^^^^^^^^^^^^^^^ +> 12 | // was non-nullish, then we can access `.c.d?.e`. Thus we should take the + | ^^^^^^^^^^^^^^^^^^^^^ +> 13 | // full property chain, exactly as-is with optionals/non-optionals, as a + | ^^^^^^^^^^^^^^^^^^^^^ +> 14 | // dependency + | ^^^^^^^^^^^^^^^^^^^^^ +> 15 | return identity(x.a.b?.c.d?.e); + | ^^^^^^^^^^^^^^^^^^^^^ +> 16 | }, + | ^^^^^^^^^^^^^^^^^^^^^ +> 17 | }); + | ^^^^^^^^^^^^^^^^^^^^^ +> 18 | }, [x.a.b?.c.d?.e]); + | ^^^^ Could not preserve existing manual memoization + 19 | const result = useMemo(() => { + 20 | return [object.callback()]; + 21 | }, [object]); +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js new file mode 100644 index 00000000000..12f8ebf3ce4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-preserve-memo-deps-mixed-optional-nonoptional-property-chain.js @@ -0,0 +1,41 @@ +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + // This is a bug in our dependency inference: we stop capturing dependencies + // after x.a.b?.c. But what this dependency is telling us is that if `x.a.b` + // was non-nullish, then we can access `.c.d?.e`. Thus we should take the + // full property chain, exactly as-is with optionals/non-optionals, as a + // dependency + return identity(x.a.b?.c.d?.e); + }, + }); + }, [x.a.b?.c.d?.e]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +function Inner({x, result}) { + 'use no memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.expect.md index 946dd33fcbf..fc00a96a9f3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @validatePreserveExistingMemoizationGuarantees +// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {logValue, useFragment, useHook, typedLog} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.js index 1621ab41c90..f3d08397770 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-capture-in-invoked-function-inferred-as-mutation.js @@ -1,4 +1,4 @@ -// @flow @validatePreserveExistingMemoizationGuarantees +// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {logValue, useFragment, useHook, typedLog} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md index 46c473a1b5e..be31341d15d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @validatePreserveExistingMemoizationGuarantees +// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useFragment} from 'react-relay'; import LogEvent from 'LogEvent'; import {useCallback, useMemo} from 'react'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.js index 34a9a128821..c71723f4964 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-missed-memoization-from-inferred-mutation-in-logger.js @@ -1,4 +1,4 @@ -// @flow @validatePreserveExistingMemoizationGuarantees +// @flow @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useFragment} from 'react-relay'; import LogEvent from 'LogEvent'; import {useCallback, useMemo} from 'react'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.expect.md index 11ef34621c4..4721e015b08 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {ValidateMemoization, useHook} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.tsx index b17a5a4f6a5..0b90cf45bc5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-repro-unmemoized-callback-captured-in-context-variable.tsx @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {ValidateMemoization, useHook} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.expect.md index 76b8d466ed4..c7e16b3c12b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {makeObject_Primitives, Stringify} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.js index b4145b1617f..b3a240a111c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-entries-mutation.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {makeObject_Primitives, Stringify} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.expect.md index fe532231896..bee5b18b7e2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {makeObject_Primitives, Stringify} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.js index 3482887d920..4f8c32367c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-object-values-mutation.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {makeObject_Primitives, Stringify} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md index 9f340f19ab9..0ead4d68f58 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -28,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c2 } from "react/compiler-runtime"; +import { c as _c2 } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.js index 96a8017848d..16af7ef85d1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/existing-variables-with-c-name.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.expect.md index 650d828bd25..f4a416a5036 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import fbt from 'fbt'; /** @@ -35,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import fbt from "fbt"; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.ts index 20b14c5c507..4ce2caadb9a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/bug-fbt-plural-multiple-function-calls.ts @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import fbt from 'fbt'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.expect.md index 387c033d98d..686d95a6f50 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false const {identity, mutate} = require('shared-runtime'); function Component(props) { @@ -24,6 +25,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript +// @enablePreserveExistingMemoizationGuarantees:false const { identity, mutate } = require("shared-runtime"); function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.js index 6dbdbac59c5..027d1e7f41e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-in-statement-type-inference.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false const {identity, mutate} = require('shared-runtime'); function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md index 6b95dda4739..c7fcfd1daae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @hookPattern:".*\b(use[^$]+)$" +// @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false import * as React from 'react'; import {makeArray, useHook} from 'shared-runtime'; @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @hookPattern:".*\b(use[^$]+)$" +import { c as _c } from "react/compiler-runtime"; // @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false import * as React from "react"; import { makeArray, useHook } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js index 246a74db72f..4db8451bc85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hooks-with-prefix.js @@ -1,4 +1,4 @@ -// @hookPattern:".*\b(use[^$]+)$" +// @hookPattern:".*\b(use[^$]+)$" @enablePreserveExistingMemoizationGuarantees:false import * as React from 'react'; import {makeArray, useHook} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.expect.md index 258371afec8..cd93ad9bf7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @debug +// @debug @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const x = makeObject(); const y = delete x[props.value]; @@ -14,7 +14,7 @@ function Component(props) { ## Code ```javascript -// @debug +// @debug @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const x = makeObject(); const y = delete x[props.value]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.js index 187e797f40f..6229cb6d3e5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-computed-delete.js @@ -1,4 +1,4 @@ -// @debug +// @debug @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const x = makeObject(); const y = delete x[props.value]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.expect.md index 4f1fb2d4929..f8e74b37dda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const x = makeObject(); const y = delete x.value; @@ -13,6 +14,7 @@ function Component(props) { ## Code ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const x = makeObject(); const y = delete x.value; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.js index b8540044449..1dd4bd710e0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-property-delete.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false function Component(props) { const x = makeObject(); const y = delete x.value; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.expect.md index a6d7eb64e10..f3ebad71dfc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel +// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {makeArray} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.js index b9b914d30ec..d084df5e7a1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-useCallback-captures-reassigned-context.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel +// @validatePreserveExistingMemoizationGuarantees @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {makeArray} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.expect.md index 83e593dbd41..98128b1e506 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; @@ -32,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { identity, ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.js index c7770ffcdce..3190c0e7c7c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity-function-expression.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.expect.md index 78cb6697fc7..012aec12b36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; @@ -29,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { identity, ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.js index bd928634a29..a542514109e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-through-identity.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity, ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.expect.md index 0a31e02ae25..53a99470a92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { mutate, @@ -49,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { mutate, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.tsx index 61f8c47e453..5d61126ee97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { mutate, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.expect.md index c985809353e..25f37285237 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, @@ -41,7 +42,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.tsx index d6bd1690f65..c6bd016280d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.expect.md index 4f665646241..9cb71732a09 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, @@ -42,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.tsx index d81c069e336..1e997906428 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.expect.md index 2cffd06f07b..81c53c90167 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, @@ -40,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.tsx index 72289eb8335..cb282549f06 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.expect.md index 458b75dff94..a51aaf5367c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, @@ -36,7 +37,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.tsx index d06ad11eb57..6d2f1785316 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.expect.md index 0786bac4341..fd7f2b0cda0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, @@ -40,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.tsx index 90b75976946..03ca2ef583a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import { typedCapture, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md index e33f52396d5..6c45eb8bfaf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableNewMutationAliasingModel +// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; @@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel +import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import { useCallback } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx index 08b9e4b2faa..f360a821325 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-deplist-controlflow.tsx @@ -1,4 +1,4 @@ -// @enableNewMutationAliasingModel +// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md index cc65670080b..f29be3d405b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableNewMutationAliasingModel +// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; @@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel +import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import { useCallback } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx index 43e2dfbb050..061af52723e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useCallback-reordering-depslist-assignment.tsx @@ -1,4 +1,4 @@ -// @enableNewMutationAliasingModel +// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md index 926887a7a44..ce3e3734e98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableNewMutationAliasingModel +// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; function useFoo(arr1, arr2) { @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel +import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; function useFoo(arr1, arr2) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts index 5b7d799d68b..7c4daae371f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/useMemo-reordering-depslist-assignment.ts @@ -1,4 +1,4 @@ -// @enableNewMutationAliasingModel +// @enableNewMutationAliasingModel @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; function useFoo(arr1, arr2) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md new file mode 100644 index 00000000000..84c611dec35 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md @@ -0,0 +1,122 @@ + +## Input + +```javascript +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); // accesses more levels of properties than the manual memo + }, + }); + // x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable + // we can only take a dep on x.y, not x.y.z + }, [x.y]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +const input1 = {x: {y: {z: 42}}}; +const input1b = {x: {y: {z: 42}}}; +const input2 = {x: {y: {z: 3.14}}}; +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [input1], + sequentialRenders: [ + input1, + input1, + input1b, // should reset even though .z didn't change + input1, + input2, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import { useMemo } from "react"; +import { identity, ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(11); + const { x } = t0; + let t1; + if ($[0] !== x.y) { + t1 = identity({ callback: () => identity(x.y.z) }); + $[0] = x.y; + $[1] = t1; + } else { + t1 = $[1]; + } + const object = t1; + let t2; + if ($[2] !== object) { + t2 = object.callback(); + $[2] = object; + $[3] = t2; + } else { + t2 = $[3]; + } + let t3; + if ($[4] !== t2) { + t3 = [t2]; + $[4] = t2; + $[5] = t3; + } else { + t3 = $[5]; + } + const result = t3; + let t4; + if ($[6] !== x.y) { + t4 = [x.y]; + $[6] = x.y; + $[7] = t4; + } else { + t4 = $[7]; + } + let t5; + if ($[8] !== result || $[9] !== t4) { + t5 = ; + $[8] = result; + $[9] = t4; + $[10] = t5; + } else { + t5 = $[10]; + } + return t5; +} + +const input1 = { x: { y: { z: 42 } } }; +const input1b = { x: { y: { z: 42 } } }; +const input2 = { x: { y: { z: 3.14 } } }; +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [input1], + sequentialRenders: [ + input1, + input1, + input1b, // should reset even though .z didn't change + input1, + input2, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":3.14}],"output":[3.14]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js new file mode 100644 index 00000000000..373fdc53fa2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js @@ -0,0 +1,35 @@ +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); // accesses more levels of properties than the manual memo + }, + }); + // x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable + // we can only take a dep on x.y, not x.y.z + }, [x.y]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +const input1 = {x: {y: {z: 42}}}; +const input1b = {x: {y: {z: 42}}}; +const input2 = {x: {y: {z: 3.14}}}; +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [input1], + sequentialRenders: [ + input1, + input1, + input1b, // should reset even though .z didn't change + input1, + input2, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md new file mode 100644 index 00000000000..82c11f7783d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md @@ -0,0 +1,117 @@ + +## Input + +```javascript +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); + }, + }); + }, [x.y.z]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import { useMemo } from "react"; +import { identity, ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(11); + const { x } = t0; + let t1; + if ($[0] !== x.y.z) { + t1 = identity({ callback: () => identity(x.y.z) }); + $[0] = x.y.z; + $[1] = t1; + } else { + t1 = $[1]; + } + const object = t1; + let t2; + if ($[2] !== object) { + t2 = object.callback(); + $[2] = object; + $[3] = t2; + } else { + t2 = $[3]; + } + let t3; + if ($[4] !== t2) { + t3 = [t2]; + $[4] = t2; + $[5] = t3; + } else { + t3 = $[5]; + } + const result = t3; + let t4; + if ($[6] !== x.y.z) { + t4 = [x.y.z]; + $[6] = x.y.z; + $[7] = t4; + } else { + t4 = $[7]; + } + let t5; + if ($[8] !== result || $[9] !== t4) { + t5 = ; + $[8] = result; + $[9] = t4; + $[10] = t5; + } else { + t5 = $[10]; + } + return t5; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: { y: { z: 42 } } }], + sequentialRenders: [ + { x: { y: { z: 42 } } }, + { x: { y: { z: 42 } } }, + { x: { y: { z: 3.14 } } }, + { x: { y: { z: 42 } } }, + { x: { y: { z: 3.14 } } }, + { x: { y: { z: 42 } } }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[42],"output":[42]}
+
{"inputs":[42],"output":[42]}
+
{"inputs":[3.14],"output":[3.14]}
+
{"inputs":[42],"output":[42]}
+
{"inputs":[3.14],"output":[3.14]}
+
{"inputs":[42],"output":[42]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js new file mode 100644 index 00000000000..6b55e68bb01 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js @@ -0,0 +1,31 @@ +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); + }, + }); + }, [x.y.z]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md new file mode 100644 index 00000000000..ab4940bcc3c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.expect.md @@ -0,0 +1,125 @@ + +## Input + +```javascript +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x, y, z}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x?.y?.z, y.a?.b, z.a.b?.c); + }, + }); + }, [x?.y?.z, y.a?.b, z.a.b?.c]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +function Inner({x, result}) { + 'use no memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import { useMemo } from "react"; +import { identity, ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(11); + const { x, y, z } = t0; + + x?.y?.z; + y.a?.b; + z.a.b?.c; + let t1; + if ($[0] !== x?.y?.z || $[1] !== y.a?.b || $[2] !== z.a.b?.c) { + t1 = identity({ callback: () => identity(x?.y?.z, y.a?.b, z.a.b?.c) }); + $[0] = x?.y?.z; + $[1] = y.a?.b; + $[2] = z.a.b?.c; + $[3] = t1; + } else { + t1 = $[3]; + } + const object = t1; + let t2; + if ($[4] !== object) { + t2 = object.callback(); + $[4] = object; + $[5] = t2; + } else { + t2 = $[5]; + } + let t3; + if ($[6] !== t2) { + t3 = [t2]; + $[6] = t2; + $[7] = t3; + } else { + t3 = $[7]; + } + const result = t3; + let t4; + if ($[8] !== result || $[9] !== x) { + t4 = ; + $[8] = result; + $[9] = x; + $[10] = t4; + } else { + t4 = $[10]; + } + return t4; +} + +function Inner({ x, result }) { + "use no memo"; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: { y: { z: 42 } } }], + sequentialRenders: [ + { x: { y: { z: 42 } } }, + { x: { y: { z: 42 } } }, + { x: { y: { z: 3.14 } } }, + { x: { y: { z: 42 } } }, + { x: { y: { z: 3.14 } } }, + { x: { y: { z: 42 } } }, + ], +}; + +``` + +### Eval output +(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]] +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js new file mode 100644 index 00000000000..820cef20cb2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-optional-property-chain.js @@ -0,0 +1,36 @@ +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x, y, z}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x?.y?.z, y.a?.b, z.a.b?.c); + }, + }); + }, [x?.y?.z, y.a?.b, z.a.b?.c]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +function Inner({x, result}) { + 'use no memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.expect.md index a30b33d8b64..9a092e3f228 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.ts index 1d0ca7f1bc7..628c786550e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.false-positive-useMemo-infer-mutate-deps.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md index eafc8f16cff..ed2e61d8ee3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; function Component({propA, propB}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts index 6c32f01b37d..a75a3936d4c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-conditional-access-own-scope.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; function Component({propA, propB}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md index 6298ab289c1..a16ef317b09 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {identity, mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts index d0c82f78663..23d77c84ec1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.hoist-useCallback-infer-conditional-value-block.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {identity, mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.expect.md index 082097cbd10..df6ee0495e1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {makeArray} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.ts index d7e77b77c26..29ff926d2e0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.invalid-useCallback-captures-reassigned-context.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {makeArray} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md index 92b667de08b..075458831b8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; function Component({propA, propB}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.ts index e5e53b8420e..c6642be4f89 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-conditional-access-noAlloc.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; function Component({propA, propB}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md index c22a96d629f..077b9aa9f6f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.ts index fef4fdfa680..637e0a974e6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useCallback-infer-less-specific-conditional-access.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md index cf24f259269..d93c52a10c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.ts index ee09122d602..9a81716828c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-access.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md index fafaad14e64..9d35f52504e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity, mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.ts index 1db0acf8398..7c9cb48c7c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-infer-less-specific-conditional-value-block.ts @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees +// @validatePreserveExistingMemoizationGuarantees @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {identity, mutate} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md index 18e0621d626..db5ff86ed23 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; @@ -35,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useCallback } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.tsx index ba0abc0d7cd..698fbeb2ca0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-deplist-controlflow.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.expect.md index 086ef4f58d6..448c2133301 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; @@ -30,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useCallback } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.tsx index 3ac3845c47f..d5d36490dfb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useCallback-reordering-depslist-assignment.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useCallback} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md index c224f1d17ec..58b2c9762b2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; function useFoo(arr1, arr2) { @@ -26,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; function useFoo(arr1, arr2) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.ts index 8025d3680fb..673380de119 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-assignment.ts @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; function useFoo(arr1, arr2) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md index d8a20367c91..d208ce4fb74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {Stringify} from 'shared-runtime'; @@ -35,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.tsx index c179d27b831..eae6a75854e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/useMemo-reordering-depslist-controlflow.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md index d913c4f29b3..6cf8820e984 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions +// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions @enablePreserveExistingMemoizationGuarantees:false function Component() { const items = useItems(); const filteredItems = useMemo( @@ -37,7 +37,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions +import { c as _c } from "react/compiler-runtime"; // @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions @enablePreserveExistingMemoizationGuarantees:false function Component() { const $ = _c(6); const items = useItems(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js index 1d01c209939..aa43a8d1886 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.js @@ -1,4 +1,4 @@ -// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions +// @enableAssumeHooksFollowRulesOfReact @enableTransitivelyFreezeFunctionExpressions @enablePreserveExistingMemoizationGuarantees:false function Component() { const items = useItems(); const filteredItems = useMemo( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.expect.md index 85d345fbf7d..68dc127bc1e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {Stringify, identity, makeArray, toJSON} from 'shared-runtime'; import {useMemo} from 'react'; @@ -40,7 +41,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { Stringify, identity, makeArray, toJSON } from "shared-runtime"; import { useMemo } from "react"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.js index 64135be731a..139be81a6c3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-renaming-conflicting-decls.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {Stringify, identity, makeArray, toJSON} from 'shared-runtime'; import {useMemo} from 'react'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md index 7af6bc996a9..4b8096f702a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false const THEME_MAP: ReadonlyMap = new Map([ ['default', 'light'], ['dark', 'dark'], @@ -21,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false const THEME_MAP: ReadonlyMap = new Map([ ["default", "light"], ["dark", "dark"], diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx index c1d835d6f0f..43a24622c5b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/ts-non-null-expression-default-value.tsx @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false const THEME_MAP: ReadonlyMap = new Map([ ['default', 'light'], ['dark', 'dark'], diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md index 7fa86838e8c..399be048d55 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function component(a) { let t = {t: a}; let z = +t.t; @@ -25,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false function component(a) { const $ = _c(8); const t = { t: a }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.js index c22b9208966..a367653dfd9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unary-expr.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false function component(a) { let t = {t: a}; let z = +t.t; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.expect.md index 9c87512a0f7..4d1cc124006 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateNoSetStateInRender:false +// @validateNoSetStateInRender:false @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {makeArray} from 'shared-runtime'; @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInRender:false +import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInRender:false @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; import { makeArray } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.ts index 317491efbfe..20a377c4749 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-named-function.ts @@ -1,4 +1,4 @@ -// @validateNoSetStateInRender:false +// @validateNoSetStateInRender:false @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; import {makeArray} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.expect.md index 260d695e09d..34529a21080 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; function Component(props) { return ( @@ -21,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees:false import { useMemo } from "react"; function Component(props) { const $ = _c(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.js index a96c044a3b8..7df73db402f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useMemo-with-optional.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false import {useMemo} from 'react'; function Component(props) { return ( diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.expect.md index 1edde1af1ce..033c7b0d98f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function Component() { const queue = [1, 2, 3]; let value = 0; @@ -22,6 +23,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript +// @enablePreserveExistingMemoizationGuarantees:false function Component() { const queue = [1, 2, 3]; let value; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.js index 295bf27d3fb..3af11ed453c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/while-with-assignment-in-test.js @@ -1,3 +1,4 @@ +// @enablePreserveExistingMemoizationGuarantees:false function Component() { const queue = [1, 2, 3]; let value = 0; diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index 9de9b564e93..d5280c091c9 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -974,12 +974,8 @@ describe('Store', () => { `); - const rendererID = getRendererID(); - const rootID = store.getRootIDForElement(store.getElementIDAtIndex(0)); await actAsync(() => { agent.overrideSuspenseMilestone({ - rendererID, - rootID, suspendedSet: [ store.getElementIDAtIndex(4), store.getElementIDAtIndex(8), @@ -1009,8 +1005,6 @@ describe('Store', () => { await actAsync(() => { agent.overrideSuspenseMilestone({ - rendererID, - rootID, suspendedSet: [], }); }); diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 5a7d6eb836e..fce8fe626d4 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -8,7 +8,11 @@ */ import EventEmitter from '../events'; -import {SESSION_STORAGE_LAST_SELECTION_KEY, __DEBUG__} from '../constants'; +import { + SESSION_STORAGE_LAST_SELECTION_KEY, + UNKNOWN_SUSPENDERS_NONE, + __DEBUG__, +} from '../constants'; import setupHighlighter from './views/Highlighter'; import { initialize as setupTraceUpdates, @@ -26,8 +30,13 @@ import type { RendererID, RendererInterface, DevToolsHookSettings, + InspectedElement, } from './types'; -import type {ComponentFilter} from 'react-devtools-shared/src/frontend/types'; +import type { + ComponentFilter, + DehydratedData, + ElementType, +} from 'react-devtools-shared/src/frontend/types'; import type {GroupItem} from './views/TraceUpdates/canvas'; import {gte, isReactNativeEnvironment} from './utils'; import { @@ -73,6 +82,13 @@ type InspectElementParams = { requestID: number, }; +type InspectScreenParams = { + forceFullData: boolean, + id: number, + path: Array | null, + requestID: number, +}; + type OverrideHookParams = { id: number, hookID: number, @@ -131,8 +147,6 @@ type OverrideSuspenseParams = { }; type OverrideSuspenseMilestoneParams = { - rendererID: number, - rootID: number, suspendedSet: Array, }; @@ -141,6 +155,111 @@ type PersistedSelection = { path: Array, }; +function createEmptyInspectedScreen( + arbitraryRootID: number, + type: ElementType, +): InspectedElement { + const suspendedBy: DehydratedData = { + cleaned: [], + data: [], + unserializable: [], + }; + return { + // invariants + id: arbitraryRootID, + type: type, + // Properties we merge + isErrored: false, + errors: [], + warnings: [], + suspendedBy, + suspendedByRange: null, + // TODO: How to merge these? + unknownSuspenders: UNKNOWN_SUSPENDERS_NONE, + // Properties where merging doesn't make sense so we ignore them entirely in the UI + rootType: null, + plugins: {stylex: null}, + nativeTag: null, + env: null, + source: null, + stack: null, + rendererPackageName: null, + rendererVersion: null, + // These don't make sense for a Root. They're just bottom values. + key: null, + canEditFunctionProps: false, + canEditHooks: false, + canEditFunctionPropsDeletePaths: false, + canEditFunctionPropsRenamePaths: false, + canEditHooksAndDeletePaths: false, + canEditHooksAndRenamePaths: false, + canToggleError: false, + canToggleSuspense: false, + isSuspended: false, + hasLegacyContext: false, + context: null, + hooks: null, + props: null, + state: null, + owners: null, + }; +} + +function mergeRoots( + left: InspectedElement, + right: InspectedElement, + suspendedByOffset: number, +): void { + const leftSuspendedByRange = left.suspendedByRange; + const rightSuspendedByRange = right.suspendedByRange; + + if (right.isErrored) { + left.isErrored = true; + } + for (let i = 0; i < right.errors.length; i++) { + left.errors.push(right.errors[i]); + } + for (let i = 0; i < right.warnings.length; i++) { + left.warnings.push(right.warnings[i]); + } + + const leftSuspendedBy: DehydratedData = left.suspendedBy; + const {data, cleaned, unserializable} = (right.suspendedBy: DehydratedData); + const leftSuspendedByData = ((leftSuspendedBy.data: any): Array); + const rightSuspendedByData = ((data: any): Array); + for (let i = 0; i < rightSuspendedByData.length; i++) { + leftSuspendedByData.push(rightSuspendedByData[i]); + } + for (let i = 0; i < cleaned.length; i++) { + leftSuspendedBy.cleaned.push( + [suspendedByOffset + cleaned[i][0]].concat(cleaned[i].slice(1)), + ); + } + for (let i = 0; i < unserializable.length; i++) { + leftSuspendedBy.unserializable.push( + [suspendedByOffset + unserializable[i][0]].concat( + unserializable[i].slice(1), + ), + ); + } + + if (rightSuspendedByRange !== null) { + if (leftSuspendedByRange === null) { + left.suspendedByRange = [ + rightSuspendedByRange[0], + rightSuspendedByRange[1], + ]; + } else { + if (rightSuspendedByRange[0] < leftSuspendedByRange[0]) { + leftSuspendedByRange[0] = rightSuspendedByRange[0]; + } + if (rightSuspendedByRange[1] > leftSuspendedByRange[1]) { + leftSuspendedByRange[1] = rightSuspendedByRange[1]; + } + } + } +} + export default class Agent extends EventEmitter<{ hideNativeHighlight: [], showNativeHighlight: [HostInstance], @@ -201,6 +320,7 @@ export default class Agent extends EventEmitter<{ bridge.addListener('getProfilingStatus', this.getProfilingStatus); bridge.addListener('getOwnersList', this.getOwnersList); bridge.addListener('inspectElement', this.inspectElement); + bridge.addListener('inspectScreen', this.inspectScreen); bridge.addListener('logElementToConsole', this.logElementToConsole); bridge.addListener('overrideError', this.overrideError); bridge.addListener('overrideSuspense', this.overrideSuspense); @@ -531,6 +651,138 @@ export default class Agent extends EventEmitter<{ } }; + inspectScreen: InspectScreenParams => void = ({ + requestID, + id, + forceFullData, + path: screenPath, + }) => { + let inspectedScreen: InspectedElement | null = null; + let found = false; + // the suspendedBy index will be from the previously merged roots. + // We need to keep track of how many suspendedBy we've already seen to know + // to which renderer the index belongs. + let suspendedByOffset = 0; + let suspendedByPathIndex: number | null = null; + // The path to hydrate for a specific renderer + let rendererPath: InspectElementParams['path'] = null; + if (screenPath !== null && screenPath.length > 1) { + const secondaryCategory = screenPath[0]; + if (secondaryCategory !== 'suspendedBy') { + throw new Error( + 'Only hydrating suspendedBy paths is supported. This is a bug.', + ); + } + if (typeof screenPath[1] !== 'number') { + throw new Error( + `Expected suspendedBy index to be a number. Received '${screenPath[1]}' instead. This is a bug.`, + ); + } + suspendedByPathIndex = screenPath[1]; + rendererPath = screenPath.slice(2); + } + + for (const rendererID in this._rendererInterfaces) { + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); + let path: InspectElementParams['path'] = null; + if (suspendedByPathIndex !== null && rendererPath !== null) { + const suspendedByPathRendererIndex = + suspendedByPathIndex - suspendedByOffset; + const rendererHasRequestedSuspendedByPath = + renderer.getElementAttributeByPath(id, [ + 'suspendedBy', + suspendedByPathRendererIndex, + ]) !== undefined; + if (rendererHasRequestedSuspendedByPath) { + path = ['suspendedBy', suspendedByPathRendererIndex].concat( + rendererPath, + ); + } + } + + const inspectedRootsPayload = renderer.inspectElement( + requestID, + id, + path, + forceFullData, + ); + switch (inspectedRootsPayload.type) { + case 'hydrated-path': + // The path will be relative to the Roots of this renderer. We adjust it + // to be relative to all Roots of this implementation. + inspectedRootsPayload.path[1] += suspendedByOffset; + // TODO: Hydration logic is flawed since the Frontend path is not based + // on the original backend data but rather its own representation of it (e.g. due to reorder). + // So we can receive null here instead when hydration fails + if (inspectedRootsPayload.value !== null) { + for ( + let i = 0; + i < inspectedRootsPayload.value.cleaned.length; + i++ + ) { + inspectedRootsPayload.value.cleaned[i][1] += suspendedByOffset; + } + } + this._bridge.send('inspectedScreen', inspectedRootsPayload); + // If we hydrated a path, it must've been in a specific renderer so we can stop here. + return; + case 'full-data': + const inspectedRoots = inspectedRootsPayload.value; + if (inspectedScreen === null) { + inspectedScreen = createEmptyInspectedScreen( + inspectedRoots.id, + inspectedRoots.type, + ); + } + mergeRoots(inspectedScreen, inspectedRoots, suspendedByOffset); + const dehydratedSuspendedBy: DehydratedData = + inspectedRoots.suspendedBy; + const suspendedBy = ((dehydratedSuspendedBy.data: any): Array); + suspendedByOffset += suspendedBy.length; + found = true; + break; + case 'no-change': + found = true; + const rootsSuspendedBy: Array = + (renderer.getElementAttributeByPath(id, ['suspendedBy']): any); + suspendedByOffset += rootsSuspendedBy.length; + break; + case 'not-found': + break; + case 'error': + // bail out and show the error + // TODO: aggregate errors + this._bridge.send('inspectedScreen', inspectedRootsPayload); + return; + } + } + + if (inspectedScreen === null) { + if (found) { + this._bridge.send('inspectedScreen', { + type: 'no-change', + responseID: requestID, + id, + }); + } else { + this._bridge.send('inspectedScreen', { + type: 'not-found', + responseID: requestID, + id, + }); + } + } else { + this._bridge.send('inspectedScreen', { + type: 'full-data', + responseID: requestID, + id, + value: inspectedScreen, + }); + } + }; + logElementToConsole: ElementAndRendererID => void = ({id, rendererID}) => { const renderer = this._rendererInterfaces[rendererID]; if (renderer == null) { @@ -567,17 +819,15 @@ export default class Agent extends EventEmitter<{ }; overrideSuspenseMilestone: OverrideSuspenseMilestoneParams => void = ({ - rendererID, - rootID, suspendedSet, }) => { - const renderer = this._rendererInterfaces[rendererID]; - if (renderer == null) { - console.warn( - `Invalid renderer id "${rendererID}" to override suspense milestone`, - ); - } else { - renderer.overrideSuspenseMilestone(rootID, suspendedSet); + for (const rendererID in this._rendererInterfaces) { + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); + if (renderer.supportsTogglingSuspense) { + renderer.overrideSuspenseMilestone(suspendedSet); + } } }; @@ -739,7 +989,7 @@ export default class Agent extends EventEmitter<{ if (renderer !== null) { const devRenderer = renderer.bundleType === 1; const enableSuspenseTab = - devRenderer && gte(renderer.version, '19.2.0-canary'); + devRenderer && gte(renderer.version, '19.3.0-canary'); if (enableSuspenseTab) { this._bridge.send('enableSuspenseTab'); } diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 1a76c859022..222baa0dae0 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -78,6 +78,7 @@ import { __DEBUG__, PROFILING_FLAG_BASIC_SUPPORT, PROFILING_FLAG_TIMELINE_SUPPORT, + PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT, TREE_OPERATION_ADD, TREE_OPERATION_REMOVE, TREE_OPERATION_REORDER_CHILDREN, @@ -1074,6 +1075,7 @@ export function attach( const supportsTogglingSuspense = typeof setSuspenseHandler === 'function' && typeof scheduleUpdate === 'function'; + const supportsPerformanceTracks = gte(version, '19.2.0'); if (typeof scheduleRefresh === 'function') { // When Fast Refresh updates a component, the frontend may need to purge cached information. @@ -2401,6 +2403,9 @@ export function attach( if (typeof injectProfilingHooks === 'function') { profilingFlags |= PROFILING_FLAG_TIMELINE_SUPPORT; } + if (supportsPerformanceTracks) { + profilingFlags |= PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT; + } } // Set supportsStrictMode to false for production renderer builds @@ -2415,7 +2420,6 @@ export function attach( !isProductionBuildOfRenderer && StrictModeBits !== 0 ? 1 : 0, ); pushOperation(hasOwnerMetadata ? 1 : 0); - pushOperation(supportsTogglingSuspense ? 1 : 0); if (isProfiling) { if (displayNamesByRootID !== null) { @@ -4897,7 +4901,11 @@ export function attach( fiberInstance.data = nextFiber; if ( mostRecentlyInspectedElement !== null && - mostRecentlyInspectedElement.id === fiberInstance.id && + (mostRecentlyInspectedElement.id === fiberInstance.id || + // If we're inspecting a Root, we inspect the Screen. + // Invalidating any Root invalidates the Screen too. + (mostRecentlyInspectedElement.type === ElementTypeRoot && + nextFiber.tag === HostRoot)) && didFiberRender(prevFiber, nextFiber) ) { // If this Fiber has updated, clear cached inspected data. @@ -6417,7 +6425,10 @@ export function attach( return inspectVirtualInstanceRaw(devtoolsInstance); } if (devtoolsInstance.kind === FIBER_INSTANCE) { - return inspectFiberInstanceRaw(devtoolsInstance); + const isRoot = devtoolsInstance.parent === null; + return isRoot + ? inspectRootsRaw(devtoolsInstance.id) + : inspectFiberInstanceRaw(devtoolsInstance); } (devtoolsInstance: FilteredFiberInstance); // assert exhaustive throw new Error('Unsupported instance kind'); @@ -6870,10 +6881,24 @@ export function attach( let currentlyInspectedPaths: Object = {}; function isMostRecentlyInspectedElement(id: number): boolean { - return ( - mostRecentlyInspectedElement !== null && - mostRecentlyInspectedElement.id === id - ); + if (mostRecentlyInspectedElement === null) { + return false; + } + if (mostRecentlyInspectedElement.id === id) { + return true; + } + + if (mostRecentlyInspectedElement.type === ElementTypeRoot) { + // we inspected the screen recently. If we're inspecting another root, we're + // still inspecting the screen. + const instance = idToDevToolsInstanceMap.get(id); + return ( + instance !== undefined && + instance.kind === FIBER_INSTANCE && + instance.parent === null + ); + } + return false; } function isMostRecentlyInspectedElementCurrent(id: number): boolean { @@ -7055,8 +7080,8 @@ export function attach( if (!hasElementUpdatedSinceLastInspected) { if (path !== null) { let secondaryCategory: 'suspendedBy' | 'hooks' | null = null; - if (path[0] === 'hooks') { - secondaryCategory = 'hooks'; + if (path[0] === 'hooks' || path[0] === 'suspendedBy') { + secondaryCategory = path[0]; } // If this element has not been updated since it was last inspected, @@ -7204,6 +7229,99 @@ export function attach( }; } + function inspectRootsRaw(arbitraryRootID: number): InspectedElement | null { + const roots = hook.getFiberRoots(rendererID); + if (roots.size === 0) { + return null; + } + + const inspectedRoots: InspectedElement = { + // invariants + id: arbitraryRootID, + type: ElementTypeRoot, + // Properties we merge + isErrored: false, + errors: [], + warnings: [], + suspendedBy: [], + suspendedByRange: null, + // TODO: How to merge these? + unknownSuspenders: UNKNOWN_SUSPENDERS_NONE, + // Properties where merging doesn't make sense so we ignore them entirely in the UI + rootType: null, + plugins: {stylex: null}, + nativeTag: null, + env: null, + source: null, + stack: null, + rendererPackageName: null, + rendererVersion: null, + // These don't make sense for a Root. They're just bottom values. + key: null, + canEditFunctionProps: false, + canEditHooks: false, + canEditFunctionPropsDeletePaths: false, + canEditFunctionPropsRenamePaths: false, + canEditHooksAndDeletePaths: false, + canEditHooksAndRenamePaths: false, + canToggleError: false, + canToggleSuspense: false, + isSuspended: false, + hasLegacyContext: false, + context: null, + hooks: null, + props: null, + state: null, + owners: null, + }; + + let minSuspendedByRange = Infinity; + let maxSuspendedByRange = -Infinity; + roots.forEach(root => { + const rootInstance = rootToFiberInstanceMap.get(root); + if (rootInstance === undefined) { + throw new Error( + 'Expected a root instance to exist for this Fiber root', + ); + } + const inspectedRoot = inspectFiberInstanceRaw(rootInstance); + if (inspectedRoot === null) { + return; + } + + if (inspectedRoot.isErrored) { + inspectedRoots.isErrored = true; + } + for (let i = 0; i < inspectedRoot.errors.length; i++) { + inspectedRoots.errors.push(inspectedRoot.errors[i]); + } + for (let i = 0; i < inspectedRoot.warnings.length; i++) { + inspectedRoots.warnings.push(inspectedRoot.warnings[i]); + } + for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) { + inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]); + } + const suspendedByRange = inspectedRoot.suspendedByRange; + if (suspendedByRange !== null) { + if (suspendedByRange[0] < minSuspendedByRange) { + minSuspendedByRange = suspendedByRange[0]; + } + if (suspendedByRange[1] > maxSuspendedByRange) { + maxSuspendedByRange = suspendedByRange[1]; + } + } + }); + + if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) { + inspectedRoots.suspendedByRange = [ + minSuspendedByRange, + maxSuspendedByRange, + ]; + } + + return inspectedRoots; + } + function logElementToConsole(id: number) { const result = isMostRecentlyInspectedElementCurrent(id) ? mostRecentlyInspectedElement @@ -7862,13 +7980,9 @@ export function attach( /** * Resets the all other roots of this renderer. - * @param rootID The root that contains this milestone * @param suspendedSet List of IDs of SuspenseComponent Fibers */ - function overrideSuspenseMilestone( - rootID: FiberInstance['id'], - suspendedSet: Array, - ) { + function overrideSuspenseMilestone(suspendedSet: Array) { if ( typeof setSuspenseHandler !== 'function' || typeof scheduleUpdate !== 'function' @@ -7878,8 +7992,6 @@ export function attach( ); } - // TODO: Allow overriding the timeline for the specified root. - const unsuspendedSet: Set = new Set(forceFallbackForFibers); let resuspended = false; diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index 8a245155ef2..46709c9a804 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -412,7 +412,6 @@ export function attach( pushOperation(0); // Profiling flag pushOperation(0); // StrictMode supported? pushOperation(hasOwnerMetadata ? 1 : 0); - pushOperation(supportsTogglingSuspense ? 1 : 0); pushOperation(SUSPENSE_TREE_OPERATION_ADD); pushOperation(id); @@ -800,6 +799,20 @@ export function attach( return null; } + const rootID = internalInstanceToRootIDMap.get(internalInstance); + if (rootID === undefined) { + throw new Error('Expected to find root ID.'); + } + const isRoot = rootID === id; + return isRoot + ? inspectRootsRaw(rootID) + : inspectInternalInstanceRaw(id, internalInstance); + } + + function inspectInternalInstanceRaw( + id: number, + internalInstance: InternalInstance, + ): InspectedElement | null { const {key} = getData(internalInstance); const type = getElementType(internalInstance); @@ -903,6 +916,98 @@ export function attach( }; } + function inspectRootsRaw(arbitraryRootID: number): InspectedElement | null { + const roots = + renderer.Mount._instancesByReactRootID || + renderer.Mount._instancesByContainerID; + + const inspectedRoots: InspectedElement = { + // invariants + id: arbitraryRootID, + type: ElementTypeRoot, + // Properties we merge + isErrored: false, + errors: [], + warnings: [], + suspendedBy: [], + suspendedByRange: null, + // TODO: How to merge these? + unknownSuspenders: UNKNOWN_SUSPENDERS_NONE, + // Properties where merging doesn't make sense so we ignore them entirely in the UI + rootType: null, + plugins: {stylex: null}, + nativeTag: null, + env: null, + source: null, + stack: null, + // TODO: We could make the Frontend accept an array to display + // a list of unique renderers contributing to this Screen. + rendererPackageName: null, + rendererVersion: null, + // These don't make sense for a Root. They're just bottom values. + key: null, + canEditFunctionProps: false, + canEditHooks: false, + canEditFunctionPropsDeletePaths: false, + canEditFunctionPropsRenamePaths: false, + canEditHooksAndDeletePaths: false, + canEditHooksAndRenamePaths: false, + canToggleError: false, + canToggleSuspense: false, + isSuspended: false, + hasLegacyContext: false, + context: null, + hooks: null, + props: null, + state: null, + owners: null, + }; + + let minSuspendedByRange = Infinity; + let maxSuspendedByRange = -Infinity; + + for (const rootKey in roots) { + const internalInstance = roots[rootKey]; + const id = getID(internalInstance); + const inspectedRoot = inspectInternalInstanceRaw(id, internalInstance); + + if (inspectedRoot === null) { + return null; + } + + if (inspectedRoot.isErrored) { + inspectedRoots.isErrored = true; + } + for (let i = 0; i < inspectedRoot.errors.length; i++) { + inspectedRoots.errors.push(inspectedRoot.errors[i]); + } + for (let i = 0; i < inspectedRoot.warnings.length; i++) { + inspectedRoots.warnings.push(inspectedRoot.warnings[i]); + } + for (let i = 0; i < inspectedRoot.suspendedBy.length; i++) { + inspectedRoots.suspendedBy.push(inspectedRoot.suspendedBy[i]); + } + const suspendedByRange = inspectedRoot.suspendedByRange; + if (suspendedByRange !== null) { + if (suspendedByRange[0] < minSuspendedByRange) { + minSuspendedByRange = suspendedByRange[0]; + } + if (suspendedByRange[1] > maxSuspendedByRange) { + maxSuspendedByRange = suspendedByRange[1]; + } + } + } + + if (minSuspendedByRange !== Infinity || maxSuspendedByRange !== -Infinity) { + inspectedRoots.suspendedByRange = [ + minSuspendedByRange, + maxSuspendedByRange, + ]; + } + + return inspectedRoots; + } + function logElementToConsole(id: number): void { const result = inspectElementRaw(id); if (result === null) { diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index e002740cb69..1052dc9d75b 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -450,10 +450,7 @@ export type RendererInterface = { onErrorOrWarning?: OnErrorOrWarning, overrideError: (id: number, forceError: boolean) => void, overrideSuspense: (id: number, forceFallback: boolean) => void, - overrideSuspenseMilestone: ( - rootID: number, - suspendedSet: Array, - ) => void, + overrideSuspenseMilestone: (suspendedSet: Array) => void, overrideValueAtPath: ( type: Type, id: number, diff --git a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js index 155717e5d89..6fd93d3519c 100644 --- a/packages/react-devtools-shared/src/backend/views/Highlighter/index.js +++ b/packages/react-devtools-shared/src/backend/views/Highlighter/index.js @@ -10,6 +10,7 @@ import Agent from 'react-devtools-shared/src/backend/agent'; import {hideOverlay, showOverlay} from './Highlighter'; +import type {HostInstance} from 'react-devtools-shared/src/backend/types'; import type {BackendBridge} from 'react-devtools-shared/src/bridge'; import type {RendererInterface} from '../../types'; @@ -26,6 +27,7 @@ export default function setupHighlighter( ): void { bridge.addListener('clearHostInstanceHighlight', clearHostInstanceHighlight); bridge.addListener('highlightHostInstance', highlightHostInstance); + bridge.addListener('highlightHostInstances', highlightHostInstances); bridge.addListener('scrollToHostInstance', scrollToHostInstance); bridge.addListener('shutdown', stopInspectingHost); bridge.addListener('startInspectingHost', startInspectingHost); @@ -157,6 +159,52 @@ export default function setupHighlighter( hideOverlay(agent); } + function highlightHostInstances({ + displayName, + hideAfterTimeout, + elements, + scrollIntoView, + }: { + displayName: string | null, + hideAfterTimeout: boolean, + elements: Array<{rendererID: number, id: number}>, + scrollIntoView: boolean, + }) { + const nodes: Array = []; + for (let i = 0; i < elements.length; i++) { + const {id, rendererID} = elements[i]; + const renderer = agent.rendererInterfaces[rendererID]; + if (renderer == null) { + console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`); + continue; + } + + // In some cases fiber may already be unmounted + if (!renderer.hasElementWithId(id)) { + continue; + } + + const hostInstances = renderer.findHostInstancesForElementID(id); + if (hostInstances !== null) { + for (let j = 0; j < hostInstances.length; j++) { + nodes.push(hostInstances[j]); + } + } + } + + if (nodes.length > 0) { + const node = nodes[0]; + // $FlowFixMe[method-unbinding] + if (scrollIntoView && typeof node.scrollIntoView === 'function') { + // If the node isn't visible show it before highlighting it. + // We may want to reconsider this; it might be a little disruptive. + node.scrollIntoView({block: 'nearest', inline: 'nearest'}); + } + } + + showOverlay(nodes, displayName, agent, hideAfterTimeout); + } + function attemptScrollToHostInstance( renderer: RendererInterface, id: number, diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index f6c11fb10d6..c332393d2aa 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -95,7 +95,7 @@ export function inspectElement( id: number, path: InspectedElementPath | null, rendererID: number, - shouldListenToPauseEvents: boolean = false, + shouldListenToPauseEvents: boolean, ): Promise { const requestID = requestCounter++; const promise = getPromiseForRequestID( @@ -117,6 +117,32 @@ export function inspectElement( return promise; } +export function inspectScreen( + bridge: FrontendBridge, + forceFullData: boolean, + arbitraryRootID: number, + path: InspectedElementPath | null, + shouldListenToPauseEvents: boolean, +): Promise { + const requestID = requestCounter++; + const promise = getPromiseForRequestID( + requestID, + 'inspectedScreen', + bridge, + `Timed out while inspecting screen.`, + shouldListenToPauseEvents, + ); + + bridge.send('inspectScreen', { + requestID, + id: arbitraryRootID, + path, + forceFullData, + }); + + return promise; +} + let storeAsGlobalCount = 0; export function storeAsGlobal({ diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index aa9c867e1f1..b6229192c23 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -65,12 +65,6 @@ export const BRIDGE_PROTOCOL: Array = [ { version: 2, minNpmVersion: '4.22.0', - maxNpmVersion: '6.2.0', - }, - // Version 3 adds supports-toggling-suspense bit to add-root - { - version: 3, - minNpmVersion: '6.2.0', maxNpmVersion: null, }, ]; @@ -92,6 +86,12 @@ type HighlightHostInstance = { openBuiltinElementsPanel: boolean, scrollIntoView: boolean, }; +type HighlightHostInstances = { + elements: Array, + displayName: string | null, + hideAfterTimeout: boolean, + scrollIntoView: boolean, +}; type ScrollToHostInstance = { ...ElementAndRendererID, @@ -145,8 +145,6 @@ type OverrideSuspense = { }; type OverrideSuspenseMilestone = { - rendererID: number, - rootID: number, suspendedSet: Array, }; @@ -167,6 +165,13 @@ type InspectElementParams = { requestID: number, }; +type InspectScreenParams = { + requestID: number, + id: number, + forceFullData: boolean, + path: Array | null, +}; + type StoreAsGlobalParams = { ...ElementAndRendererID, count: number, @@ -199,6 +204,7 @@ export type BackendEvents = { fastRefreshScheduled: [], getSavedPreferences: [], inspectedElement: [InspectedElementPayload], + inspectedScreen: [InspectedElementPayload], isReloadAndProfileSupportedByBackend: [boolean], operations: [Array], ownersList: [OwnersList], @@ -243,7 +249,9 @@ type FrontendEvents = { getProfilingData: [{rendererID: RendererID}], getProfilingStatus: [], highlightHostInstance: [HighlightHostInstance], + highlightHostInstances: [HighlightHostInstances], inspectElement: [InspectElementParams], + inspectScreen: [InspectScreenParams], logElementToConsole: [ElementAndRendererID], overrideError: [OverrideError], overrideSuspense: [OverrideSuspense], diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js index 1a8ef8caa8b..c398e130d84 100644 --- a/packages/react-devtools-shared/src/constants.js +++ b/packages/react-devtools-shared/src/constants.js @@ -30,8 +30,9 @@ export const SUSPENSE_TREE_OPERATION_REORDER_CHILDREN = 10; export const SUSPENSE_TREE_OPERATION_RESIZE = 11; export const SUSPENSE_TREE_OPERATION_SUSPENDERS = 12; -export const PROFILING_FLAG_BASIC_SUPPORT = 0b01; -export const PROFILING_FLAG_TIMELINE_SUPPORT = 0b10; +export const PROFILING_FLAG_BASIC_SUPPORT /*. */ = 0b001; +export const PROFILING_FLAG_TIMELINE_SUPPORT /* */ = 0b010; +export const PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT /* */ = 0b100; export const UNKNOWN_SUSPENDERS_NONE: UnknownSuspendersReason = 0; // If we had at least one debugInfo, then that might have been the reason. export const UNKNOWN_SUSPENDERS_REASON_PRODUCTION: UnknownSuspendersReason = 1; // We're running in prod. That might be why we had unknown suspenders. diff --git a/packages/react-devtools-shared/src/devtools/store.js b/packages/react-devtools-shared/src/devtools/store.js index 6ffbb52d94e..99971f6a1a6 100644 --- a/packages/react-devtools-shared/src/devtools/store.js +++ b/packages/react-devtools-shared/src/devtools/store.js @@ -13,6 +13,7 @@ import {inspect} from 'util'; import { PROFILING_FLAG_BASIC_SUPPORT, PROFILING_FLAG_TIMELINE_SUPPORT, + PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT, TREE_OPERATION_ADD, TREE_OPERATION_REMOVE, TREE_OPERATION_REMOVE_ROOT, @@ -86,12 +87,16 @@ export type Config = { supportsTraceUpdates?: boolean, }; +const ADVANCED_PROFILING_NONE = 0; +const ADVANCED_PROFILING_TIMELINE = 1; +const ADVANCED_PROFILING_PERFORMANCE_TRACKS = 2; +type AdvancedProfiling = 0 | 1 | 2; + export type Capabilities = { supportsBasicProfiling: boolean, hasOwnerMetadata: boolean, supportsStrictMode: boolean, - supportsTogglingSuspense: boolean, - supportsTimeline: boolean, + supportsAdvancedProfiling: AdvancedProfiling, }; /** @@ -112,6 +117,7 @@ export default class Store extends EventEmitter<{ roots: [], rootSupportsBasicProfiling: [], rootSupportsTimelineProfiling: [], + rootSupportsPerformanceTracks: [], suspenseTreeMutated: [[Map]], supportsNativeStyleEditor: [], supportsReloadAndProfile: [], @@ -195,6 +201,7 @@ export default class Store extends EventEmitter<{ // These options default to false but may be updated as roots are added and removed. _rootSupportsBasicProfiling: boolean = false; _rootSupportsTimelineProfiling: boolean = false; + _rootSupportsPerformanceTracks: boolean = false; _bridgeProtocol: BridgeProtocol | null = null; _unsupportedBridgeProtocolDetected: boolean = false; @@ -474,6 +481,11 @@ export default class Store extends EventEmitter<{ return this._rootSupportsTimelineProfiling; } + // At least one of the currently mounted roots support performance tracks. + get rootSupportsPerformanceTracks(): boolean { + return this._rootSupportsPerformanceTracks; + } + get supportsInspectMatchingDOMElement(): boolean { return this._supportsInspectMatchingDOMElement; } @@ -493,14 +505,6 @@ export default class Store extends EventEmitter<{ ); } - supportsTogglingSuspense(rootID: Element['id']): boolean { - const capabilities = this._rootIDToCapabilities.get(rootID); - if (capabilities === undefined) { - throw new Error(`No capabilities registered for root ${rootID}`); - } - return capabilities.supportsTogglingSuspense; - } - // This build of DevTools supports the Timeline profiler. // This is a static flag, controlled by the Store config. get supportsTimeline(): boolean { @@ -885,38 +889,48 @@ export default class Store extends EventEmitter<{ * @param uniqueSuspendersOnly Filters out boundaries without unique suspenders */ getSuspendableDocumentOrderSuspense( - rootID: Element['id'] | void, uniqueSuspendersOnly: boolean, ): $ReadOnlyArray { - if (rootID === undefined) { - return []; - } - const root = this.getElementByID(rootID); - if (root === null) { - return []; - } - if (!this.supportsTogglingSuspense(rootID)) { + const roots = this.roots; + if (roots.length === 0) { return []; } + const list: SuspenseNode['id'][] = []; - const suspense = this.getSuspenseByID(rootID); - if (suspense !== null) { - const stack = [suspense]; - while (stack.length > 0) { - const current = stack.pop(); - if (current === undefined) { - continue; - } - // Include the root even if we won't show it suspended (because that's just blank). - // You should be able to see what suspended the shell. - if (!uniqueSuspendersOnly || current.hasUniqueSuspenders) { - list.push(current.id); + for (let i = 0; i < roots.length; i++) { + const rootID = roots[i]; + const root = this.getElementByID(rootID); + if (root === null) { + continue; + } + // TODO: This includes boundaries that can't be suspended due to no support from the renderer. + + const suspense = this.getSuspenseByID(rootID); + if (suspense !== null) { + if (list.length === 0) { + // start with an arbitrary root that will allow inspection of the Screen + list.push(suspense.id); } - // Add children in reverse order to maintain document order - for (let j = current.children.length - 1; j >= 0; j--) { - const childSuspense = this.getSuspenseByID(current.children[j]); - if (childSuspense !== null) { - stack.push(childSuspense); + + const stack = [suspense]; + while (stack.length > 0) { + const current = stack.pop(); + if (current === undefined) { + continue; + } + if ( + (!uniqueSuspendersOnly || current.hasUniqueSuspenders) && + // Roots are already included as part of the Screen + current.id !== rootID + ) { + list.push(current.id); + } + // Add children in reverse order to maintain document order + for (let j = current.children.length - 1; j >= 0; j--) { + const childSuspense = this.getSuspenseByID(current.children[j]); + if (childSuspense !== null) { + stack.push(childSuspense); + } } } } @@ -1161,15 +1175,23 @@ export default class Store extends EventEmitter<{ const isStrictModeCompliant = operations[i] > 0; i++; + const profilerFlags = operations[i++]; const supportsBasicProfiling = - (operations[i] & PROFILING_FLAG_BASIC_SUPPORT) !== 0; + (profilerFlags & PROFILING_FLAG_BASIC_SUPPORT) !== 0; const supportsTimeline = - (operations[i] & PROFILING_FLAG_TIMELINE_SUPPORT) !== 0; - i++; + (profilerFlags & PROFILING_FLAG_TIMELINE_SUPPORT) !== 0; + const supportsPerformanceTracks = + (profilerFlags & PROFILING_FLAG_PERFORMANCE_TRACKS_SUPPORT) !== 0; + let supportsAdvancedProfiling: AdvancedProfiling = + ADVANCED_PROFILING_NONE; + if (supportsPerformanceTracks) { + supportsAdvancedProfiling = ADVANCED_PROFILING_PERFORMANCE_TRACKS; + } else if (supportsTimeline) { + supportsAdvancedProfiling = ADVANCED_PROFILING_TIMELINE; + } let supportsStrictMode = false; let hasOwnerMetadata = false; - let supportsTogglingSuspense = false; // If we don't know the bridge protocol, guess that we're dealing with the latest. // If we do know it, we can take it into consideration when parsing operations. @@ -1182,9 +1204,6 @@ export default class Store extends EventEmitter<{ hasOwnerMetadata = operations[i] > 0; i++; - - supportsTogglingSuspense = operations[i] > 0; - i++; } this._roots = this._roots.concat(id); @@ -1193,8 +1212,7 @@ export default class Store extends EventEmitter<{ supportsBasicProfiling, hasOwnerMetadata, supportsStrictMode, - supportsTogglingSuspense, - supportsTimeline, + supportsAdvancedProfiling, }); // Not all roots support StrictMode; @@ -1539,7 +1557,12 @@ export default class Store extends EventEmitter<{ if (name === null) { // The boundary isn't explicitly named. // Pick a sensible default. - name = this._guessSuspenseName(element); + if (parentID === 0) { + // For Roots we use their display name. + name = element.displayName; + } else { + name = this._guessSuspenseName(element); + } } } @@ -1842,21 +1865,33 @@ export default class Store extends EventEmitter<{ const prevRootSupportsProfiling = this._rootSupportsBasicProfiling; const prevRootSupportsTimelineProfiling = this._rootSupportsTimelineProfiling; + const prevRootSupportsPerformanceTracks = + this._rootSupportsPerformanceTracks; this._hasOwnerMetadata = false; this._rootSupportsBasicProfiling = false; this._rootSupportsTimelineProfiling = false; + this._rootSupportsPerformanceTracks = false; this._rootIDToCapabilities.forEach( - ({supportsBasicProfiling, hasOwnerMetadata, supportsTimeline}) => { + ({ + supportsBasicProfiling, + hasOwnerMetadata, + supportsAdvancedProfiling, + }) => { if (supportsBasicProfiling) { this._rootSupportsBasicProfiling = true; } if (hasOwnerMetadata) { this._hasOwnerMetadata = true; } - if (supportsTimeline) { + if (supportsAdvancedProfiling === ADVANCED_PROFILING_TIMELINE) { this._rootSupportsTimelineProfiling = true; } + if ( + supportsAdvancedProfiling === ADVANCED_PROFILING_PERFORMANCE_TRACKS + ) { + this._rootSupportsPerformanceTracks = true; + } }, ); @@ -1872,6 +1907,12 @@ export default class Store extends EventEmitter<{ ) { this.emit('rootSupportsTimelineProfiling'); } + if ( + this._rootSupportsPerformanceTracks !== + prevRootSupportsPerformanceTracks + ) { + this.emit('rootSupportsPerformanceTracks'); + } } if (hasSuspenseTreeChanged) { diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js index 9e928b02319..28be5f9e1c7 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js @@ -209,7 +209,6 @@ function updateTree( i++; // Profiling flag i++; // supportsStrictMode flag i++; // hasOwnerMetadata flag - i++; // supportsTogglingSuspense flag if (__DEBUG__) { debug('Add', `new root fiber ${id}`); diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js index 13dab6efbc7..d7112ec7d3a 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseBreadcrumbs.js @@ -25,7 +25,7 @@ export default function SuspenseBreadcrumbs(): React$Node { const store = useContext(StoreContext); const treeDispatch = useContext(TreeDispatcherContext); const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); - const {selectedSuspenseID, selectedRootID, lineage} = useContext( + const {selectedSuspenseID, lineage, roots} = useContext( SuspenseTreeStateContext, ); @@ -45,13 +45,13 @@ export default function SuspenseBreadcrumbs(): React$Node { // that rendered the whole screen. In laymans terms this is really "Initial Paint". // TODO: Once we add subtree selection, then the equivalent should be called // "Transition" since in that case it's really about a Transition within the page. - selectedRootID !== null ? ( + roots.length > 0 ? (
  • + aria-current="true"> diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css index 7ad31524968..ba862051d95 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css @@ -1,5 +1,12 @@ .SuspenseRectsContainer { padding: .25rem; + cursor: pointer; + outline: 1px solid var(--color-component-name); + border-radius: 0.25rem; +} + +.SuspenseRectsContainer[data-highlighted='true'] { + background: var(--color-dimmest); } .SuspenseRectsViewBox { @@ -28,6 +35,8 @@ pointer-events: all; outline-style: solid; outline-width: 1px; + border-radius: 0.125rem; + cursor: pointer; } .SuspenseRectsScaledRect { @@ -42,7 +51,7 @@ /* highlight this boundary */ .SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect, .SuspenseRectsBoundary[data-highlighted='true'] > .SuspenseRectsRect { - background-color: var(--color-background-hover); + background-color: var(--color-background-hover); } .SuspenseRectsRect[data-highlighted='true'] { diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index 00ca3e14594..8e43944e7a7 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -278,11 +278,7 @@ function getDocumentBoundingRect( }; } -function SuspenseRectsShell({ - rootID, -}: { - rootID: SuspenseNode['id'], -}): React$Node { +function SuspenseRectsRoot({rootID}: {rootID: SuspenseNode['id']}): React$Node { const store = useContext(StoreContext); const root = store.getSuspenseByID(rootID); if (root === null) { @@ -299,6 +295,9 @@ const ViewBox = createContext((null: any)); function SuspenseRectsContainer(): React$Node { const store = useContext(StoreContext); + const {inspectedElementID} = useContext(TreeStateContext); + const treeDispatch = useContext(TreeDispatcherContext); + const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); // TODO: This relies on a full re-render of all children when the Suspense tree changes. const {roots} = useContext(SuspenseTreeStateContext); @@ -312,14 +311,38 @@ function SuspenseRectsContainer(): React$Node { const width = '100%'; const aspectRatio = `1 / ${heightScale}`; + function handleClick(event: SyntheticMouseEvent) { + if (event.defaultPrevented) { + // Already clicked on an inner rect + return; + } + if (roots.length === 0) { + // Nothing to select + return; + } + const arbitraryRootID = roots[0]; + + event.preventDefault(); + treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: arbitraryRootID}); + suspenseTreeDispatch({ + type: 'SET_SUSPENSE_LINEAGE', + payload: arbitraryRootID, + }); + } + + const isRootSelected = roots.includes(inspectedElementID); + return ( -
    +
    {roots.map(rootID => { - return ; + return ; })}
    diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css index 4668ede127d..932a23103f1 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.css @@ -37,7 +37,7 @@ padding-right: 0; } -.SuspenseScrubberBead, .SuspenseScrubberBeadSelected { +.SuspenseScrubberBead { flex: 1; height: 0.5rem; background: var(--color-background-selected); @@ -51,9 +51,11 @@ background: var(--color-background-selected); } +.SuspenseScrubberBeadTransition { + background: var(--color-component-name); +} + .SuspenseScrubberStepHighlight > .SuspenseScrubberBead, -.SuspenseScrubberStepHighlight > .SuspenseScrubberBeadSelected, -.SuspenseScrubberStep:hover > .SuspenseScrubberBead, -.SuspenseScrubberStep:hover > .SuspenseScrubberBeadSelected { +.SuspenseScrubberStep:hover > .SuspenseScrubberBead { height: 0.75rem; } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js index cbb76e41647..53d20b6467c 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseScrubber.js @@ -14,6 +14,8 @@ import {useRef} from 'react'; import styles from './SuspenseScrubber.css'; +import Tooltip from '../Components/reach-ui/tooltip'; + export default function SuspenseScrubber({ min, max, @@ -53,24 +55,38 @@ export default function SuspenseScrubber({ const steps = []; for (let index = min; index <= max; index++) { steps.push( -
    + label={ + index === min + ? // The first step in the timeline is always a Transition (Initial Paint). + // TODO: Support multiple environments. + 'Initial Paint' + : // TODO: Consider adding the name of this specific boundary if this step has only one. + 'Suspense' + }>
    -
    , + onPointerDown={handlePress.bind(null, index)} + onMouseEnter={onHoverSegment.bind(null, index)}> +
    +
    + , ); } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js index b4ed7ec1f93..f698f0ae165 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTab.js @@ -34,13 +34,13 @@ import { SuspenseTreeStateContext, } from './SuspenseTreeContext'; import {StoreContext, OptionsContext} from '../context'; -import {TreeDispatcherContext} from '../Components/TreeContext'; +import { + TreeDispatcherContext, + TreeStateContext, +} from '../Components/TreeContext'; import Button from '../Button'; import Toggle from '../Toggle'; -import typeof { - SyntheticEvent, - SyntheticPointerEvent, -} from 'react-dom-bindings/src/events/SyntheticEvent'; +import typeof {SyntheticPointerEvent} from 'react-dom-bindings/src/events/SyntheticEvent'; import SettingsModal from 'react-devtools-shared/src/devtools/views/Settings/SettingsModal'; import SettingsModalContextToggle from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContextToggle'; import {SettingsModalContextController} from 'react-devtools-shared/src/devtools/views/Settings/SettingsModalContext'; @@ -71,20 +71,14 @@ function ToggleUniqueSuspenders() { const store = useContext(StoreContext); const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); - const {selectedRootID: rootID, uniqueSuspendersOnly} = useContext( - SuspenseTreeStateContext, - ); + const {uniqueSuspendersOnly} = useContext(SuspenseTreeStateContext); function handleToggleUniqueSuspenders() { const nextUniqueSuspendersOnly = !uniqueSuspendersOnly; - const nextTimeline = - rootID === null - ? [] - : // TODO: Handle different timeline modes (e.g. random order) - store.getSuspendableDocumentOrderSuspense( - rootID, - nextUniqueSuspendersOnly, - ); + // TODO: Handle different timeline modes (e.g. random order) + const nextTimeline = store.getSuspendableDocumentOrderSuspense( + nextUniqueSuspendersOnly, + ); suspenseTreeDispatch({ type: 'SET_SUSPENSE_TIMELINE', payload: [nextTimeline, null, nextUniqueSuspendersOnly], @@ -101,55 +95,6 @@ function ToggleUniqueSuspenders() { ); } -function SelectRoot() { - const store = useContext(StoreContext); - const {roots, selectedRootID, uniqueSuspendersOnly} = useContext( - SuspenseTreeStateContext, - ); - const treeDispatch = useContext(TreeDispatcherContext); - const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); - - function handleChange(event: SyntheticEvent) { - const newRootID = +event.currentTarget.value; - // TODO: scrollIntoView both suspense rects and host instance. - const nextTimeline = store.getSuspendableDocumentOrderSuspense( - newRootID, - uniqueSuspendersOnly, - ); - suspenseTreeDispatch({ - type: 'SET_SUSPENSE_TIMELINE', - payload: [nextTimeline, newRootID, uniqueSuspendersOnly], - }); - if (nextTimeline.length > 0) { - const milestone = nextTimeline[nextTimeline.length - 1]; - treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone}); - } - } - return ( - roots.length > 0 && ( - - ) - ); -} - function ToggleTreeList({ dispatch, state, @@ -240,6 +185,18 @@ function SuspenseTab(_: {}) { treeListHorizontalFraction, } = state; + const {inspectedElementID} = useContext(TreeStateContext); + const {timeline} = useContext(SuspenseTreeStateContext); + const treeDispatch = useContext(TreeDispatcherContext); + useLayoutEffect(() => { + // If the inspected element is still null and we've loaded a timeline, we can set the initial selection. + // TODO: This tab should use its own source of truth instead so we only show suspense boundaries. + if (inspectedElementID === null && timeline.length > 0) { + const milestone = timeline[timeline.length - 1]; + treeDispatch({type: 'SELECT_ELEMENT_BY_ID', payload: milestone}); + } + }, [timeline, inspectedElementID]); + useLayoutEffect(() => { const wrapperElement = wrapperTreeRef.current; @@ -427,7 +384,6 @@ function SuspenseTab(_: {}) {
    -
    {!hideSettings && } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js index b7340da915b..30ca21476f2 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTimeline.js @@ -9,7 +9,7 @@ import * as React from 'react'; import {useContext, useEffect, useRef} from 'react'; -import {BridgeContext, StoreContext} from '../context'; +import {BridgeContext} from '../context'; import {TreeDispatcherContext} from '../Components/TreeContext'; import {useHighlightHostInstance, useScrollToHostInstance} from '../hooks'; import { @@ -23,20 +23,15 @@ import ButtonIcon from '../ButtonIcon'; function SuspenseTimelineInput() { const bridge = useContext(BridgeContext); - const store = useContext(StoreContext); const treeDispatch = useContext(TreeDispatcherContext); const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext); const {highlightHostInstance, clearHighlightHostInstance} = useHighlightHostInstance(); const scrollToHostInstance = useScrollToHostInstance(); - const { - selectedRootID: rootID, - timeline, - timelineIndex, - hoveredTimelineIndex, - playing, - } = useContext(SuspenseTreeStateContext); + const {timeline, timelineIndex, hoveredTimelineIndex, playing} = useContext( + SuspenseTreeStateContext, + ); const min = 0; const max = timeline.length > 0 ? timeline.length - 1 : 0; @@ -112,24 +107,12 @@ function SuspenseTimelineInput() { // For now we just exclude it from deps since we don't lint those anyway. function changeTimelineIndex(newIndex: number) { // Synchronize timeline index with what is resuspended. - if (rootID === null) { - return; - } - const rendererID = store.getRendererIDForElement(rootID); - if (rendererID === null) { - console.error( - `No renderer ID found for root element ${rootID} in suspense timeline.`, - ); - return; - } // We suspend everything after the current selection. The root isn't showing // anything suspended in the root. The step after that should have one less // thing suspended. I.e. the first suspense boundary should be unsuspended // when it's selected. This also lets you show everything in the last step. const suspendedSet = timeline.slice(timelineIndex + 1); bridge.send('overrideSuspenseMilestone', { - rendererID, - rootID, suspendedSet, }); if (isInitialMount.current) { @@ -164,20 +147,6 @@ function SuspenseTimelineInput() { }; }, [playing]); - if (rootID === null) { - return ( -
    No root selected.
    - ); - } - - if (!store.supportsTogglingSuspense(rootID)) { - return ( -
    - Can't step through Suspense in production apps. -
    - ); - } - if (timeline.length === 0) { return (
    @@ -226,10 +195,9 @@ function SuspenseTimelineInput() { } export default function SuspenseTimeline(): React$Node { - const {selectedRootID} = useContext(SuspenseTreeStateContext); return (
    - +
    ); } diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js index 151f454eb5e..bfe3f42bda6 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseTreeContext.js @@ -7,10 +7,7 @@ * @flow */ import type {ReactContext} from 'shared/ReactTypes'; -import type { - Element, - SuspenseNode, -} from 'react-devtools-shared/src/frontend/types'; +import type {SuspenseNode} from 'react-devtools-shared/src/frontend/types'; import type Store from '../../store'; import * as React from 'react'; @@ -27,7 +24,6 @@ import {StoreContext} from '../context'; export type SuspenseTreeState = { lineage: $ReadOnlyArray | null, roots: $ReadOnlyArray, - selectedRootID: SuspenseNode['id'] | null, selectedSuspenseID: SuspenseNode['id'] | null, timeline: $ReadOnlyArray, timelineIndex: number | -1, @@ -107,60 +103,27 @@ type Props = { children: React$Node, }; -function getDefaultRootID(store: Store): Element['id'] | null { - const designatedRootID = store.roots.find(rootID => { - const suspense = store.getSuspenseByID(rootID); - return ( - store.supportsTogglingSuspense(rootID) && - suspense !== null && - suspense.children.length > 1 - ); - }); - - return designatedRootID === undefined ? null : designatedRootID; -} - function getInitialState(store: Store): SuspenseTreeState { - let initialState: SuspenseTreeState; const uniqueSuspendersOnly = true; - const selectedRootID = getDefaultRootID(store); - // TODO: Default to nearest from inspected - if (selectedRootID === null) { - initialState = { - selectedSuspenseID: null, - lineage: null, - roots: store.roots, - selectedRootID, - timeline: [], - timelineIndex: -1, - hoveredTimelineIndex: -1, - uniqueSuspendersOnly, - playing: false, - }; - } else { - const timeline = store.getSuspendableDocumentOrderSuspense( - selectedRootID, - uniqueSuspendersOnly, - ); - const timelineIndex = timeline.length - 1; - const selectedSuspenseID = - timelineIndex === -1 ? null : timeline[timelineIndex]; - const lineage = - selectedSuspenseID !== null - ? store.getSuspenseLineage(selectedSuspenseID) - : []; - initialState = { - selectedSuspenseID, - lineage, - roots: store.roots, - selectedRootID, - timeline, - timelineIndex, - hoveredTimelineIndex: -1, - uniqueSuspendersOnly, - playing: false, - }; - } + const timeline = + store.getSuspendableDocumentOrderSuspense(uniqueSuspendersOnly); + const timelineIndex = timeline.length - 1; + const selectedSuspenseID = + timelineIndex === -1 ? null : timeline[timelineIndex]; + const lineage = + selectedSuspenseID !== null + ? store.getSuspenseLineage(selectedSuspenseID) + : []; + const initialState: SuspenseTreeState = { + selectedSuspenseID, + lineage, + roots: store.roots, + timeline, + timelineIndex, + hoveredTimelineIndex: -1, + uniqueSuspendersOnly, + playing: false, + }; return initialState; } @@ -209,23 +172,10 @@ function SuspenseTreeContextController({children}: Props): React.Node { selectedTimelineID = removedIDs.get(selectedTimelineID); } - let nextRootID = state.selectedRootID; - if (selectedTimelineID !== null && selectedTimelineID !== 0) { - nextRootID = - store.getSuspenseRootIDForSuspense(selectedTimelineID); - } - if (nextRootID === null) { - nextRootID = getDefaultRootID(store); - } - - const nextTimeline = - nextRootID === null - ? [] - : // TODO: Handle different timeline modes (e.g. random order) - store.getSuspendableDocumentOrderSuspense( - nextRootID, - state.uniqueSuspendersOnly, - ); + // TODO: Handle different timeline modes (e.g. random order) + const nextTimeline = store.getSuspendableDocumentOrderSuspense( + state.uniqueSuspendersOnly, + ); let nextTimelineIndex = selectedTimelineID === null || nextTimeline.length === 0 @@ -250,7 +200,6 @@ function SuspenseTreeContextController({children}: Props): React.Node { ...state, lineage: nextLineage, roots: store.roots, - selectedRootID: nextRootID, selectedSuspenseID, timeline: nextTimeline, timelineIndex: nextTimelineIndex, @@ -258,27 +207,21 @@ function SuspenseTreeContextController({children}: Props): React.Node { } case 'SELECT_SUSPENSE_BY_ID': { const selectedSuspenseID = action.payload; - const selectedRootID = - store.getSuspenseRootIDForSuspense(selectedSuspenseID); return { ...state, selectedSuspenseID, - selectedRootID, playing: false, // pause }; } case 'SET_SUSPENSE_LINEAGE': { const suspenseID = action.payload; const lineage = store.getSuspenseLineage(suspenseID); - const selectedRootID = - store.getSuspenseRootIDForSuspense(suspenseID); return { ...state, lineage, selectedSuspenseID: suspenseID, - selectedRootID, playing: false, // pause }; } @@ -316,8 +259,6 @@ function SuspenseTreeContextController({children}: Props): React.Node { ...state, selectedSuspenseID: nextSelectedSuspenseID, lineage: nextLineage, - selectedRootID: - nextRootID === null ? state.selectedRootID : nextRootID, timeline: nextTimeline, timelineIndex: nextMilestoneIndex, uniqueSuspendersOnly: nextUniqueSuspendersOnly, diff --git a/packages/react-devtools-shared/src/devtools/views/hooks.js b/packages/react-devtools-shared/src/devtools/views/hooks.js index a4ed2da526e..2984c2fbfcd 100644 --- a/packages/react-devtools-shared/src/devtools/views/hooks.js +++ b/packages/react-devtools-shared/src/devtools/views/hooks.js @@ -353,20 +353,44 @@ export function useHighlightHostInstance(): { const highlightHostInstance = useCallback( (id: number, scrollIntoView?: boolean = false) => { const element = store.getElementByID(id); - const rendererID = store.getRendererIDForElement(id); - if (element !== null && rendererID !== null) { + if (element !== null) { + const isRoot = element.parentID === 0; let displayName = element.displayName; if (displayName !== null && element.nameProp !== null) { displayName += ` name="${element.nameProp}"`; } - bridge.send('highlightHostInstance', { - displayName, - hideAfterTimeout: false, - id, - openBuiltinElementsPanel: false, - rendererID, - scrollIntoView: scrollIntoView, - }); + if (isRoot) { + // Inspect screen + const elements: Array<{rendererID: number, id: number}> = []; + + for (let i = 0; i < store.roots.length; i++) { + const rootID = store.roots[i]; + const rendererID = store.getRendererIDForElement(rootID); + if (rendererID === null) { + continue; + } + elements.push({rendererID, id: rootID}); + } + + bridge.send('highlightHostInstances', { + displayName, + hideAfterTimeout: false, + elements, + scrollIntoView: scrollIntoView, + }); + } else { + const rendererID = store.getRendererIDForElement(id); + if (rendererID !== null) { + bridge.send('highlightHostInstance', { + displayName, + hideAfterTimeout: false, + id, + openBuiltinElementsPanel: false, + rendererID, + scrollIntoView: scrollIntoView, + }); + } + } } }, [store, bridge], diff --git a/packages/react-devtools-shared/src/inspectedElementMutableSource.js b/packages/react-devtools-shared/src/inspectedElementMutableSource.js index ae90bd021bc..d967109163a 100644 --- a/packages/react-devtools-shared/src/inspectedElementMutableSource.js +++ b/packages/react-devtools-shared/src/inspectedElementMutableSource.js @@ -12,6 +12,7 @@ import { convertInspectedElementBackendToFrontend, hydrateHelper, inspectElement as inspectElementAPI, + inspectScreen as inspectScreenAPI, } from 'react-devtools-shared/src/backendAPI'; import {fillInPath} from 'react-devtools-shared/src/hydration'; @@ -57,21 +58,31 @@ export function inspectElement( rendererID: number, shouldListenToPauseEvents: boolean = false, ): Promise { - const {id} = element; + const {id, parentID} = element; // This could indicate that the DevTools UI has been closed and reopened. // The in-memory cache will be clear but the backend still thinks we have cached data. // In this case, we need to tell it to resend the full data. const forceFullData = !inspectedElementCache.has(id); - - return inspectElementAPI( - bridge, - forceFullData, - id, - path, - rendererID, - shouldListenToPauseEvents, - ).then((data: any) => { + const isRoot = parentID === 0; + const promisedElement = isRoot + ? inspectScreenAPI( + bridge, + forceFullData, + id, + path, + shouldListenToPauseEvents, + ) + : inspectElementAPI( + bridge, + forceFullData, + id, + path, + rendererID, + shouldListenToPauseEvents, + ); + + return promisedElement.then((data: any) => { const {type} = data; let inspectedElement; diff --git a/packages/react-devtools-shared/src/utils.js b/packages/react-devtools-shared/src/utils.js index bc8e7684505..007db77f220 100644 --- a/packages/react-devtools-shared/src/utils.js +++ b/packages/react-devtools-shared/src/utils.js @@ -262,7 +262,6 @@ export function printOperationsArray(operations: Array) { i++; // supportsProfiling i++; // supportsStrictMode i++; // hasOwnerMetadata - i++; // supportsTogglingSuspense } else { const parentID = ((operations[i]: any): number); i++; diff --git a/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js b/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js index e55f6a3c55e..020246e8a4a 100644 --- a/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js +++ b/packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js @@ -1,6 +1,8 @@ import * as React from 'react'; -const {experimental_useEffectEvent, useState, useEffect} = React; +const {useState, useEffect} = React; +const useEffectEvent = + React.useEffectEvent || React.experimental_useEffectEvent; export default function UseEffectEvent(): React.Node { return ( @@ -12,14 +14,14 @@ export default function UseEffectEvent(): React.Node { } function SingleHookCase() { - const onClick = experimental_useEffectEvent(() => {}); + const onClick = useEffectEvent(() => {}); return
    ; } function useCustomHook() { const [state, setState] = useState(); - const onClick = experimental_useEffectEvent(() => {}); + const onClick = useEffectEvent(() => {}); useEffect(() => {}); return [state, setState, onClick]; diff --git a/packages/react-devtools-timeline/src/Timeline.js b/packages/react-devtools-timeline/src/Timeline.js index 482375a46f9..f209309bb0a 100644 --- a/packages/react-devtools-timeline/src/Timeline.js +++ b/packages/react-devtools-timeline/src/Timeline.js @@ -33,8 +33,14 @@ import {TimelineSearchContextController} from './TimelineSearchContext'; import styles from './Timeline.css'; export function Timeline(_: {}): React.Node { - const {file, inMemoryTimelineData, isTimelineSupported, setFile, viewState} = - useContext(TimelineContext); + const { + file, + inMemoryTimelineData, + isPerformanceTracksSupported, + isTimelineSupported, + setFile, + viewState, + } = useContext(TimelineContext); const {didRecordCommits, isProfiling} = useContext(ProfilerContext); const ref = useRef(null); @@ -95,7 +101,11 @@ export function Timeline(_: {}): React.Node { } else if (isTimelineSupported) { content = ; } else { - content = ; + content = ( + + ); } return ( diff --git a/packages/react-devtools-timeline/src/TimelineContext.js b/packages/react-devtools-timeline/src/TimelineContext.js index 83813eb267a..7835158e989 100644 --- a/packages/react-devtools-timeline/src/TimelineContext.js +++ b/packages/react-devtools-timeline/src/TimelineContext.js @@ -31,6 +31,7 @@ import type { export type Context = { file: File | null, inMemoryTimelineData: Array | null, + isPerformanceTracksSupported: boolean, isTimelineSupported: boolean, searchInputContainerRef: RefObject, setFile: (file: File | null) => void, @@ -66,6 +67,18 @@ function TimelineContextController({children}: Props): React.Node { }, ); + const isPerformanceTracksSupported = useSyncExternalStore( + function subscribe(callback) { + store.addListener('rootSupportsPerformanceTracks', callback); + return function unsubscribe() { + store.removeListener('rootSupportsPerformanceTracks', callback); + }; + }, + function getState() { + return store.rootSupportsPerformanceTracks; + }, + ); + const inMemoryTimelineData = useSyncExternalStore | null>( function subscribe(callback) { store.profilerStore.addListener('isProcessingData', callback); @@ -135,6 +148,7 @@ function TimelineContextController({children}: Props): React.Node { () => ({ file, inMemoryTimelineData, + isPerformanceTracksSupported, isTimelineSupported, searchInputContainerRef, setFile, @@ -145,6 +159,7 @@ function TimelineContextController({children}: Props): React.Node { [ file, inMemoryTimelineData, + isPerformanceTracksSupported, isTimelineSupported, setFile, viewState, diff --git a/packages/react-devtools-timeline/src/TimelineNotSupported.js b/packages/react-devtools-timeline/src/TimelineNotSupported.js index de13a0439c3..7eac79da94f 100644 --- a/packages/react-devtools-timeline/src/TimelineNotSupported.js +++ b/packages/react-devtools-timeline/src/TimelineNotSupported.js @@ -12,16 +12,48 @@ import {isInternalFacebookBuild} from 'react-devtools-feature-flags'; import styles from './TimelineNotSupported.css'; -export default function TimelineNotSupported(): React.Node { +type Props = { + isPerformanceTracksSupported: boolean, +}; + +function PerformanceTracksSupported() { return ( -
    -
    Timeline profiling not supported.
    + <>

    - Timeline profiler requires a development or profiling build of{' '} - react-dom@^18. + Please use{' '} + + React Performance tracks + {' '} + instead of the Timeline profiler.

    + + ); +} + +function UnknownUnsupportedReason() { + return ( + <> +

    + Timeline profiler requires a development or profiling build of{' '} + react-dom@{'>='}18. +

    +

    + In React 19.2 and above{' '} + + React Performance tracks + {' '} + can be used instead. +

    + + ); +} + +export default function TimelineNotSupported({ + isPerformanceTracksSupported, +}: Props): React.Node { + return ( +
    +
    Timeline profiling not supported.
    + + {isPerformanceTracksSupported ? ( + + ) : ( + + )} {isInternalFacebookBuild && (