Skip to content

fix(hmr): prevent updating unmounting component during HMR rerender #13775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

edison1105
Copy link
Member

@edison1105 edison1105 commented Aug 18, 2025

close #13771
close #13772

Summary by CodeRabbit

  • New Features

    • None
  • Bug Fixes

    • Improved Hot Module Replacement stability by avoiding rerenders on components that have been disposed, reducing errors during development hot-reload.
    • Ensures UI updates propagate correctly through nested components with forwarded slots during HMR, reflecting the latest changes immediately.
  • Tests

    • Added test coverage for nested HMR rerender scenarios with forwarded slots to validate correct UI updates.

Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

Adds an HMR rerender guard to skip updates for disposed component jobs and introduces a test verifying nested component rerenders propagate through forwarded slots during HMR.

Changes

Cohort / File(s) Summary
HMR Core Logic
packages/runtime-core/src/hmr.ts
Import SchedulerJobFlags; in rerender(id, newRender?), call instance.update() only if the instance’s job does not include DISPOSED.
HMR Tests
packages/runtime-core/__tests__/hmr.spec.ts
Add test “rerender for nested component” to verify rerender propagation through nested/forwarded slots via HMR id.

Sequence Diagram(s)

sequenceDiagram
  participant DevServer as HMR Runtime
  participant Comp as Component Instance
  participant Sched as Scheduler

  DevServer->>Comp: rerender(id, newRender?)
  Comp->>Sched: check job flags
  alt Job not DISPOSED
    Sched-->>Comp: allowed to update
    Comp->>Comp: instance.update()
  else Job DISPOSED
    Sched-->>Comp: skip update
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Assessment against linked issues

Objective Addressed Explanation
Handle nested Teleport with conditional rendering during hot update without target node errors (#13771) No Teleport or mounting logic changes; only HMR rerender guard and a slots-related test.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Add DISPOSED flag check before calling instance.update in HMR rerender (packages/runtime-core/src/hmr.ts: rerender()) Not related to Teleport target resolution or conditional mounting; focuses on HMR update scheduling.
New test for nested component rerender via forwarded slots (packages/runtime-core/tests/hmr.spec.ts: new test block) Tests HMR slot propagation, not Teleport behavior described in the issue.

Suggested labels

scope: hmr, :hammer: p3-minor-bug

Suggested reviewers

  • Doctor-wu
  • LittleSound
  • KazariEX
  • baiwusanyu-c

Poem

I thump my paw—hot code anew,
Slots hop along, from foo to view.
If jobs are spent, I’ll skip the run,
No ghostly updates, none begun.
With tidy hops through nested burrows,
I ship this patch—carrot kudos! 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch edison/fix/nestedTeleportHmr

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@edison1105 edison1105 changed the title fix(hmr): prevent updating unmounting instance during rerender fix(hmr): prevent updating unmounting component during rerender Aug 18, 2025
@edison1105 edison1105 changed the title fix(hmr): prevent updating unmounting component during rerender fix(hmr): prevent updating unmounting component during HMR rerender Aug 18, 2025
@edison1105 edison1105 marked this pull request as ready for review August 18, 2025 03:01
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 (4)
packages/runtime-core/src/hmr.ts (2)

99-103: Nit: prefer explicit, null-safe flag handling for readability.

Relying on JS bitwise coercion of undefined to 0 works but is implicit. Reading flags via ?. and ?? 0 makes the intent clearer and safer if job or flags are absent.

Apply this diff for clarity:

-    // #13771 don't update if the job is already disposed
-    if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
-      instance.update()
-    }
+    // #13771 don't update if the job is already disposed
+    const flags = instance.job?.flags ?? 0
+    if ((flags & SchedulerJobFlags.DISPOSED) === 0) {
+      instance.update()
+    }

98-104: Optional: ensure isHmrUpdating is always reset with try/finally.

If an exception occurs during instance.update(), isHmrUpdating may remain true until the wrapper logs and returns, which could have side effects on subsequent updates. Using try/finally localizes the guarantee.

Apply this diff:

-    // this flag forces child components with slot content to update
-    isHmrUpdating = true
-    // #13771 don't update if the job is already disposed
-    const flags = instance.job?.flags ?? 0
-    if ((flags & SchedulerJobFlags.DISPOSED) === 0) {
-      instance.update()
-    }
-    isHmrUpdating = false
+    // this flag forces child components with slot content to update
+    isHmrUpdating = true
+    try {
+      // #13771 don't update if the job is already disposed
+      const flags = instance.job?.flags ?? 0
+      if ((flags & SchedulerJobFlags.DISPOSED) === 0) {
+        instance.update()
+      }
+    } finally {
+      isHmrUpdating = false
+    }
packages/runtime-core/__tests__/hmr.spec.ts (2)

931-931: Fix record registration id for App to match its __hmrId.

App declares __hmrId = appId but is registered under parentId. While not breaking this test, it’s inconsistent and can confuse HMR bookkeeping.

Apply this diff:

-    createRecord(parentId, App)
+    createRecord(appId, App)

898-939: Consider adding a regression test for nested Teleport + conditional + HMR.

The root cause in #13771 involved a nested Teleport with a conditional (v-if) where HMR triggered an update as the component was unmounting, leading to DOM anchor lookup failures. The disposed-job guard should prevent that. A focused test will lock the fix in.

You can add a test like this (positions near the Teleport test block):

test('rerender does not update disposed job in nested Teleport with conditional', async () => {
  const root = nodeOps.createElement('div')
  const target = nodeOps.createElement('div')

  const childId = 'teleport-nested-conditional-child'
  const Child: ComponentOptions = {
    __hmrId: childId,
    render: () => h('div', 'child')
  }
  createRecord(childId, Child)

  const parentId = 'teleport-nested-conditional-parent'
  const Parent: ComponentOptions = {
    __hmrId: parentId,
    data() {
      return { show: true, target }
    },
    components: { Child },
    render: compileToFunction(`
      <div>
        <teleport :to="target">
          <Child v-if="show" />
        </teleport>
      </div>
    `)
  }
  createRecord(parentId, Parent)

  render(h(Parent), root)
  expect(serializeInner(root)).toBe(`<div><!--teleport start--><!--teleport end--></div>`)
  expect(serializeInner(target)).toBe(`<div>child</div>`)

  // start unmount
  // toggle off, then trigger HMR on Child in the same flush window
  ;(root.children[0] as TestElement) // no-op to keep types happy
  // mutate state by re-rendering Parent
  rerender(
    parentId,
    compileToFunction(`
      <div>
        <teleport :to="target">
          <!-- v-if false -->
        </teleport>
      </div>
    `)
  )
  // HMR update on child while it's being unmounted
  rerender(childId, () => h('div', 'updated'))

  // expect no crash and teleported content empty
  expect(serializeInner(root)).toBe(`<div><!--teleport start--><!--teleport end--></div>`)
  expect(serializeInner(target)).toBe(``)
})

If you’d like, I can adapt this to match the test harness’ preferred patterns (e.g., using nextTick/triggerEvent as needed).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c875019 and f634421.

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/hmr.spec.ts (1 hunks)
  • packages/runtime-core/src/hmr.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/runtime-core/__tests__/hmr.spec.ts (2)
packages/runtime-core/src/component.ts (1)
  • ComponentOptions (276-276)
packages/runtime-test/src/serialize.ts (1)
  • serializeInner (22-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules
  • GitHub Check: Header rules
  • GitHub Check: Pages changed
🔇 Additional comments (3)
packages/runtime-core/src/hmr.ts (2)

10-10: Importing SchedulerJobFlags is appropriate for the disposed-job guard.

The added import from ./scheduler is correct and scoped to the new check.


99-103: Good fix: skip HMR updates for disposed jobs to avoid crashes during unmount.

Conditionally calling instance.update() based on SchedulerJobFlags.DISPOSED addresses the race where an unmounting instance was being updated, fixing #13771.

packages/runtime-core/__tests__/hmr.spec.ts (1)

898-939: Test adds valuable coverage for HMR rerender propagation through forwarded slots.

This verifies that rerendering the child propagates correctly through a parent that forwards its slot, which is a realistic nesting scenario.

@edison1105 edison1105 added ready to merge The PR is ready to be merged. scope: hmr 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Aug 18, 2025
Copy link

pkg-pr-new bot commented Aug 18, 2025

Open in StackBlitz

@vue/compiler-core

npm i https://pkg.pr.new/@vue/compiler-core@13775

@vue/compiler-dom

npm i https://pkg.pr.new/@vue/compiler-dom@13775

@vue/compiler-sfc

npm i https://pkg.pr.new/@vue/compiler-sfc@13775

@vue/compiler-ssr

npm i https://pkg.pr.new/@vue/compiler-ssr@13775

@vue/reactivity

npm i https://pkg.pr.new/@vue/reactivity@13775

@vue/runtime-core

npm i https://pkg.pr.new/@vue/runtime-core@13775

@vue/runtime-dom

npm i https://pkg.pr.new/@vue/runtime-dom@13775

@vue/server-renderer

npm i https://pkg.pr.new/@vue/server-renderer@13775

@vue/shared

npm i https://pkg.pr.new/@vue/shared@13775

vue

npm i https://pkg.pr.new/vue@13775

@vue/compat

npm i https://pkg.pr.new/@vue/compat@13775

commit: f634421

Copy link

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 101 kB 38.4 kB 34.6 kB
vue.global.prod.js 159 kB 58.6 kB 52.2 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.6 kB 18.2 kB 16.7 kB
createApp 54.6 kB 21.2 kB 19.4 kB
createSSRApp 58.8 kB 23 kB 20.9 kB
defineCustomElement 59.6 kB 22.8 kB 20.8 kB
overall 68.7 kB 26.4 kB 24.1 kB

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: hmr
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Teleport] Cannot find the target node when teleport nested and set conditional rendering
1 participant