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 f249466431d58..7bb65fbc04d76 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 57567f325fd9e..9fa88680ca2eb 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 3f1f7b3222a35..293d9964cc448 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 c7ef86f7c222c..3c0768e7f511a 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 44d4974f6f88d..e9475a070b888 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 8d89c65f2629f..5bdeeaee1a8eb 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 6adc3056080e2..9f147194da1cd 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 e6d2c795c378a..29d6797fdd40e 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 6f13b0cd489d2..06a7ee3a37f0a 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 656a54db5c713..199284b8caa93 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 5b8824eca6b6f..407fdcb0488f2 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 a365800e3ed9d..5ed1a9157bdfd 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 c5167994cffcc..3b2768d350a68 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 4e017cf2f777f..ec10d66f26d27 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 8fe4602c89994..8592ae65e4b81 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 accabed80fc4f..0fda92c726cb0 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 d8223e2949837..8d603c629b6c2 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 f07d00854cd85..086cda353e5fc 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 371348a560643..25b11eb38c247 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 06d2945868dd1..7af080b072f32 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 0000000000000..e9772e67993f9 --- /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 0000000000000..12f8ebf3ce40f --- /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 946dd33fcbf60..fc00a96a9f37e 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 1621ab41c9050..f3d08397770a3 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 46c473a1b5e7c..be31341d15d20 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 34a9a12882188..c71723f4964f2 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 11ef34621c499..4721e015b08b8 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 b17a5a4f6a568..0b90cf45bc53f 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 76b8d466ed4e9..c7e16b3c12ba1 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 b4145b1617f81..b3a240a111c22 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 fe532231896e3..bee5b18b7e24d 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 3482887d92077..4f8c32367c197 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 9f340f19ab963..0ead4d68f582e 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 96a8017848ddd..16af7ef85d154 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 650d828bd2589..f4a416a503658 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 20b14c5c50714..4ce2caadb9a95 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 387c033d98dc7..686d95a6f5045 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 6dbdbac59c5e2..027d1e7f41eec 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 6b95dda4739eb..c7fcfd1daaef3 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 246a74db72fd2..4db8451bc855b 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 258371afec834..cd93ad9bf7a33 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 187e797f40f8f..6229cb6d3e5a9 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 4f1fb2d492991..f8e74b37dda25 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 b8540044449b9..1dd4bd710e034 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 a6d7eb64e10aa..f3ebad71dfca1 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 b9b914d30ec90..d084df5e7a191 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 83e593dbd4149..98128b1e506f1 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 c7770ffcdce2b..3190c0e7c7c47 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 78cb6697fc7ac..012aec12b3667 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 bd928634a29bf..a542514109eaa 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 0a31e02ae25e7..53a99470a9247 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 61f8c47e453d4..5d61126ee9788 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 c985809353e45..25f3728523750 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 d6bd1690f6557..c6bd016280d23 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 4f665646241fa..9cb71732a0997 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 d81c069e336ba..1e997906428ff 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 2cffd06f07b75..81c53c9016790 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 72289eb833571..cb282549f0684 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 458b75dff94a8..a51aaf5367ce9 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 d06ad11eb5753..6d2f17853163a 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 0786bac4341f6..fd7f2b0cda057 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 90b7597694607..03ca2ef583aeb 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 e33f52396d5e5..6c45eb8bfaf8f 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 08b9e4b2faa6c..f360a8213259b 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 cc65670080b64..f29be3d405b4a 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 43e2dfbb0504a..061af52723e0d 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 926887a7a448b..ce3e3734e98ce 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 5b7d799d68b13..7c4daae371f1e 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 0000000000000..84c611dec3559 --- /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 0000000000000..373fdc53fa277 --- /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 0000000000000..82c11f7783d43 --- /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 0000000000000..6b55e68bb018c --- /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 0000000000000..ab4940bcc3cef --- /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 0000000000000..820cef20cb2a1 --- /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 a30b33d8b648e..9a092e3f228e3 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 1d0ca7f1bc701..628c786550e1d 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 eafc8f16cffd8..ed2e61d8ee374 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 6c32f01b37dac..a75a3936d4c0f 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 6298ab289c142..a16ef317b09ac 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 d0c82f7866303..23d77c84ec149 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 082097cbd10f6..df6ee0495e17f 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 d7e77b77c260a..29ff926d2e052 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 92b667de08b1a..075458831b8de 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 e5e53b8420ee3..c6642be4f8933 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 c22a96d629f22..077b9aa9f6f5f 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 fef4fdfa6805b..637e0a974e6b6 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 cf24f25926959..d93c52a10c12c 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 ee09122d602fd..9a81716828c34 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 fafaad14e6489..9d35f52504e1a 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 1db0acf83985f..7c9cb48c7c1f7 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 18e0621d62669..db5ff86ed2355 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 ba0abc0d7cdf0..698fbeb2ca053 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 086ef4f58d6d4..448c2133301af 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 3ac3845c47f74..d5d36490dfbba 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 c224f1d17ec7d..58b2c9762b29b 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 8025d3680fb51..673380de119c4 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 d8a20367c9179..d208ce4fb74d1 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 c179d27b8312d..eae6a75854e06 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 d913c4f29b38e..6cf8820e98445 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 1d01c20993957..aa43a8d18867c 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 85d345fbf7dda..68dc127bc1e99 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 64135be731a20..139be81a6c3bd 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 7af6bc996a99a..4b8096f702a0f 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 c1d835d6f0f06..43a24622c5ba9 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 7fa86838e8c2a..399be048d5509 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 c22b9208966eb..a367653dfd9e7 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 9c87512a0f7c8..4d1cc124006c9 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 317491efbfe72..20a377c47497c 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 260d695e09d8a..34529a2108087 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 a96c044a3b86b..7df73db402f8b 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 1edde1af1cea7..033c7b0d98fbb 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 295bf27d3fb9f..3af11ed453caa 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 9de9b564e93a6..d5280c091c953 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 5a7d6eb836e03..fce8fe626d443 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 1a76c85902286..222baa0dae04c 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 8a245155ef2cc..46709c9a8048c 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 e002740cb69f7..1052dc9d75b14 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 155717e5d8943..6fd93d3519c87 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 f6c11fb10d66a..c332393d2aa1b 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 aa9c867e1f1f2..b6229192c23b1 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 1a8ef8caa8b14..c398e130d841b 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 6ffbb52d94e8e..99971f6a1a697 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 9e928b0231985..28be5f9e1c7e1 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 13dab6efbc72a..d7112ec7d3a60 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 7ad31524968e8..ba862051d9513 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 00ca3e1459476..8e43944e7a79d 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 4668ede127dd6..932a23103f1a3 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 cbb76e4164708..53d20b6467c46 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 b4ed7ec1f93c8..f698f0ae16549 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 b7340da915b9b..30ca21476f2b6 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 151f454eb5ef6..bfe3f42bda6d5 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 a4ed2da526e16..2984c2fbfcda7 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 ae90bd021bcb6..d967109163aa1 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 bc8e7684505f4..007db77f2202a 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 e55f6a3c55e4f..020246e8a4a71 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 482375a46f920..f209309bb0ab7 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 83813eb267a9f..7835158e98952 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 de13a0439c3df..7eac79da94f51 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 && (