fix(runtime-dom): defer teleport mount/update until suspense resolves#8619
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
…ner-hooks # Conflicts: # packages/runtime-core/__tests__/components/Suspense.spec.ts
Size ReportBundles
Usages
|
…ner-hooks # Conflicts: # packages/runtime-core/__tests__/components/Suspense.spec.ts # packages/runtime-core/src/components/Teleport.ts
…inner-hooks # Conflicts: # packages/runtime-core/__tests__/components/Suspense.spec.ts # packages/runtime-core/src/components/Teleport.ts
@vue/compiler-core
@vue/compiler-dom
@vue/compiler-sfc
@vue/compiler-ssr
@vue/reactivity
@vue/runtime-core
@vue/runtime-dom
@vue/server-renderer
@vue/shared
vue
@vue/compat
commit: |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughDefers Teleport mount/patch when its parent Suspense has a pending post-render branch or a prior teleport mount is pending; adds tests covering Teleport behavior while Suspense is pending, reactive updates, and disabled-state transitions. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App as App/VNode tree
participant Suspense as Suspense boundary
participant Teleport as Teleport vnode
participant Scheduler as queuePostRenderEffect
participant Target as DOM target
App->>Suspense: mount subtree (contains Teleport + async)
Suspense-->>App: pendingBranch = true (async unresolved)
App->>Teleport: mount
alt parentSuspense.pendingBranch || isTeleportDeferred
Teleport->>Scheduler: schedule mountToTarget (deferred)
note right of Teleport: mark __isMounted = false
Scheduler->>Teleport: post-render callback (after Suspense resolves)
Suspense->>Teleport: pendingBranch -> false (on resolve)
Scheduler->>Teleport: mountToTarget()
Teleport->>Target: insert teleported nodes
else
Teleport->>Target: mountToTarget() immediately
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
/ecosystem-ci run |
|
📝 Ran ecosystem CI: Open
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/runtime-core/src/components/Teleport.ts (2)
172-180:⚠️ Potential issue | 🟠 MajorInvalidate the queued target mount when this Teleport is discarded.
A same-root pending update can remove or replace this Teleport without clearing the old callback from the parent suspense's buffered
effects. In that case, Lines 177-180 will still run on resolve and callmountToTarget(), which can resurrect stale teleported DOM intarget. Please add a vnode-local invalidation guard and clear it fromremove().🛡️ Example guard
) { n2.el!.__isMounted = false queuePostRenderEffect(() => { + if (n2.el!.__isMounted !== false) return mountToTarget() delete n2.el!.__isMounted }, parentSuspense) } else {Also clear that flag from
remove()when a pending teleport is discarded.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/runtime-core/src/components/Teleport.ts` around lines 172 - 180, Add a vnode-local invalidation guard so the queued post-render callback for Teleport checks whether this Teleport was discarded before running mountToTarget: when scheduling queuePostRenderEffect in the Teleport mount branch (where isTeleportDeferred or parentSuspense pendingBranch is true) set a flag on the vnode (e.g. n2.__pendingTeleport = true) and have the queued function early-return if that flag is falsy; then ensure the flag is cleared in the Teleport remove() path (e.g. delete n2.__pendingTeleport) so cancelled/vnode-replaced Teleports cannot run mountToTarget after being removed.
185-203:⚠️ Potential issue | 🔴 CriticalCarry the DOM refs through the deferred patch path.
The early return at Lines 189-203 happens before
n2inheritsel/anchor/target refs fromn1. One pre-resolve update still works because the queued retry closes over the original vnode, but a second update before resolution will patch from a Teleport whoseelwas never assigned and throw onn1.el!.__isMounted. Copy the refs before re-queueing.🔁 Minimal fix
if (n1.el!.__isMounted === false) { + n2.el = n1.el + n2.anchor = n1.anchor + n2.targetStart = n1.targetStart + n2.targetAnchor = n1.targetAnchor + n2.target = n1.target queuePostRenderEffect(() => { TeleportImpl.process( n1,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/runtime-core/src/components/Teleport.ts` around lines 185 - 203, Before early-returning and queueing the deferred retry in Teleport.ts, copy DOM refs from the old vnode to the new vnode so later retries have valid refs: set n2.el = n1.el, n2.anchor = n1.anchor and n2.target = n1.target (and any other teleport-specific ref fields) immediately before calling queuePostRenderEffect and returning. This change should be made in the code path that checks n1.el!.__isMounted === false and uses queuePostRenderEffect/TelportImpl.process so the queued closure operates on a vnode with the correct DOM references.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/runtime-core/src/components/Teleport.ts`:
- Around line 172-180: Add a vnode-local invalidation guard so the queued
post-render callback for Teleport checks whether this Teleport was discarded
before running mountToTarget: when scheduling queuePostRenderEffect in the
Teleport mount branch (where isTeleportDeferred or parentSuspense pendingBranch
is true) set a flag on the vnode (e.g. n2.__pendingTeleport = true) and have the
queued function early-return if that flag is falsy; then ensure the flag is
cleared in the Teleport remove() path (e.g. delete n2.__pendingTeleport) so
cancelled/vnode-replaced Teleports cannot run mountToTarget after being removed.
- Around line 185-203: Before early-returning and queueing the deferred retry in
Teleport.ts, copy DOM refs from the old vnode to the new vnode so later retries
have valid refs: set n2.el = n1.el, n2.anchor = n1.anchor and n2.target =
n1.target (and any other teleport-specific ref fields) immediately before
calling queuePostRenderEffect and returning. This change should be made in the
code path that checks n1.el!.__isMounted === false and uses
queuePostRenderEffect/TelportImpl.process so the queued closure operates on a
vnode with the correct DOM references.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3cf82bad-ec9a-43f4-8ad1-19335a484174
📒 Files selected for processing (2)
packages/runtime-core/__tests__/components/Suspense.spec.tspackages/runtime-core/src/components/Teleport.ts
close: #8603
Summary by CodeRabbit
Bug Fixes
Tests