Skip to content

fix(runtime-dom): defer teleport mount/update until suspense resolves#8619

Merged
edison1105 merged 14 commits into
vuejs:mainfrom
baiwusanyu-c:bwsy/feat/suspense-inner-hooks
Mar 25, 2026
Merged

fix(runtime-dom): defer teleport mount/update until suspense resolves#8619
edison1105 merged 14 commits into
vuejs:mainfrom
baiwusanyu-c:bwsy/feat/suspense-inner-hooks

Conversation

@baiwusanyu-c

@baiwusanyu-c baiwusanyu-c commented Jun 21, 2023

Copy link
Copy Markdown
Member

close: #8603

Summary by CodeRabbit

  • Bug Fixes

    • Teleport now defers mounting and updates correctly when used inside Suspense so teleported content appears only after async content resolves and respects enabled/disabled state.
  • Tests

    • Added tests covering Teleport + Suspense: fallback behavior while pending, reactive updates before resolution, and disabled-state handling across async resolution.

@baiwusanyu-c

This comment was marked as outdated.

Comment thread packages/runtime-core/src/components/Teleport.ts Outdated
@baiwusanyu-c baiwusanyu-c changed the title feat(runtime-dom): teleport mount after suspense is resolved fix(runtime-dom): teleport mount after suspense is resolved Jun 21, 2023
@baiwusanyu-c baiwusanyu-c requested a review from edison1105 June 21, 2023 07:38
Comment thread packages/runtime-core/src/components/Teleport.ts
Comment thread pnpm-lock.yaml Outdated
Comment thread packages/runtime-core/src/components/Teleport.ts Outdated
@baiwusanyu-c baiwusanyu-c requested a review from edison1105 June 21, 2023 08:56
…ner-hooks

# Conflicts:
#	packages/runtime-core/__tests__/components/Suspense.spec.ts
@github-actions

github-actions Bot commented Nov 13, 2023

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 105 kB (+30 B) 39.8 kB (+17 B) 35.8 kB (+51 B)
vue.global.prod.js 164 kB (+30 B) 59.8 kB (+19 B) 53.2 kB (+20 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 48.3 kB 18.8 kB 17.2 kB
createApp 56.4 kB 21.8 kB 19.9 kB
createSSRApp 60.6 kB 23.6 kB 21.5 kB
defineCustomElement 62.6 kB 23.8 kB 21.6 kB
overall 70.9 kB 27.1 kB 24.7 kB

baiwusanyu-c and others added 2 commits January 3, 2024 10:23
…ner-hooks

# Conflicts:
#	packages/runtime-core/__tests__/components/Suspense.spec.ts
#	packages/runtime-core/src/components/Teleport.ts
@edison1105 edison1105 added scope: suspense 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready for review This PR requires more reviews labels Aug 1, 2024
baiwusanyu-c and others added 2 commits September 5, 2024 13:52
…inner-hooks

# Conflicts:
#	packages/runtime-core/__tests__/components/Suspense.spec.ts
#	packages/runtime-core/src/components/Teleport.ts
@pkg-pr-new

pkg-pr-new Bot commented Sep 5, 2024

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@8619
npm i https://pkg.pr.new/@vue/compiler-core@8619
yarn add https://pkg.pr.new/@vue/compiler-core@8619.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@8619
npm i https://pkg.pr.new/@vue/compiler-dom@8619
yarn add https://pkg.pr.new/@vue/compiler-dom@8619.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@8619
npm i https://pkg.pr.new/@vue/compiler-sfc@8619
yarn add https://pkg.pr.new/@vue/compiler-sfc@8619.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@8619
npm i https://pkg.pr.new/@vue/compiler-ssr@8619
yarn add https://pkg.pr.new/@vue/compiler-ssr@8619.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@8619
npm i https://pkg.pr.new/@vue/reactivity@8619
yarn add https://pkg.pr.new/@vue/reactivity@8619.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@8619
npm i https://pkg.pr.new/@vue/runtime-core@8619
yarn add https://pkg.pr.new/@vue/runtime-core@8619.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@8619
npm i https://pkg.pr.new/@vue/runtime-dom@8619
yarn add https://pkg.pr.new/@vue/runtime-dom@8619.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@8619
npm i https://pkg.pr.new/@vue/server-renderer@8619
yarn add https://pkg.pr.new/@vue/server-renderer@8619.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@8619
npm i https://pkg.pr.new/@vue/shared@8619
yarn add https://pkg.pr.new/@vue/shared@8619.tgz

vue

pnpm add https://pkg.pr.new/vue@8619
npm i https://pkg.pr.new/vue@8619
yarn add https://pkg.pr.new/vue@8619.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@8619
npm i https://pkg.pr.new/@vue/compat@8619
yarn add https://pkg.pr.new/@vue/compat@8619.tgz

commit: cd1c621

@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6dcfd5bc-87ed-4cb6-9a15-23a5fdc39b4c

📥 Commits

Reviewing files that changed from the base of the PR and between 5999e36 and cd1c621.

📒 Files selected for processing (1)
  • packages/runtime-core/src/components/Teleport.ts

📝 Walkthrough

Walkthrough

Defers 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

Cohort / File(s) Summary
Suspense-Teleport Tests
packages/runtime-core/__tests__/components/Suspense.spec.ts
Added three tests validating Teleport behavior during Suspense pending/resolution: fallback-only rendering while pending, reactive value updates inside Teleport before resolution, and toggling disabled during pending/resolved phases.
Teleport implementation
packages/runtime-core/src/components/Teleport.ts
Altered mount/update deferral logic to also consider parentSuspense.pendingBranch and prior pending mounts (__isMounted === false), defer initial mount via queuePostRenderEffect, and reorder anchor/target setup so deferred-path returns earlier to avoid premature mounting/patching.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

scope: teleport

Poem

🐰 I waited in the meadow, patient and small,
While Suspense held its breath and answered the call;
Teleports stayed tucked until the post-render song—
Then I hopped your nodes home, tidy and strong. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: deferring teleport mount/update behavior in relation to suspense resolution.
Linked Issues check ✅ Passed The changes implement the core objective from issue #8603: deferring Teleport rendering until Suspense async dependencies resolve, with tests validating teleport behavior across async resolution.
Out of Scope Changes check ✅ Passed All changes are directly related to addressing the linked issue: Teleport.ts modifications defer mounting/updates during Suspense pending state, and test cases verify the expected behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@edison1105 edison1105 changed the title fix(runtime-dom): teleport mount after suspense is resolved fix(runtime-dom): defer teleport mount/update until suspense resolves Mar 25, 2026
@edison1105

Copy link
Copy Markdown
Member

/ecosystem-ci run

@edison1105 edison1105 removed the ready for review This PR requires more reviews label Mar 25, 2026
@edison1105 edison1105 added the ready to merge The PR is ready to be merged. label Mar 25, 2026
@vue-bot

vue-bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

📝 Ran ecosystem CI: Open

suite result latest scheduled
radix-vue success success
language-tools failure failure
vite-plugin-vue success success
primevue success success
router success success
vant success success
quasar failure failure
pinia success success
test-utils success success
vitepress success success
nuxt success success
vueuse success success
vue-macros success success
vuetify success success
vue-simple-compiler success success
vue-i18n success success

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Invalidate 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 call mountToTarget(), which can resurrect stale teleported DOM in target. Please add a vnode-local invalidation guard and clear it from remove().

🛡️ 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 | 🔴 Critical

Carry the DOM refs through the deferred patch path.

The early return at Lines 189-203 happens before n2 inherits el/anchor/target refs from n1. 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 whose el was never assigned and throw on n1.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

📥 Commits

Reviewing files that changed from the base of the PR and between d61d803 and 5999e36.

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/components/Suspense.spec.ts
  • packages/runtime-core/src/components/Teleport.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. ready to merge The PR is ready to be merged. scope: suspense

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Teleport rendered before async dependencies resolve in Suspense component

3 participants