From a9f14cb44e58c13843cdeacc7dc352562cb3b976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 21 Nov 2024 17:16:54 -0500 Subject: [PATCH 01/27] Fix Logging of Immediately Resolved Promises (#31610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoid re-emitting the yellow "Event" log when we ping inside the original event. Instead of treating events as repeated when we get repeated updates, we treat them as repeated if we've ever logged out this event before. Additionally, in the case the prerender sibling flag is on we need to ensure that if a render gets interrupted when it has been suspended we treat that as "Prewarm" instead of "Interrupted Render". Before: Screenshot 2024-11-19 at 2 39 44 PM After: Screenshot 2024-11-21 at 4 53 16 PM --- .../src/ReactFiberWorkLoop.js | 17 ++++++-- .../src/ReactProfilerTimer.js | 42 +++++++++---------- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 8c90c6df2b6dc..9b452020375df 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -265,7 +265,6 @@ import { startProfilerTimer, stopProfilerTimerIfRunningAndRecordDuration, stopProfilerTimerIfRunningAndRecordIncompleteDuration, - markUpdateAsRepeat, trackSuspendedTime, startYieldTimer, yieldStartTime, @@ -927,6 +926,7 @@ export function performWorkOnRoot( // We've returned from yielding to the event loop. Let's log the time it took. const yieldEndTime = now(); switch (yieldReason) { + case SuspendedOnImmediate: case SuspendedOnData: logSuspendedYieldTime(yieldStartTime, yieldEndTime, yieldedFiber); break; @@ -1009,7 +1009,6 @@ export function performWorkOnRoot( setCurrentTrackFromLanes(lanes); logInconsistentRender(renderStartTime, renderEndTime); finalizeRender(lanes, renderEndTime); - markUpdateAsRepeat(lanes); } // A store was mutated in an interleaved event. Render again, // synchronously, to block further mutations. @@ -1036,7 +1035,6 @@ export function performWorkOnRoot( setCurrentTrackFromLanes(lanes); logErroredRenderPhase(renderStartTime, renderEndTime); finalizeRender(lanes, renderEndTime); - markUpdateAsRepeat(lanes); } lanes = errorRetryLanes; exitStatus = recoverFromConcurrentError( @@ -1740,7 +1738,18 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { previousRenderStartTime > 0 ) { setCurrentTrackFromLanes(workInProgressRootRenderLanes); - logInterruptedRenderPhase(previousRenderStartTime, renderStartTime); + if ( + workInProgressRootExitStatus === RootSuspended || + workInProgressRootExitStatus === RootSuspendedWithDelay + ) { + // If the root was already suspended when it got interrupted and restarted, + // then this is considered a prewarm and not an interrupted render because + // we couldn't have shown anything anyway so it's not a bad thing that we + // got interrupted. + logSuspendedRenderPhase(previousRenderStartTime, renderStartTime); + } else { + logInterruptedRenderPhase(previousRenderStartTime, renderStartTime); + } finalizeRender(workInProgressRootRenderLanes, renderStartTime); } diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index fdd7b7827b4d7..2b20f45673e34 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -80,9 +80,12 @@ export function startUpdateTimerByLane(lane: Lane): void { blockingUpdateTime = now(); const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); - blockingEventIsRepeat = - newEventTime === blockingEventTime && - newEventType === blockingEventType; + if ( + newEventTime !== blockingEventTime || + newEventType !== blockingEventType + ) { + blockingEventIsRepeat = false; + } blockingEventTime = newEventTime; blockingEventType = newEventType; } @@ -92,9 +95,12 @@ export function startUpdateTimerByLane(lane: Lane): void { if (transitionStartTime < 0) { const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); - transitionEventIsRepeat = - newEventTime === transitionEventTime && - newEventType === transitionEventType; + if ( + newEventTime !== transitionEventTime || + newEventType !== transitionEventType + ) { + transitionEventIsRepeat = false; + } transitionEventTime = newEventTime; transitionEventType = newEventType; } @@ -102,19 +108,6 @@ export function startUpdateTimerByLane(lane: Lane): void { } } -export function markUpdateAsRepeat(lanes: Lanes): void { - if (!enableProfilerTimer || !enableComponentPerformanceTrack) { - return; - } - // We're about to do a retry of this render. It is not a new update, so treat this - // as a repeat within the same event. - if (includesSyncLane(lanes) || includesBlockingLane(lanes)) { - blockingEventIsRepeat = true; - } else if (includesTransitionLane(lanes)) { - transitionEventIsRepeat = true; - } -} - export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) { if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return; @@ -129,6 +122,7 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) { export function clearBlockingTimers(): void { blockingUpdateTime = -1.1; blockingSuspendedTime = -1.1; + blockingEventIsRepeat = true; } export function startAsyncTransitionTimer(): void { @@ -139,9 +133,12 @@ export function startAsyncTransitionTimer(): void { transitionStartTime = now(); const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); - transitionEventIsRepeat = - newEventTime === transitionEventTime && - newEventType === transitionEventType; + if ( + newEventTime !== transitionEventTime || + newEventType !== transitionEventType + ) { + transitionEventIsRepeat = false; + } transitionEventTime = newEventTime; transitionEventType = newEventType; } @@ -173,6 +170,7 @@ export function clearTransitionTimers(): void { transitionStartTime = -1.1; transitionUpdateTime = -1.1; transitionSuspendedTime = -1.1; + transitionEventIsRepeat = true; } export function clampBlockingTimers(finalTime: number): void { From 91061073d57783c061889ac6720ef1ab7f0c2149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 21 Nov 2024 19:36:38 -0500 Subject: [PATCH 02/27] Mark ping time as update (#31611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures that we mark the time from ping until we render as "Blocked". We intentionally don't want to show the event time even if it's something like "load" because it draws attention away from interactions etc. Screenshot 2024-11-21 at 7 22 39 PM --- .../react-reconciler/src/ReactFiberWorkLoop.js | 5 +++++ .../react-reconciler/src/ReactProfilerTimer.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 9b452020375df..2ff35b0c6a7f2 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -269,6 +269,7 @@ import { startYieldTimer, yieldStartTime, yieldReason, + startPingTimerByLanes, } from './ReactProfilerTimer'; import {setCurrentTrackFromLanes} from './ReactFiberPerformanceTrack'; @@ -3961,6 +3962,10 @@ function pingSuspendedRoot( markRootPinged(root, pingedLanes); + if (enableProfilerTimer && enableComponentPerformanceTrack) { + startPingTimerByLanes(pingedLanes); + } + warnIfSuspenseResolutionNotWrappedWithActDEV(root); if ( diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 2b20f45673e34..d408ba0ff7bd0 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -108,6 +108,24 @@ export function startUpdateTimerByLane(lane: Lane): void { } } +export function startPingTimerByLanes(lanes: Lanes): void { + if (!enableProfilerTimer || !enableComponentPerformanceTrack) { + return; + } + // Mark the update time and clamp anything before it because we don't want + // to show the event time for pings but we also don't want to clear it + // because we still need to track if this was a repeat. + if (includesSyncLane(lanes) || includesBlockingLane(lanes)) { + if (blockingUpdateTime < 0) { + blockingClampTime = blockingUpdateTime = now(); + } + } else if (includesTransitionLane(lanes)) { + if (transitionUpdateTime < 0) { + transitionClampTime = transitionUpdateTime = now(); + } + } +} + export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) { if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return; From e697386c10d837017de9516b9252b717fbb60924 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 22 Nov 2024 12:15:13 -0500 Subject: [PATCH 03/27] [compiler] First cut at dep inference (#31386) This is for researching/prototyping, not a feature we are releasing imminently. Putting up an early version of inferring effect dependencies to get feedback on the approach. We do not plan to ship this as-is, and may not start by going after direct `useEffect` calls. Until we make that decision, the heuristic I use to detect when to insert effect deps will suffice for testing. The approach is simple: when we see a useEffect call with no dep array we insert the deps inferred for the lambda passed in. If the first argument is not a lambda then we do not do anything. This diff is the easy part. I think the harder part will be ensuring that we can infer the deps even when we have to bail out of memoization. We have no other features that *must* run regardless of rules of react violations. Does anyone foresee any issues using the compiler passes to infer reactive deps when there may be violations? I have a few questions: 1. Will there ever be more than one instruction in a block containing a useEffect? if no, I can get rid of the`addedInstrs` variable that I use to make sure I insert the effect deps array temp creation at the right spot. 2. Are there any cases for resolving the first argument beyond just looking at the lvalue's identifier id that I'll need to take into account? e.g., do I need to recursively resolve certain bindings? --------- Co-authored-by: Mofei Zhang --- .../src/Entrypoint/Pipeline.ts | 5 + .../src/HIR/Environment.ts | 13 + .../src/Inference/InferEffectDependencies.ts | 247 ++++++++++++++++++ .../src/Inference/index.ts | 1 + .../infer-effect-dependencies.expect.md | 129 +++++++++ .../compiler/infer-effect-dependencies.js | 36 +++ .../compiler/nonreactive-dep.expect.md | 80 ++++++ .../fixtures/compiler/nonreactive-dep.js | 25 ++ .../compiler/nonreactive-ref.expect.md | 51 ++++ .../fixtures/compiler/nonreactive-ref.js | 14 + .../compiler/outlined-function.expect.md | 46 ++++ .../fixtures/compiler/outlined-function.js | 14 + .../compiler/pruned-nonreactive-obj.expect.md | 119 +++++++++ .../compiler/pruned-nonreactive-obj.js | 48 ++++ .../reactive-memberexpr-merge.expect.md | 49 ++++ .../compiler/reactive-memberexpr-merge.js | 8 + .../compiler/reactive-memberexpr.expect.md | 49 ++++ .../fixtures/compiler/reactive-memberexpr.js | 8 + .../reactive-optional-chain.expect.md | 60 +++++ .../compiler/reactive-optional-chain.js | 9 + .../compiler/reactive-variable.expect.md | 49 ++++ .../fixtures/compiler/reactive-variable.js | 8 + .../todo-import-namespace-useEffect.expect.md | 50 ++++ .../todo-import-namespace-useEffect.js | 10 + compiler/packages/snap/src/compiler.ts | 6 + 25 files changed, 1134 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 1127e91029328..bbd076e46a914 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -36,6 +36,7 @@ import { inferReactivePlaces, inferReferenceEffects, inlineImmediatelyInvokedFunctionExpressions, + inferEffectDependencies, } from '../Inference'; import { constantPropagation, @@ -354,6 +355,10 @@ function* runWithEnvironment( value: hir, }); + if (env.config.inferEffectDependencies) { + inferEffectDependencies(env, hir); + } + if (env.config.inlineJsxTransform) { inlineJsxTransform(hir, env.config.inlineJsxTransform); yield log({ 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 855bc039abf37..432eaf96ff99b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -233,6 +233,19 @@ const EnvironmentConfigSchema = z.object({ enableFunctionDependencyRewrite: z.boolean().default(true), + /** + * Enables inference of optional dependency chains. Without this flag + * a property chain such as `props?.items?.foo` will infer as a dep on + * just `props`. With this flag enabled, we'll infer that full path as + * the dependency. + */ + enableOptionalDependencies: z.boolean().default(true), + + /** + * Enables inference and auto-insertion of effect dependencies. Still experimental. + */ + inferEffectDependencies: z.boolean().default(false), + /** * Enables inlining ReactElement object literals in place of JSX * An alternative to the standard JSX transform which replaces JSX with React's jsxProd() runtime diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts new file mode 100644 index 0000000000000..9dc7dff78a6af --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -0,0 +1,247 @@ +import {CompilerError, SourceLocation} from '..'; +import { + ArrayExpression, + Effect, + Environment, + FunctionExpression, + GeneratedSource, + HIRFunction, + IdentifierId, + Instruction, + isUseEffectHookType, + makeInstructionId, + TInstruction, + InstructionId, + ScopeId, + ReactiveScopeDependency, + Place, + ReactiveScopeDependencies, +} from '../HIR'; +import { + createTemporaryPlace, + fixScopeAndIdentifierRanges, + markInstructionIds, +} from '../HIR/HIRBuilder'; +import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; + +/** + * Infers reactive dependencies captured by useEffect lambdas and adds them as + * a second argument to the useEffect call if no dependency array is provided. + */ +export function inferEffectDependencies( + env: Environment, + fn: HIRFunction, +): void { + let hasRewrite = false; + const fnExpressions = new Map< + IdentifierId, + TInstruction + >(); + const scopeInfos = new Map< + ScopeId, + {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean} + >(); + + /** + * When inserting LoadLocals, we need to retain the reactivity of the base + * identifier, as later passes e.g. PruneNonReactiveDeps take the reactivity of + * a base identifier as the "maximal" reactivity of all its references. + * Concretely, + * reactive(Identifier i) = Union_{reference of i}(reactive(reference)) + */ + const reactiveIds = inferReactiveIdentifiers(fn); + + for (const [, block] of fn.body.blocks) { + if ( + block.terminal.kind === 'scope' || + block.terminal.kind === 'pruned-scope' + ) { + const scopeBlock = fn.body.blocks.get(block.terminal.block)!; + scopeInfos.set(block.terminal.scope.id, { + pruned: block.terminal.kind === 'pruned-scope', + deps: block.terminal.scope.dependencies, + hasSingleInstr: + scopeBlock.instructions.length === 1 && + scopeBlock.terminal.kind === 'goto' && + scopeBlock.terminal.block === block.terminal.fallthrough, + }); + } + const rewriteInstrs = new Map>(); + for (const instr of block.instructions) { + const {value, lvalue} = instr; + if (value.kind === 'FunctionExpression') { + fnExpressions.set( + lvalue.identifier.id, + instr as TInstruction, + ); + } else if ( + /* + * This check is not final. Right now we only look for useEffects without a dependency array. + * This is likely not how we will ship this feature, but it is good enough for us to make progress + * on the implementation and test it. + */ + value.kind === 'CallExpression' && + isUseEffectHookType(value.callee.identifier) && + value.args.length === 1 && + value.args[0].kind === 'Identifier' + ) { + const fnExpr = fnExpressions.get(value.args[0].identifier.id); + if (fnExpr != null) { + const scopeInfo = + fnExpr.lvalue.identifier.scope != null + ? scopeInfos.get(fnExpr.lvalue.identifier.scope.id) + : null; + CompilerError.invariant(scopeInfo != null, { + reason: 'Expected function expression scope to exist', + loc: value.loc, + }); + if (scopeInfo.pruned || !scopeInfo.hasSingleInstr) { + /** + * TODO: retry pipeline that ensures effect function expressions + * are placed into their own scope + */ + CompilerError.throwTodo({ + reason: + '[InferEffectDependencies] Expected effect function to have non-pruned scope and its scope to have exactly one instruction', + loc: fnExpr.loc, + }); + } + + /** + * Step 1: write new instructions to insert a dependency array + * + * Note that it's invalid to prune non-reactive deps in this pass, see + * the `infer-effect-deps/pruned-nonreactive-obj` fixture for an + * explanation. + */ + const effectDeps: Array = []; + const newInstructions: Array = []; + for (const dep of scopeInfo.deps) { + const {place, instructions} = writeDependencyToInstructions( + dep, + reactiveIds.has(dep.identifier.id), + fn.env, + fnExpr.loc, + ); + newInstructions.push(...instructions); + effectDeps.push(place); + } + const deps: ArrayExpression = { + kind: 'ArrayExpression', + elements: effectDeps, + loc: GeneratedSource, + }; + + const depsPlace = createTemporaryPlace(env, GeneratedSource); + depsPlace.effect = Effect.Read; + + newInstructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...depsPlace, effect: Effect.Mutate}, + value: deps, + }); + + // Step 2: insert the deps array as an argument of the useEffect + value.args[1] = {...depsPlace, effect: Effect.Freeze}; + rewriteInstrs.set(instr.id, newInstructions); + } + } + } + if (rewriteInstrs.size > 0) { + hasRewrite = true; + const newInstrs = []; + for (const instr of block.instructions) { + const newInstr = rewriteInstrs.get(instr.id); + if (newInstr != null) { + newInstrs.push(...newInstr, instr); + } else { + newInstrs.push(instr); + } + } + block.instructions = newInstrs; + } + } + if (hasRewrite) { + // Renumber instructions and fix scope ranges + markInstructionIds(fn.body); + fixScopeAndIdentifierRanges(fn.body); + } +} + +function writeDependencyToInstructions( + dep: ReactiveScopeDependency, + reactive: boolean, + env: Environment, + loc: SourceLocation, +): {place: Place; instructions: Array} { + const instructions: Array = []; + let currValue = createTemporaryPlace(env, GeneratedSource); + currValue.reactive = reactive; + instructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...currValue, effect: Effect.Mutate}, + value: { + kind: 'LoadLocal', + place: { + kind: 'Identifier', + identifier: dep.identifier, + effect: Effect.Capture, + reactive, + loc: loc, + }, + loc: loc, + }, + }); + for (const path of dep.path) { + if (path.optional) { + /** + * TODO: instead of truncating optional paths, reuse + * instructions from hoisted dependencies block(s) + */ + break; + } + const nextValue = createTemporaryPlace(env, GeneratedSource); + nextValue.reactive = reactive; + instructions.push({ + id: makeInstructionId(0), + loc: GeneratedSource, + lvalue: {...nextValue, effect: Effect.Mutate}, + value: { + kind: 'PropertyLoad', + object: {...currValue, effect: Effect.Capture}, + property: path.property, + loc: loc, + }, + }); + currValue = nextValue; + } + currValue.effect = Effect.Freeze; + return {place: currValue, instructions}; +} + +function inferReactiveIdentifiers(fn: HIRFunction): Set { + const reactiveIds: Set = new Set(); + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + /** + * No need to traverse into nested functions as + * 1. their effects are recorded in `LoweredFunction.dependencies` + * 2. we don't mark `reactive` in these anyways + */ + for (const place of eachInstructionOperand(instr)) { + if (place.reactive) { + reactiveIds.add(place.identifier.id); + } + } + } + + for (const place of eachTerminalOperand(block.terminal)) { + if (place.reactive) { + reactiveIds.add(place.identifier.id); + } + } + } + return reactiveIds; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts index ee76a37bcb4b7..93b99fb385262 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/index.ts @@ -11,3 +11,4 @@ export {inferMutableRanges} from './InferMutableRanges'; export {inferReactivePlaces} from './InferReactivePlaces'; export {default as inferReferenceEffects} from './InferReferenceEffects'; export {inlineImmediatelyInvokedFunctionExpressions} from './InlineImmediatelyInvokedFunctionExpressions'; +export {inferEffectDependencies} from './InferEffectDependencies'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md new file mode 100644 index 0000000000000..febdd005a3cb0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md @@ -0,0 +1,129 @@ + +## Input + +```javascript +// @inferEffectDependencies +const moduleNonReactive = 0; + +function Component({foo, bar}) { + const localNonreactive = 0; + const ref = useRef(0); + const localNonPrimitiveReactive = { + foo, + }; + const localNonPrimitiveNonreactive = {}; + useEffect(() => { + console.log(foo); + console.log(bar); + console.log(moduleNonReactive); + console.log(localNonreactive); + console.log(globalValue); + console.log(ref.current); + console.log(localNonPrimitiveReactive); + console.log(localNonPrimitiveNonreactive); + }); + + // Optional chains and property accesses + // TODO: we may be able to save bytes by omitting property accesses if the + // object of the member expression is already included in the inferred deps + useEffect(() => { + console.log(bar?.baz); + console.log(bar.qux); + }); + + function f() { + console.log(foo); + } + + // No inferred dep array, the argument is not a lambda + useEffect(f); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +const moduleNonReactive = 0; + +function Component(t0) { + const $ = _c(12); + const { foo, bar } = t0; + + const ref = useRef(0); + let t1; + if ($[0] !== foo) { + t1 = { foo }; + $[0] = foo; + $[1] = t1; + } else { + t1 = $[1]; + } + const localNonPrimitiveReactive = t1; + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = {}; + $[2] = t2; + } else { + t2 = $[2]; + } + const localNonPrimitiveNonreactive = t2; + let t3; + if ($[3] !== bar || $[4] !== foo || $[5] !== localNonPrimitiveReactive) { + t3 = () => { + console.log(foo); + console.log(bar); + console.log(moduleNonReactive); + console.log(0); + console.log(globalValue); + console.log(ref.current); + console.log(localNonPrimitiveReactive); + console.log(localNonPrimitiveNonreactive); + }; + $[3] = bar; + $[4] = foo; + $[5] = localNonPrimitiveReactive; + $[6] = t3; + } else { + t3 = $[6]; + } + useEffect(t3, [ + foo, + bar, + ref, + localNonPrimitiveReactive, + localNonPrimitiveNonreactive, + ]); + let t4; + if ($[7] !== bar.baz || $[8] !== bar.qux) { + t4 = () => { + console.log(bar?.baz); + console.log(bar.qux); + }; + $[7] = bar.baz; + $[8] = bar.qux; + $[9] = t4; + } else { + t4 = $[9]; + } + useEffect(t4, [bar.baz, bar.qux]); + let t5; + if ($[10] !== foo) { + t5 = function f() { + console.log(foo); + }; + $[10] = foo; + $[11] = t5; + } else { + t5 = $[11]; + } + const f = t5; + + useEffect(f); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js new file mode 100644 index 0000000000000..6a70bc1298b0a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js @@ -0,0 +1,36 @@ +// @inferEffectDependencies +const moduleNonReactive = 0; + +function Component({foo, bar}) { + const localNonreactive = 0; + const ref = useRef(0); + const localNonPrimitiveReactive = { + foo, + }; + const localNonPrimitiveNonreactive = {}; + useEffect(() => { + console.log(foo); + console.log(bar); + console.log(moduleNonReactive); + console.log(localNonreactive); + console.log(globalValue); + console.log(ref.current); + console.log(localNonPrimitiveReactive); + console.log(localNonPrimitiveNonreactive); + }); + + // Optional chains and property accesses + // TODO: we may be able to save bytes by omitting property accesses if the + // object of the member expression is already included in the inferred deps + useEffect(() => { + console.log(bar?.baz); + console.log(bar.qux); + }); + + function f() { + console.log(foo); + } + + // No inferred dep array, the argument is not a lambda + useEffect(f); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md new file mode 100644 index 0000000000000..1cfbe71e78843 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.expect.md @@ -0,0 +1,80 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {makeObject_Primitives, print} from 'shared-runtime'; + +/** + * Note that `obj` is currently added to the effect dependency array, even + * though it's non-reactive due to memoization. + * + * This is a TODO in effect dependency inference. Note that we cannot simply + * filter out non-reactive effect dependencies, as some non-reactive (by data + * flow) values become reactive due to scope pruning. See the + * `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters. + * + * Realizing that this `useEffect` should have an empty dependency array + * requires effect dependency inference to be structured similarly to memo + * dependency inference. + * Pass 1: add all potential dependencies regardless of dataflow reactivity + * Pass 2: (todo) prune non-reactive dependencies + * + * Note that instruction reordering should significantly reduce scope pruning + */ +function NonReactiveDepInEffect() { + const obj = makeObject_Primitives(); + useEffect(() => print(obj)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { makeObject_Primitives, print } from "shared-runtime"; + +/** + * Note that `obj` is currently added to the effect dependency array, even + * though it's non-reactive due to memoization. + * + * This is a TODO in effect dependency inference. Note that we cannot simply + * filter out non-reactive effect dependencies, as some non-reactive (by data + * flow) values become reactive due to scope pruning. See the + * `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters. + * + * Realizing that this `useEffect` should have an empty dependency array + * requires effect dependency inference to be structured similarly to memo + * dependency inference. + * Pass 1: add all potential dependencies regardless of dataflow reactivity + * Pass 2: (todo) prune non-reactive dependencies + * + * Note that instruction reordering should significantly reduce scope pruning + */ +function NonReactiveDepInEffect() { + const $ = _c(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = makeObject_Primitives(); + $[0] = t0; + } else { + t0 = $[0]; + } + const obj = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => print(obj); + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1, [obj]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js new file mode 100644 index 0000000000000..85d9699750293 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-dep.js @@ -0,0 +1,25 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {makeObject_Primitives, print} from 'shared-runtime'; + +/** + * Note that `obj` is currently added to the effect dependency array, even + * though it's non-reactive due to memoization. + * + * This is a TODO in effect dependency inference. Note that we cannot simply + * filter out non-reactive effect dependencies, as some non-reactive (by data + * flow) values become reactive due to scope pruning. See the + * `infer-effect-deps/pruned-nonreactive-obj` fixture for why this matters. + * + * Realizing that this `useEffect` should have an empty dependency array + * requires effect dependency inference to be structured similarly to memo + * dependency inference. + * Pass 1: add all potential dependencies regardless of dataflow reactivity + * Pass 2: (todo) prune non-reactive dependencies + * + * Note that instruction reordering should significantly reduce scope pruning + */ +function NonReactiveDepInEffect() { + const obj = makeObject_Primitives(); + useEffect(() => print(obj)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md new file mode 100644 index 0000000000000..cf1ebd9252f6a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.expect.md @@ -0,0 +1,51 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect, useRef} from 'react'; +import {print} from 'shared-runtime'; + +/** + * Special case of `infer-effect-deps/nonreactive-dep`. + * + * We know that local `useRef` return values are stable, regardless of + * inferred memoization. + */ +function NonReactiveRefInEffect() { + const ref = useRef('initial value'); + useEffect(() => print(ref.current)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect, useRef } from "react"; +import { print } from "shared-runtime"; + +/** + * Special case of `infer-effect-deps/nonreactive-dep`. + * + * We know that local `useRef` return values are stable, regardless of + * inferred memoization. + */ +function NonReactiveRefInEffect() { + const $ = _c(1); + const ref = useRef("initial value"); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = () => print(ref.current); + $[0] = t0; + } else { + t0 = $[0]; + } + useEffect(t0, [ref]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js new file mode 100644 index 0000000000000..8a8ab2f636be7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/nonreactive-ref.js @@ -0,0 +1,14 @@ +// @inferEffectDependencies +import {useEffect, useRef} from 'react'; +import {print} from 'shared-runtime'; + +/** + * Special case of `infer-effect-deps/nonreactive-dep`. + * + * We know that local `useRef` return values are stable, regardless of + * inferred memoization. + */ +function NonReactiveRefInEffect() { + const ref = useRef('initial value'); + useEffect(() => print(ref.current)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md new file mode 100644 index 0000000000000..8272d4793fb8a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.expect.md @@ -0,0 +1,46 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; +/** + * This compiled output is technically incorrect but this is currently the same + * case as a bailout (an effect that overfires). + * + * To ensure an empty deps array is passed, we need special case + * `InferEffectDependencies` for outlined functions (likely easier) or run it + * before OutlineFunctions + */ +function OutlinedFunctionInEffect() { + useEffect(() => print('hello world!')); +} + +``` + +## Code + +```javascript +// @inferEffectDependencies +import { useEffect } from "react"; +import { print } from "shared-runtime"; +/** + * This compiled output is technically incorrect but this is currently the same + * case as a bailout (an effect that overfires). + * + * To ensure an empty deps array is passed, we need special case + * `InferEffectDependencies` for outlined functions (likely easier) or run it + * before OutlineFunctions + */ +function OutlinedFunctionInEffect() { + useEffect(_temp); +} +function _temp() { + return print("hello world!"); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js new file mode 100644 index 0000000000000..2ee2c94e3c249 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/outlined-function.js @@ -0,0 +1,14 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; +/** + * This compiled output is technically incorrect but this is currently the same + * case as a bailout (an effect that overfires). + * + * To ensure an empty deps array is passed, we need special case + * `InferEffectDependencies` for outlined functions (likely easier) or run it + * before OutlineFunctions + */ +function OutlinedFunctionInEffect() { + useEffect(() => print('hello world!')); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md new file mode 100644 index 0000000000000..fbb88fb32801d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.expect.md @@ -0,0 +1,119 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useIdentity, mutate, makeObject} from 'shared-runtime'; +import {useEffect} from 'react'; + +/** + * When a semantically non-reactive value has a pruned scope (i.e. the object + * identity becomes reactive, but the underlying value it represents should be + * constant), the compiler can choose to either + * - add it as a dependency (and rerun the effect) + * - not add it as a dependency + * + * We keep semantically non-reactive values in both memo block and effect + * dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`. + * ```js + * function Component() { + * // obj is semantically non-reactive, but its memo scope is pruned due to + * // the interleaving hook call + * const obj = {}; + * useHook(); + * write(obj); + * + * const ref = useRef(); + * + * // this effect needs to be rerun when obj's referential identity changes, + * // because it might alias obj to a useRef / mutable store. + * useEffect(() => ref.current = obj, ???); + * + * // in a custom hook (or child component), the user might expect versioning + * // invariants to hold + * useHook(ref, obj); + * } + * + * // defined elsewhere + * function useHook(someRef, obj) { + * useEffect( + * () => assert(someRef.current === obj), + * [someRef, obj] + * ); + * } + * ``` + */ +function PrunedNonReactive() { + const obj = makeObject(); + useIdentity(null); + mutate(obj); + + useEffect(() => print(obj.value)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useIdentity, mutate, makeObject } from "shared-runtime"; +import { useEffect } from "react"; + +/** + * When a semantically non-reactive value has a pruned scope (i.e. the object + * identity becomes reactive, but the underlying value it represents should be + * constant), the compiler can choose to either + * - add it as a dependency (and rerun the effect) + * - not add it as a dependency + * + * We keep semantically non-reactive values in both memo block and effect + * dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`. + * ```js + * function Component() { + * // obj is semantically non-reactive, but its memo scope is pruned due to + * // the interleaving hook call + * const obj = {}; + * useHook(); + * write(obj); + * + * const ref = useRef(); + * + * // this effect needs to be rerun when obj's referential identity changes, + * // because it might alias obj to a useRef / mutable store. + * useEffect(() => ref.current = obj, ???); + * + * // in a custom hook (or child component), the user might expect versioning + * // invariants to hold + * useHook(ref, obj); + * } + * + * // defined elsewhere + * function useHook(someRef, obj) { + * useEffect( + * () => assert(someRef.current === obj), + * [someRef, obj] + * ); + * } + * ``` + */ +function PrunedNonReactive() { + const $ = _c(2); + const obj = makeObject(); + useIdentity(null); + mutate(obj); + let t0; + if ($[0] !== obj.value) { + t0 = () => print(obj.value); + $[0] = obj.value; + $[1] = t0; + } else { + t0 = $[1]; + } + useEffect(t0, [obj.value]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js new file mode 100644 index 0000000000000..692b97b5144b3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/pruned-nonreactive-obj.js @@ -0,0 +1,48 @@ +// @inferEffectDependencies +import {useIdentity, mutate, makeObject} from 'shared-runtime'; +import {useEffect} from 'react'; + +/** + * When a semantically non-reactive value has a pruned scope (i.e. the object + * identity becomes reactive, but the underlying value it represents should be + * constant), the compiler can choose to either + * - add it as a dependency (and rerun the effect) + * - not add it as a dependency + * + * We keep semantically non-reactive values in both memo block and effect + * dependency arrays to avoid versioning invariants e.g. `x !== y.aliasedX`. + * ```js + * function Component() { + * // obj is semantically non-reactive, but its memo scope is pruned due to + * // the interleaving hook call + * const obj = {}; + * useHook(); + * write(obj); + * + * const ref = useRef(); + * + * // this effect needs to be rerun when obj's referential identity changes, + * // because it might alias obj to a useRef / mutable store. + * useEffect(() => ref.current = obj, ???); + * + * // in a custom hook (or child component), the user might expect versioning + * // invariants to hold + * useHook(ref, obj); + * } + * + * // defined elsewhere + * function useHook(someRef, obj) { + * useEffect( + * () => assert(someRef.current === obj), + * [someRef, obj] + * ); + * } + * ``` + */ +function PrunedNonReactive() { + const obj = makeObject(); + useIdentity(null); + mutate(obj); + + useEffect(() => print(obj.value)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md new file mode 100644 index 0000000000000..82eb54a908829 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveMemberExprMerge({propVal}) { + const obj = {a: {b: propVal}}; + useEffect(() => print(obj.a, obj.a.b)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function ReactiveMemberExprMerge(t0) { + const $ = _c(4); + const { propVal } = t0; + let t1; + if ($[0] !== propVal) { + t1 = { a: { b: propVal } }; + $[0] = propVal; + $[1] = t1; + } else { + t1 = $[1]; + } + const obj = t1; + let t2; + if ($[2] !== obj.a) { + t2 = () => print(obj.a, obj.a.b); + $[2] = obj.a; + $[3] = t2; + } else { + t2 = $[3]; + } + useEffect(t2, [obj.a]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js new file mode 100644 index 0000000000000..071f5abbf545c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr-merge.js @@ -0,0 +1,8 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveMemberExprMerge({propVal}) { + const obj = {a: {b: propVal}}; + useEffect(() => print(obj.a, obj.a.b)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md new file mode 100644 index 0000000000000..74c4b9eb1ee20 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveMemberExpr({propVal}) { + const obj = {a: {b: propVal}}; + useEffect(() => print(obj.a.b)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function ReactiveMemberExpr(t0) { + const $ = _c(4); + const { propVal } = t0; + let t1; + if ($[0] !== propVal) { + t1 = { a: { b: propVal } }; + $[0] = propVal; + $[1] = t1; + } else { + t1 = $[1]; + } + const obj = t1; + let t2; + if ($[2] !== obj.a.b) { + t2 = () => print(obj.a.b); + $[2] = obj.a.b; + $[3] = t2; + } else { + t2 = $[3]; + } + useEffect(t2, [obj.a.b]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js new file mode 100644 index 0000000000000..0eabc54657339 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-memberexpr.js @@ -0,0 +1,8 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveMemberExpr({propVal}) { + const obj = {a: {b: propVal}}; + useEffect(() => print(obj.a.b)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md new file mode 100644 index 0000000000000..7c9f21b85cdab --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.expect.md @@ -0,0 +1,60 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null}; + useEffect(() => print(obj.a?.b)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr(t0) { + const $ = _c(7); + const { cond, propVal } = t0; + let t1; + if ($[0] !== cond || $[1] !== propVal) { + t1 = cond ? { b: propVal } : null; + $[0] = cond; + $[1] = propVal; + $[2] = t1; + } else { + t1 = $[2]; + } + let t2; + if ($[3] !== t1) { + t2 = { a: t1 }; + $[3] = t1; + $[4] = t2; + } else { + t2 = $[4]; + } + const obj = t2; + let t3; + if ($[5] !== obj.a?.b) { + t3 = () => print(obj.a?.b); + $[5] = obj.a?.b; + $[6] = t3; + } else { + t3 = $[6]; + } + useEffect(t3, [obj.a]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js new file mode 100644 index 0000000000000..8a76784e241b5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-optional-chain.js @@ -0,0 +1,9 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +// TODO: take optional chains as dependencies +function ReactiveMemberExpr({cond, propVal}) { + const obj = {a: cond ? {b: propVal} : null}; + useEffect(() => print(obj.a?.b)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md new file mode 100644 index 0000000000000..6443b90e4affa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + const arr = [propVal]; + useEffect(() => print(arr)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect } from "react"; +import { print } from "shared-runtime"; + +function ReactiveVariable(t0) { + const $ = _c(4); + const { propVal } = t0; + let t1; + if ($[0] !== propVal) { + t1 = [propVal]; + $[0] = propVal; + $[1] = t1; + } else { + t1 = $[1]; + } + const arr = t1; + let t2; + if ($[2] !== arr) { + t2 = () => print(arr); + $[2] = arr; + $[3] = t2; + } else { + t2 = $[3]; + } + useEffect(t2, [arr]); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js new file mode 100644 index 0000000000000..ae3ee2c8e2f64 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-variable.js @@ -0,0 +1,8 @@ +// @inferEffectDependencies +import {useEffect} from 'react'; +import {print} from 'shared-runtime'; + +function ReactiveVariable({propVal}) { + const arr = [propVal]; + useEffect(() => print(arr)); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md new file mode 100644 index 0000000000000..6302067a5a5eb --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +// @inferEffectDependencies +import * as React from 'react'; + +/** + * TODO: recognize import namespace + */ +function NonReactiveDepInEffect() { + const obj = makeObject_Primitives(); + React.useEffect(() => print(obj)); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import * as React from "react"; + +/** + * TODO: recognize import namespace + */ +function NonReactiveDepInEffect() { + const $ = _c(2); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = makeObject_Primitives(); + $[0] = t0; + } else { + t0 = $[0]; + } + const obj = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => print(obj); + $[1] = t1; + } else { + t1 = $[1]; + } + React.useEffect(t1); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js new file mode 100644 index 0000000000000..4c9eec898614f --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-import-namespace-useEffect.js @@ -0,0 +1,10 @@ +// @inferEffectDependencies +import * as React from 'react'; + +/** + * TODO: recognize import namespace + */ +function NonReactiveDepInEffect() { + const obj = makeObject_Primitives(); + React.useEffect(() => print(obj)); +} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 95af40d62a880..6942ffb2adc7b 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -174,6 +174,11 @@ function makePluginOptions( .filter(s => s.length > 0); } + let inferEffectDependencies = false; + if (firstLine.includes('@inferEffectDependencies')) { + inferEffectDependencies = true; + } + let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -197,6 +202,7 @@ function makePluginOptions( hookPattern, validatePreserveExistingMemoizationGuarantees, validateBlocklistedImports, + inferEffectDependencies, }, compilationMode, logger, From 1345c37941d4e2e29034bb0fc0cfb6d01bb1d841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 22 Nov 2024 13:04:05 -0500 Subject: [PATCH 04/27] Mark all lanes in order on every new render (#31615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a hack that ensures that all four lanes as visible whether you have any tracks in them or not, and that they're in the priority order within the Scheduler track group. We do want to show all even if they're not used because it shows what options you're missing out on. Screenshot 2024-11-22 at 12 38 30 PM In Chrome, the order of tracks within a group are determined by the earliest start time. We add fake markers at start time zero in that order eagerly. Ideally we could do this only once but because calls that aren't recorded aren't considered for ordering purposes, we need to keep adding these over and over again in case recording has just started. We can't tell when recording starts. Currently performance.mark() are in first insertion order but performance.measure() are in the reverse order. I'm not sure that's intentional. We can always add the 0 time slot even if it's in the past. That's still considered for ordering purposes as long as the measurement is recorded at the time we call it. --- .../src/ReactFiberPerformanceTrack.js | 59 ++++++++++++++++++- .../src/ReactFiberWorkLoop.js | 12 +++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 04e586314293d..56470bb0e55d6 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -25,7 +25,6 @@ const COMPONENTS_TRACK = 'Components ⚛'; // Reused to avoid thrashing the GC. const reusableComponentDevToolDetails = { - dataType: 'track-entry', color: 'primary', track: COMPONENTS_TRACK, }; @@ -40,7 +39,6 @@ const reusableComponentOptions = { const LANES_TRACK_GROUP = 'Scheduler ⚛'; const reusableLaneDevToolDetails = { - dataType: 'track-entry', color: 'primary', track: 'Blocking', // Lane trackGroup: LANES_TRACK_GROUP, @@ -57,6 +55,63 @@ export function setCurrentTrackFromLanes(lanes: number): void { reusableLaneDevToolDetails.track = getGroupNameOfHighestPriorityLane(lanes); } +const blockingLaneMarker = { + startTime: 0, + detail: { + devtools: { + color: 'primary-light', + track: 'Blocking', + trackGroup: LANES_TRACK_GROUP, + }, + }, +}; + +const transitionLaneMarker = { + startTime: 0, + detail: { + devtools: { + color: 'primary-light', + track: 'Transition', + trackGroup: LANES_TRACK_GROUP, + }, + }, +}; + +const suspenseLaneMarker = { + startTime: 0, + detail: { + devtools: { + color: 'primary-light', + track: 'Suspense', + trackGroup: LANES_TRACK_GROUP, + }, + }, +}; + +const idleLaneMarker = { + startTime: 0, + detail: { + devtools: { + color: 'primary-light', + track: 'Idle', + trackGroup: LANES_TRACK_GROUP, + }, + }, +}; + +export function markAllLanesInOrder() { + if (supportsUserTiming) { + // Ensure we create all tracks in priority order. Currently performance.mark() are in + // first insertion order but performance.measure() are in the reverse order. We can + // always add the 0 time slot even if it's in the past. That's still considered for + // ordering. + performance.mark('Blocking Track', blockingLaneMarker); + performance.mark('Transition Track', transitionLaneMarker); + performance.mark('Suspense Track', suspenseLaneMarker); + performance.mark('Idle Track', idleLaneMarker); + } +} + export function logComponentRender( fiber: Fiber, startTime: number, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 2ff35b0c6a7f2..f812d16c9c65a 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -82,6 +82,8 @@ import { logYieldTime, logActionYieldTime, logSuspendedYieldTime, + setCurrentTrackFromLanes, + markAllLanesInOrder, } from './ReactFiberPerformanceTrack'; import { @@ -271,7 +273,6 @@ import { yieldReason, startPingTimerByLanes, } from './ReactProfilerTimer'; -import {setCurrentTrackFromLanes} from './ReactFiberPerformanceTrack'; // DEV stuff import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; @@ -1727,6 +1728,15 @@ function finalizeRender(lanes: Lanes, finalizationTime: number): void { function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { if (enableProfilerTimer && enableComponentPerformanceTrack) { + // The order of tracks within a group are determined by the earliest start time. + // Are tracks should show up in priority order and we should ideally always show + // every track. This is a hack to ensure that we're displaying all tracks in the + // right order. Ideally we could do this only once but because calls that aren't + // recorded aren't considered for ordering purposes, we need to keep adding these + // over and over again in case recording has just started. We can't tell when + // recording starts. + markAllLanesInOrder(); + const previousRenderStartTime = renderStartTime; // Starting a new render. Log the end of any previous renders and the // blocked time before the render started. From aba370f1e45d21f19f33c04c33fc99fb3d0109e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 22 Nov 2024 13:24:29 -0500 Subject: [PATCH 05/27] Add moveBefore Experiment (#31596) A long standing issue for React has been that if you reorder stateful nodes, they may lose their state and reload. The thing moving loses its state. There's no way to solve this in general where two stateful nodes swap. The [`moveBefore()` proposal](https://chromestatus.com/feature/5135990159835136?gate=5177450351558656) has now moved to [intent-to-ship](https://groups.google.com/a/chromium.org/g/blink-dev/c/YE_xLH6MkRs/m/_7CD0NYMAAAJ). This function is kind of like `insertBefore` but preserves state. There's [a demo here](https://state-preserving-atomic-move.glitch.me/). Ideally we'd port this demo to a fixture so we can try it. Currently this flag is always off - even in experimental. That's because this is still behind a Chrome flag so it's a little early to turn it on even in experimental. So you need a custom build. It's on in RN but only because it doesn't apply there which makes it easier to tell that it's safe to ship once it's on everywhere else. The other reason it's still off is because there's currently a semantic breaking change. `moveBefore()` errors if both nodes are disconnected. That happens if we're inside a completely disconnected React root. That's not usually how you should use React because it means effects can't read layout etc. However, it is currently supported. To handle this we'd have to try/catch the `moveBefore` to handle this case but we hope this semantic will change before it ships. Before we turn this on in experimental we either have to wait for the implementation to not error in the disconnected-disconnected case in Chrome or we'd have to add try/catch. --- .../src/client/ReactFiberConfigDOM.js | 22 +++++++++++++++++-- packages/shared/ReactFeatureFlags.js | 3 +++ .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + ...actFeatureFlags.test-renderer.native-fb.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 8 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 3315ce92bbc4f..bfa9adb48d737 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -93,6 +93,7 @@ import { enableTrustedTypesIntegration, enableAsyncActions, disableLegacyMode, + enableMoveBefore, } from 'shared/ReactFeatureFlags'; import { HostComponent, @@ -525,6 +526,7 @@ export function appendInitialChild( parentInstance: Instance, child: Instance | TextInstance, ): void { + // Note: This should not use moveBefore() because initial are appended while disconnected. parentInstance.appendChild(child); } @@ -757,11 +759,22 @@ export function commitTextUpdate( textInstance.nodeValue = newText; } +const supportsMoveBefore = + // $FlowFixMe[prop-missing]: We're doing the feature detection here. + enableMoveBefore && + typeof window !== 'undefined' && + typeof window.Node.prototype.moveBefore === 'function'; + export function appendChild( parentInstance: Instance, child: Instance | TextInstance, ): void { - parentInstance.appendChild(child); + if (supportsMoveBefore) { + // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore. + parentInstance.moveBefore(child, null); + } else { + parentInstance.appendChild(child); + } } export function appendChildToContainer( @@ -799,7 +812,12 @@ export function insertBefore( child: Instance | TextInstance, beforeChild: Instance | TextInstance | SuspenseInstance, ): void { - parentInstance.insertBefore(child, beforeChild); + if (supportsMoveBefore) { + // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore. + parentInstance.moveBefore(child, beforeChild); + } else { + parentInstance.insertBefore(child, beforeChild); + } } export function insertInContainerBefore( diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 997e9461059fa..e82bce7b50cbe 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -210,6 +210,9 @@ export const disableIEWorkarounds = true; // request for certain browsers. export const enableFilterEmptyStringAttributesDOM = true; +// Enable the moveBefore() alternative to insertBefore(). This preserves states of moves. +export const enableMoveBefore = false; + // Disabled caching behavior of `react/cache` in client runtimes. export const disableClientCache = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 748aa03a9d7ae..defa7cd330d7f 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -55,6 +55,7 @@ export const enableDebugTracing = false; export const enableDeferRootSchedulingToMicrotask = true; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFilterEmptyStringAttributesDOM = true; +export const enableMoveBefore = true; export const enableFizzExternalRuntime = true; export const enableFlightReadableStream = true; export const enableGetInspectorDataForInstanceInProduction = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 9f48f3877f81d..16c156f41c5ac 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -44,6 +44,7 @@ export const enableDeferRootSchedulingToMicrotask = true; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFabricCompleteRootInCommitPhase = false; export const enableFilterEmptyStringAttributesDOM = true; +export const enableMoveBefore = true; export const enableFizzExternalRuntime = true; export const enableFlightReadableStream = true; export const enableGetInspectorDataForInstanceInProduction = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index fbb95e0829018..01462919600d2 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -45,6 +45,7 @@ export const favorSafetyOverHydrationPerf = true; export const enableComponentStackLocations = true; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; +export const enableMoveBefore = false; export const enableGetInspectorDataForInstanceInProduction = false; export const enableFabricCompleteRootInCommitPhase = false; export const enableHiddenSubtreeInsertionEffectCleanup = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 8bc77e4bfe375..51d9b90d4f7be 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -35,6 +35,7 @@ export const enableDebugTracing = false; export const enableDeferRootSchedulingToMicrotask = true; export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFilterEmptyStringAttributesDOM = true; +export const enableMoveBefore = false; export const enableFizzExternalRuntime = true; export const enableFlightReadableStream = true; export const enableGetInspectorDataForInstanceInProduction = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index e28627760a756..272886cc58d07 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -47,6 +47,7 @@ export const favorSafetyOverHydrationPerf = true; export const enableComponentStackLocations = true; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; +export const enableMoveBefore = false; export const enableGetInspectorDataForInstanceInProduction = false; export const enableRenderableContext = false; export const enableFabricCompleteRootInCommitPhase = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 67d1ad74c0494..8ddbf3dd7c519 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -57,6 +57,7 @@ export const enableCPUSuspense = true; export const enableUseMemoCacheHook = true; export const enableUseEffectEventHook = true; export const enableFilterEmptyStringAttributesDOM = true; +export const enableMoveBefore = false; export const enableAsyncActions = true; export const disableInputAttributeSyncing = false; export const enableLegacyFBSupport = true; From eee5ca2a92c32103b6bc3c1f7e6cdf1c4807ee56 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:59:59 -0500 Subject: [PATCH 06/27] [compiler] Prune all unused array destructure items during DCE (#31619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We didn't originally support holes within array patterns, so DCE was only able to prune unused items from the end of an array pattern. Now that we support holes we can replace any unused item with a hole, and then just prune the items to the last identifier/spread entry. Note: this was motivated by finding useState where either the state or setState go unused — both are strong indications that you're violating the rules in some way. By DCE-ing the unused portions of the useState destructuring we can easily check if you're ignoring either value. closes #31603 This is a redo of that PR not using ghstack --- .../src/Optimization/DeadCodeElimination.ts | 32 +++++++++---------- .../compiler/concise-arrow-expr.expect.md | 2 +- ...alysis-destructured-rest-element.expect.md | 3 +- ...rtent-mutability-readonly-lambda.expect.md | 2 +- ...ted-callback-from-other-callback.expect.md | 2 +- .../preserve-use-memo-transition.expect.md | 2 +- .../compiler/react-namespace.expect.md | 2 +- ...rol-dependency-phi-setState-type.expect.md | 4 +-- ...ession-of-jsxexpressioncontainer.expect.md | 3 +- .../unused-array-middle-element.expect.md | 2 +- ...patch-considered-as-non-reactive.expect.md | 2 +- ...urned-dispatcher-is-non-reactive.expect.md | 2 +- 12 files changed, 27 insertions(+), 31 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts index 0202d3ecf043f..2b752c6dfd28e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts @@ -6,7 +6,6 @@ */ import { - ArrayPattern, BlockId, HIRFunction, Identifier, @@ -184,29 +183,28 @@ function rewriteInstruction(instr: Instruction, state: State): void { switch (instr.value.lvalue.pattern.kind) { case 'ArrayPattern': { /* - * For arrays, we can only eliminate unused items from the end of the array, - * so we iterate from the end and break once we find a used item. Note that - * we already know at least one item is used, from the pruneableValue check. + * For arrays, we can prune items prior to the end by replacing + * them with a hole. Items at the end can simply be dropped. */ - let nextItems: ArrayPattern['items'] | null = null; - const originalItems = instr.value.lvalue.pattern.items; - for (let i = originalItems.length - 1; i >= 0; i--) { - const item = originalItems[i]; + let lastEntryIndex = 0; + const items = instr.value.lvalue.pattern.items; + for (let i = 0; i < items.length; i++) { + const item = items[i]; if (item.kind === 'Identifier') { - if (state.isIdOrNameUsed(item.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } else if (item.kind === 'Spread') { - if (state.isIdOrNameUsed(item.place.identifier)) { - nextItems = originalItems.slice(0, i + 1); - break; + if (!state.isIdOrNameUsed(item.place.identifier)) { + items[i] = {kind: 'Hole'}; + } else { + lastEntryIndex = i; } } } - if (nextItems !== null) { - instr.value.lvalue.pattern.items = nextItems; - } + items.length = lastEntryIndex + 1; break; } case 'ObjectPattern': { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md index 787418f75b3fb..996afa1cb224b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/concise-arrow-expr.expect.md @@ -16,7 +16,7 @@ function component() { import { c as _c } from "react/compiler-runtime"; function component() { const $ = _c(1); - const [x, setX] = useState(0); + const [, setX] = useState(0); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const handler = (v) => setX(v); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md index dfa55302aee84..f38ffba958587 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/escape-analysis-destructured-rest-element.expect.md @@ -35,8 +35,7 @@ function Component(props) { } let d; if ($[2] !== props.c) { - const [c, ...t0] = props.c; - d = t0; + [, ...d] = props.c; $[2] = props.c; $[3] = d; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md index 72433173b17ca..76dfc5ab1ca5c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/inadvertent-mutability-readonly-lambda.expect.md @@ -24,7 +24,7 @@ function Component(props) { import { c as _c } from "react/compiler-runtime"; function Component(props) { const $ = _c(2); - const [value, setValue] = useState(null); + const [, setValue] = useState(null); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = (e) => setValue((value_0) => value_0 + e.target.value); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md index 710f3ac8ec782..57ca944af6bc9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-calls-to-hoisted-callback-from-other-callback.expect.md @@ -39,7 +39,7 @@ import { useState } from "react"; function Component(props) { const $ = _c(1); - const [_state, setState] = useState(); + const [, setState] = useState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const a = () => b(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md index 941c37bc3f29f..3687198df3a99 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/preserve-use-memo-transition.expect.md @@ -28,7 +28,7 @@ import { useCallback, useTransition } from "react"; function useFoo() { const $ = _c(1); - const [t, start] = useTransition(); + const [, start] = useTransition(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md index 0afc5b651bb94..5deb18397d91b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/react-namespace.expect.md @@ -32,7 +32,7 @@ function Component(props) { const $ = _c(5); React.useContext(FooContext); const ref = React.useRef(); - const [x, setX] = React.useState(false); + const [, setX] = React.useState(false); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md index 26e1f35d5b94a..c56bb7936c1aa 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/reactive-control-dependency-phi-setState-type.expect.md @@ -61,8 +61,8 @@ import { useState } from "react"; function Component(props) { const $ = _c(5); - const [x, setX] = useState(false); - const [y, setY] = useState(false); + const [, setX] = useState(false); + const [, setY] = useState(false); let setState; if (props.cond) { setState = setX; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md index b04c51abc51de..3e71c05a2efbf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md @@ -52,8 +52,7 @@ function Component(props) { const { buttons } = props; let nonPrimaryButtons; if ($[0] !== buttons) { - const [primaryButton, ...t0] = buttons; - nonPrimaryButtons = t0; + [, ...nonPrimaryButtons] = buttons; $[0] = buttons; $[1] = nonPrimaryButtons; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md index 2cf76a57adf40..4ae1fb6e22c43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unused-array-middle-element.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript function foo(props) { - const [x, unused, y] = props.a; + const [x, , y] = props.a; return x + y; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md index 4181651e3d60f..4b6b94ce431c2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md @@ -29,7 +29,7 @@ import { useActionState } from "react"; function Component() { const $ = _c(1); - const [actionState, dispatchAction] = useActionState(); + const [, dispatchAction] = useActionState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const onSubmitAction = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md index f5adab196b7bd..a500af2fc8f76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useReducer-returned-dispatcher-is-non-reactive.expect.md @@ -30,7 +30,7 @@ import { useReducer } from "react"; function f() { const $ = _c(1); - const [state, dispatch] = useReducer(); + const [, dispatch] = useReducer(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { const onClick = () => { From e3b7ef32be6a6d01ea050a10a218538e3a75c64f Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 22 Nov 2024 16:13:42 -0500 Subject: [PATCH 07/27] [crud] Only export uRC when flag is enabled (#31617) It's tricky to do feature detection of uRC currently because it's always present on the export. Let's conditionally export it instead. --- .../src/__tests__/ReactHooksWithNoopRenderer-test.js | 5 +++++ packages/react/src/ReactClient.js | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index a0e84b81d034f..a43b6c124df7d 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -3276,6 +3276,11 @@ describe('ReactHooksWithNoopRenderer', () => { } } + // @gate !enableUseResourceEffectHook + it('is null when flag is disabled', async () => { + expect(useResourceEffect).toBeUndefined(); + }); + // @gate enableUseResourceEffectHook it('validates create return value', async () => { function App({id}) { diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js index 15f70bf70a747..478d9e026aae8 100644 --- a/packages/react/src/ReactClient.js +++ b/packages/react/src/ReactClient.js @@ -65,6 +65,7 @@ import {startTransition} from './ReactStartTransition'; import {act} from './ReactAct'; import {captureOwnerStack} from './ReactOwnerStack'; import ReactCompilerRuntime from './ReactCompilerRuntime'; +import {enableUseResourceEffectHook} from 'shared/ReactFeatureFlags'; const Children = { map, @@ -90,7 +91,6 @@ export { useContext, useEffect, useEffectEvent as experimental_useEffectEvent, - useResourceEffect as experimental_useResourceEffect, useImperativeHandle, useDebugValue, useInsertionEffect, @@ -131,3 +131,6 @@ export { act, // DEV-only captureOwnerStack, // DEV-only }; + +export const experimental_useResourceEffect: typeof useResourceEffect | void = + enableUseResourceEffectHook ? useResourceEffect : undefined; From 2a9f4c04e54294b668e0a2ae11c1930c2e57b248 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Fri, 22 Nov 2024 17:19:20 -0500 Subject: [PATCH 08/27] [compiler] Infer deps configuration (#31616) Adds a way to configure how we insert deps for experimental purposes. ``` [ { module: 'react', imported: 'useEffect', numRequiredArgs: 1, }, { module: 'MyExperimentalEffectHooks', imported: 'useExperimentalEffect', numRequiredArgs: 2, }, ] ``` would insert dependencies for calls of `useEffect` imported from `react` if they have 1 argument and calls of useExperimentalEffect` from `MyExperimentalEffectHooks` if they have 2 arguments. The pushed dep array is appended to the arg list. --- .../src/Entrypoint/Pipeline.ts | 2 +- .../src/HIR/Environment.ts | 51 +++++++++++++++- .../src/Inference/InferEffectDependencies.ts | 46 ++++++++++---- .../infer-deps-custom-config.expect.md | 61 +++++++++++++++++++ .../compiler/infer-deps-custom-config.js | 9 +++ .../infer-effect-dependencies.expect.md | 4 ++ .../compiler/infer-effect-dependencies.js | 2 + compiler/packages/snap/src/compiler.ts | 6 -- .../snap/src/sprout/shared-runtime.ts | 9 +++ 9 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index bbd076e46a914..90921454c864f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -356,7 +356,7 @@ function* runWithEnvironment( }); if (env.config.inferEffectDependencies) { - inferEffectDependencies(env, hir); + inferEffectDependencies(hir); } if (env.config.inlineJsxTransform) { 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 432eaf96ff99b..90f53d495b5a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -242,9 +242,40 @@ const EnvironmentConfigSchema = z.object({ enableOptionalDependencies: z.boolean().default(true), /** - * Enables inference and auto-insertion of effect dependencies. Still experimental. + * Enables inference and auto-insertion of effect dependencies. Takes in an array of + * configurable module and import pairs to allow for user-land experimentation. For example, + * [ + * { + * module: 'react', + * imported: 'useEffect', + * numRequiredArgs: 1, + * },{ + * module: 'MyExperimentalEffectHooks', + * imported: 'useExperimentalEffect', + * numRequiredArgs: 2, + * }, + * ] + * would insert dependencies for calls of `useEffect` imported from `react` and calls of + * useExperimentalEffect` from `MyExperimentalEffectHooks`. + * + * `numRequiredArgs` tells the compiler the amount of arguments required to append a dependency + * array to the end of the call. With the configuration above, we'd insert dependencies for + * `useEffect` if it is only given a single argument and it would be appended to the argument list. + * + * numRequiredArgs must always be greater than 0, otherwise there is no function to analyze for dependencies + * + * Still experimental. */ - inferEffectDependencies: z.boolean().default(false), + inferEffectDependencies: z + .nullable( + z.array( + z.object({ + function: ExternalFunctionSchema, + numRequiredArgs: z.number(), + }), + ), + ) + .default(null), /** * Enables inlining ReactElement object literals in place of JSX @@ -614,6 +645,22 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = { source: 'react-compiler-runtime', importSpecifierName: 'useContext_withSelector', }, + inferEffectDependencies: [ + { + function: { + source: 'react', + importSpecifierName: 'useEffect', + }, + numRequiredArgs: 1, + }, + { + function: { + source: 'shared-runtime', + importSpecifierName: 'useSpecialEffect', + }, + numRequiredArgs: 2, + }, + ], }; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts index 9dc7dff78a6af..9fb91a4ac829b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts @@ -8,7 +8,6 @@ import { HIRFunction, IdentifierId, Instruction, - isUseEffectHookType, makeInstructionId, TInstruction, InstructionId, @@ -23,20 +22,33 @@ import { markInstructionIds, } from '../HIR/HIRBuilder'; import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors'; +import {getOrInsertWith} from '../Utils/utils'; /** * Infers reactive dependencies captured by useEffect lambdas and adds them as * a second argument to the useEffect call if no dependency array is provided. */ -export function inferEffectDependencies( - env: Environment, - fn: HIRFunction, -): void { +export function inferEffectDependencies(fn: HIRFunction): void { let hasRewrite = false; const fnExpressions = new Map< IdentifierId, TInstruction >(); + + const autodepFnConfigs = new Map>(); + for (const effectTarget of fn.env.config.inferEffectDependencies!) { + const moduleTargets = getOrInsertWith( + autodepFnConfigs, + effectTarget.function.source, + () => new Map(), + ); + moduleTargets.set( + effectTarget.function.importSpecifierName, + effectTarget.numRequiredArgs, + ); + } + const autodepFnLoads = new Map(); + const scopeInfos = new Map< ScopeId, {pruned: boolean; deps: ReactiveScopeDependencies; hasSingleInstr: boolean} @@ -74,15 +86,23 @@ export function inferEffectDependencies( lvalue.identifier.id, instr as TInstruction, ); + } else if ( + value.kind === 'LoadGlobal' && + value.binding.kind === 'ImportSpecifier' + ) { + const moduleTargets = autodepFnConfigs.get(value.binding.module); + if (moduleTargets != null) { + const numRequiredArgs = moduleTargets.get(value.binding.imported); + if (numRequiredArgs != null) { + autodepFnLoads.set(lvalue.identifier.id, numRequiredArgs); + } + } } else if ( /* - * This check is not final. Right now we only look for useEffects without a dependency array. - * This is likely not how we will ship this feature, but it is good enough for us to make progress - * on the implementation and test it. + * TODO: Handle method calls */ value.kind === 'CallExpression' && - isUseEffectHookType(value.callee.identifier) && - value.args.length === 1 && + autodepFnLoads.get(value.callee.identifier.id) === value.args.length && value.args[0].kind === 'Identifier' ) { const fnExpr = fnExpressions.get(value.args[0].identifier.id); @@ -132,7 +152,7 @@ export function inferEffectDependencies( loc: GeneratedSource, }; - const depsPlace = createTemporaryPlace(env, GeneratedSource); + const depsPlace = createTemporaryPlace(fn.env, GeneratedSource); depsPlace.effect = Effect.Read; newInstructions.push({ @@ -142,8 +162,8 @@ export function inferEffectDependencies( value: deps, }); - // Step 2: insert the deps array as an argument of the useEffect - value.args[1] = {...depsPlace, effect: Effect.Freeze}; + // Step 2: push the inferred deps array as an argument of the useEffect + value.args.push({...depsPlace, effect: Effect.Freeze}); rewriteInstrs.set(instr.id, newInstructions); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md new file mode 100644 index 0000000000000..b439d5dee07e2 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.expect.md @@ -0,0 +1,61 @@ + +## Input + +```javascript +// @inferEffectDependencies +import {print, useSpecialEffect} from 'shared-runtime'; + +function CustomConfig({propVal}) { + // Insertion + useSpecialEffect(() => print(propVal), [propVal]); + // No insertion + useSpecialEffect(() => print(propVal), [propVal], [propVal]); +} + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { print, useSpecialEffect } from "shared-runtime"; + +function CustomConfig(t0) { + const $ = _c(7); + const { propVal } = t0; + let t1; + let t2; + if ($[0] !== propVal) { + t1 = () => print(propVal); + t2 = [propVal]; + $[0] = propVal; + $[1] = t1; + $[2] = t2; + } else { + t1 = $[1]; + t2 = $[2]; + } + useSpecialEffect(t1, t2, [propVal]); + let t3; + let t4; + let t5; + if ($[3] !== propVal) { + t3 = () => print(propVal); + t4 = [propVal]; + t5 = [propVal]; + $[3] = propVal; + $[4] = t3; + $[5] = t4; + $[6] = t5; + } else { + t3 = $[4]; + t4 = $[5]; + t5 = $[6]; + } + useSpecialEffect(t3, t4, t5); +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js new file mode 100644 index 0000000000000..124e607a8ff48 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-deps-custom-config.js @@ -0,0 +1,9 @@ +// @inferEffectDependencies +import {print, useSpecialEffect} from 'shared-runtime'; + +function CustomConfig({propVal}) { + // Insertion + useSpecialEffect(() => print(propVal), [propVal]); + // No insertion + useSpecialEffect(() => print(propVal), [propVal], [propVal]); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md index febdd005a3cb0..6e45941f24264 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.expect.md @@ -3,6 +3,8 @@ ```javascript // @inferEffectDependencies +import {useEffect, useRef} from 'react'; + const moduleNonReactive = 0; function Component({foo, bar}) { @@ -45,6 +47,8 @@ function Component({foo, bar}) { ```javascript import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies +import { useEffect, useRef } from "react"; + const moduleNonReactive = 0; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js index 6a70bc1298b0a..723ad6516565f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies.js @@ -1,4 +1,6 @@ // @inferEffectDependencies +import {useEffect, useRef} from 'react'; + const moduleNonReactive = 0; function Component({foo, bar}) { diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 6942ffb2adc7b..95af40d62a880 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -174,11 +174,6 @@ function makePluginOptions( .filter(s => s.length > 0); } - let inferEffectDependencies = false; - if (firstLine.includes('@inferEffectDependencies')) { - inferEffectDependencies = true; - } - let logs: Array<{filename: string | null; event: LoggerEvent}> = []; let logger: Logger | null = null; if (firstLine.includes('@logger')) { @@ -202,7 +197,6 @@ function makePluginOptions( hookPattern, validatePreserveExistingMemoizationGuarantees, validateBlocklistedImports, - inferEffectDependencies, }, compilationMode, logger, diff --git a/compiler/packages/snap/src/sprout/shared-runtime.ts b/compiler/packages/snap/src/sprout/shared-runtime.ts index bd069ae6d0299..58815842cb03c 100644 --- a/compiler/packages/snap/src/sprout/shared-runtime.ts +++ b/compiler/packages/snap/src/sprout/shared-runtime.ts @@ -363,6 +363,14 @@ export function useFragment(..._args: Array): object { }; } +export function useSpecialEffect( + fn: () => any, + _secondArg: any, + deps: Array, +) { + React.useEffect(fn, deps); +} + export function typedArrayPush(array: Array, item: T): void { array.push(item); } @@ -370,4 +378,5 @@ export function typedArrayPush(array: Array, item: T): void { export function typedLog(...values: Array): void { console.log(...values); } + export default typedLog; From 7670501b0dc1a97983058b5217a205b62e2094a1 Mon Sep 17 00:00:00 2001 From: Pavel <19418601+rakleed@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:32:11 +0300 Subject: [PATCH 09/27] Replace deprecated dependency in `eslint-plugin-react-compiler` (#31629) --- .../eslint-plugin-react-compiler/package.json | 2 +- .../rollup.config.js | 2 +- .../src/rules/ReactCompilerRule.ts | 4 +- compiler/yarn.lock | 153 +++++++----------- 4 files changed, 59 insertions(+), 102 deletions(-) diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index eec2c9424846a..67ae4f672fcfa 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -13,7 +13,7 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-transform-private-methods": "^7.25.9", "hermes-parser": "^0.25.1", "zod": "^3.22.4", "zod-validation-error": "^3.0.3" diff --git a/compiler/packages/eslint-plugin-react-compiler/rollup.config.js b/compiler/packages/eslint-plugin-react-compiler/rollup.config.js index 4d813564098a4..2615c47f8b61f 100644 --- a/compiler/packages/eslint-plugin-react-compiler/rollup.config.js +++ b/compiler/packages/eslint-plugin-react-compiler/rollup.config.js @@ -17,7 +17,7 @@ import banner2 from 'rollup-plugin-banner2'; const NO_INLINE = new Set([ '@babel/core', - '@babel/plugin-proposal-private-methods', + '@babel/plugin-transform-private-methods', 'hermes-parser', 'zod', 'zod-validation-error', diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index b9a1ffa440c49..1ce1da5fe6a51 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -7,7 +7,7 @@ import {transformFromAstSync} from '@babel/core'; // @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; +import PluginTransformPrivateMethods from '@babel/plugin-transform-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; import BabelPluginReactCompiler, { CompilerErrorDetailOptions, @@ -282,7 +282,7 @@ const rule: Rule.RuleModule = { highlightCode: false, retainLines: true, plugins: [ - [PluginProposalPrivateMethods, {loose: true}], + [PluginTransformPrivateMethods, {loose: true}], [BabelPluginReactCompiler, options], ], sourceType: 'module', diff --git a/compiler/yarn.lock b/compiler/yarn.lock index b4c72ff3c5ede..682dee97423bd 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -133,12 +133,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.25.9" "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": version "7.22.3" @@ -169,21 +169,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" - integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - semver "^6.3.1" - "@babel/helper-create-class-features-plugin@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" @@ -212,6 +197,19 @@ "@babel/helper-split-export-declaration" "^7.18.6" semver "^6.3.0" +"@babel/helper-create-class-features-plugin@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" + integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/traverse" "^7.25.9" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1": version "7.22.1" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70" @@ -243,11 +241,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8" integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA== -"@babel/helper-environment-visitor@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" - integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== - "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" @@ -264,14 +257,6 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" -"@babel/helper-function-name@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" - integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== - dependencies: - "@babel/template" "^7.22.5" - "@babel/types" "^7.22.5" - "@babel/helper-function-name@^7.7.4": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" @@ -301,12 +286,13 @@ dependencies: "@babel/types" "^7.22.3" -"@babel/helper-member-expression-to-functions@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2" - integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ== +"@babel/helper-member-expression-to-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" + integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== dependencies: - "@babel/types" "^7.22.5" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" "@babel/helper-module-imports@^7.18.6": version "7.18.6" @@ -388,12 +374,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== +"@babel/helper-optimise-call-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" + integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.25.9" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0": version "7.19.0" @@ -415,6 +401,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" @@ -448,14 +439,14 @@ "@babel/traverse" "^7.19.1" "@babel/types" "^7.19.0" -"@babel/helper-replace-supers@^7.22.9": - version "7.22.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" - integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== +"@babel/helper-replace-supers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz#ba447224798c3da3f8713fc272b145e33da6a5c5" + integrity sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-member-expression-to-functions" "^7.22.5" - "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/traverse" "^7.25.9" "@babel/helper-simple-access@^7.18.6": version "7.18.6" @@ -485,12 +476,13 @@ dependencies: "@babel/types" "^7.20.0" -"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== dependencies: - "@babel/types" "^7.22.5" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" @@ -499,13 +491,6 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-split-export-declaration@^7.7.4": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" @@ -528,11 +513,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - "@babel/helper-string-parser@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" @@ -658,11 +638,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32" integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA== -"@babel/parser@^7.22.5": - version "7.22.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" - integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== - "@babel/parser@^7.24.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" @@ -703,14 +678,6 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-transform-optional-chaining" "^7.22.3" -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object@^7.21.0": version "7.21.10" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.10.tgz#861ab9c7d152291c47d27838867f27c560f562c4" @@ -1229,6 +1196,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.1" "@babel/helper-plugin-utils" "^7.21.5" +"@babel/plugin-transform-private-methods@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" + integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-private-property-in-object@^7.22.3": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56" @@ -1554,15 +1529,6 @@ "@babel/parser" "^7.21.9" "@babel/types" "^7.21.5" -"@babel/template@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== - dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" - "@babel/template@^7.24.7": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" @@ -1662,15 +1628,6 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" -"@babel/types@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" - integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - to-fast-properties "^2.0.0" - "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.6", "@babel/types@^7.7.4": version "7.25.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" From 5b0ef217ef32333a8e56f39be04327c89efa346f Mon Sep 17 00:00:00 2001 From: Ricky Date: Mon, 2 Dec 2024 10:02:31 -0500 Subject: [PATCH 10/27] s/server action/server function (#31005) ## Overview Changes the error message to say "Server Functions" instead of "Server Actions" since this error can fire in cases like: ```