Skip to content

Conversation

@linzhe141
Copy link
Contributor

@linzhe141 linzhe141 commented Dec 18, 2025

close #14215

Summary by CodeRabbit

  • Tests

    • Added coverage ensuring unmounting a newly mounted child app does not trigger watchers in an existing parent app.
  • Bug Fixes

    • Pre-flush callback handling updated to include the previous component context, improving callback behavior during render cycles.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Walkthrough

Passes the previous vnode's component instance into pre-flush callbacks and adds a test that unmounting a dynamically mounted app does not trigger watchers in another app.

Changes

Cohort / File(s) Summary
Test coverage
packages/runtime-core/__tests__/apiCreateApp.spec.ts
Adds test "unmount new app should not trigger other app's watcher" to verify unmounting a dynamically mounted app doesn't fire watchers in a separate app.
Renderer change
packages/runtime-core/src/renderer.ts
When unmounting an existing vnode (container._vnode), captures its component instance and calls flushPreFlushCbs(instance) instead of flushPreFlushCbs(), providing the previous component context to pre-flush callbacks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review focus:
    • Validate safe extraction and nullability handling of the previous vnode's component instance in renderer.ts.
    • Confirm flushPreFlushCbs(instance) callers and callbacks correctly accept and use the instance without regressions.
    • Ensure the new test is deterministic and properly isolates apps/watchers.

Poem

🐰 I hop between mounts, ears all astir,
One app unmounts — I barely purr.
Watchers stay put, no startled leap,
Gentle code, a quiet sleep.
Nibble on tests, then off to burrow deep. 🥕

Pre-merge checks and finishing touches

✅ 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 summarizes the main fix: preventing unmounting one app from triggering watchers in other apps, which aligns with the test and code changes.
Linked Issues check ✅ Passed The PR's changes directly address issue #14215: the test case verifies the fix prevents cross-app watcher triggering, and the renderer.ts modification passes the instance context to pre-flush callbacks to resolve the root cause.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the cross-app watcher triggering issue; the test addition validates the fix and the renderer.ts modification implements the necessary context handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@github-actions
Copy link

github-actions bot commented Dec 18, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 103 kB (+30 B) 39 kB (+4 B) 35.1 kB (+29 B)
vue.global.prod.js 161 kB (+30 B) 58.9 kB (+7 B) 52.5 kB (+33 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.9 kB (+30 B) 18.3 kB (+6 B) 16.8 kB (+4 B)
createApp 55.1 kB (+30 B) 21.4 kB (+9 B) 19.6 kB (-7 B)
createSSRApp 59.3 kB (+30 B) 23.1 kB (+9 B) 21.1 kB (+19 B)
defineCustomElement 60.6 kB (+30 B) 23.1 kB (+7 B) 21.1 kB (+7 B)
overall 69.3 kB (+30 B) 26.6 kB (+9 B) 24.3 kB (+11 B)

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 18, 2025

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: 39b6c96

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/runtime-core/src/renderer.ts (1)

2391-2397: Fix correctly prevents cross-app watcher triggering, but simplify the expression.

The fix is correct: by passing the previous component instance to flushPreFlushCbs, only callbacks associated with that specific component are flushed, preventing watchers in other apps from being inadvertently triggered during unmount.

However, the nested ternary expression on lines 2395-2397 is overly verbose.

🔎 Simplify with optional chaining:
     const prev = container._vnode
     container._vnode = vnode
     if (!isFlushing) {
       isFlushing = true
-      flushPreFlushCbs(
-        prev ? (prev.component ? prev.component : undefined) : undefined,
-      )
+      flushPreFlushCbs(prev?.component)
       flushPostFlushCbs()
       isFlushing = false
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8a2de4 and 8810664.

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/apiCreateApp.spec.ts (1 hunks)
  • packages/runtime-core/src/renderer.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/runtime-core/src/renderer.ts (1)
packages/runtime-core/src/scheduler.ts (1)
  • flushPreFlushCbs (142-171)
packages/runtime-core/__tests__/apiCreateApp.spec.ts (1)
packages/runtime-core/src/apiCreateApp.ts (1)
  • App (33-119)
🔇 Additional comments (1)
packages/runtime-core/__tests__/apiCreateApp.spec.ts (1)

650-686: Well-structured test that properly validates the fix.

The test correctly verifies that unmounting one app doesn't trigger watchers in another independent app. The setup with separate apps and the spy function effectively demonstrates the bug is fixed.

@edison1105 edison1105 changed the title fix(watch): unmount new app should not trigger ohter app's watcher fix(watch): unmount new app should not trigger other app's watcher Dec 18, 2025
@edison1105 edison1105 added ready to merge The PR is ready to be merged. 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Dec 18, 2025
@edison1105 edison1105 changed the title fix(watch): unmount new app should not trigger other app's watcher fix(watch): unmount app should not trigger other app's watcher Dec 18, 2025
@edison1105 edison1105 changed the title fix(watch): unmount app should not trigger other app's watcher fix(runtime-core): pass component instance to flushPreFlushCbs on unmount Dec 18, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/runtime-core/src/renderer.ts (1)

2376-2380: Instance capture logic is correct for standard Vue app usage; consider documenting the component vnode assumption.

The code captures instance = container._vnode.component to scope pre-flush callbacks during unmount, preventing watchers from other apps from executing. This works correctly for standard Vue applications where the root vnode is always a component. While accessing .component on a non-component vnode (edge case) would result in undefined, this is unlikely in practice and the fix still achieves its primary goal for typical usage.

Consider adding a comment for maintainability:

if (vnode == null) {
  if (container._vnode) {
    unmount(container._vnode, null, null, true)
+   // Capture component instance to scope pre-flush callbacks to this
+   // component only, preventing watchers in other apps from triggering
    instance = container._vnode.component
  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 249cd5f and 39b6c96.

📒 Files selected for processing (1)
  • packages/runtime-core/src/renderer.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-core/src/renderer.ts (1)
packages/runtime-core/src/scheduler.ts (1)
  • flushPreFlushCbs (142-171)
🔇 Additional comments (1)
packages/runtime-core/src/renderer.ts (1)

2396-2396: Well-targeted fix for scoping pre-flush callbacks.

Passing the captured instance to flushPreFlushCbs() correctly scopes the pre-flush callbacks to only the component being unmounted. According to the relevant code snippet, when an instance is provided, flushPreFlushCbs filters callbacks by cb.id === instance.uid, which prevents watchers from unrelated apps/components from being triggered.

The behavior is appropriately differentiated:

  • Unmount path: instance is defined → only matching callbacks are flushed (the fix)
  • Mount/patch path: instance is undefined → all callbacks are flushed (existing behavior)

This targeted approach addresses issue #14215 without affecting other code paths.

@edison1105
Copy link
Member

/ecosystem-ci run

@edison1105 edison1105 merged commit e857e12 into vuejs:main Dec 18, 2025
30 of 34 checks passed
@vue-bot
Copy link
Contributor

vue-bot commented Dec 18, 2025

📝 Ran ecosystem CI: Open

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

@linzhe141 linzhe141 deleted the fix-unmount-app-with-other-app-watch branch December 18, 2025 12:23
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

the watch Unexpected triggering

3 participants