Skip to content

Conversation

@edison1105
Copy link
Member

@edison1105 edison1105 commented Nov 24, 2025

close #14127

Summary by CodeRabbit

  • Bug Fixes
    • Improved Hot Module Replacement so cached static text nodes are correctly replaced during HMR, ensuring static text updates while dynamic interpolations continue to update.
  • Tests
    • Added an HMR test that verifies cached static text updates and preservation of dynamic interpolations across consecutive HMR updates.

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Walkthrough

During DEV/HMR updates, cached Text vnodes are given a per-node index and, when updated, are replaced by newly created text nodes instead of reusing the cached DOM node; traversal logic accounts for Fragment offsets. A test verifying cached text HMR updates was added.

Changes

Cohort / File(s) Summary
Renderer HMR text handling
packages/runtime-core/src/renderer.ts
Adds DEV-only HMR path: cached Text vnodes (PatchFlags.CACHED) receive a per-node index (__elIndex) during traversal; during HMR updates the runtime creates a new text node and replaces the old element instead of calling hostSetText. Fragment offset (i + (n1.type === Fragment ? 1 : 0)) is used when computing __elIndex.
HMR test
packages/runtime-core/__tests__/hmr.spec.ts
Adds test "update cached text nodes" that renders a component with static cached text and a dynamic count, performs state update and two HMR rerenders, and asserts the static text updates while the dynamic count is preserved.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Dev (HMR)
    participant Runtime as Runtime Core
    participant DOM as Host DOM

    Note over Dev,Runtime: Normal runtime update (non-cached or no HMR)
    Dev->>Runtime: trigger update
    Runtime->>DOM: hostSetText(existingEl, newText)

    Note over Dev,Runtime: DEV/HMR update for cached Text vnode
    Dev->>Runtime: HMR rerender with PatchFlags.CACHED Text vnode
    Runtime->>Runtime: detect cached text & HMR active
    Runtime->>Runtime: compute per-node index (__elIndex) (i + FragmentOffset)
    Runtime->>Runtime: create new text node
    Runtime->>DOM: replace old element with new text node
    Runtime->>DOM: continue patching dynamic children
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review attentions:
    • Correctness of __elIndex computation and Fragment offset handling in renderer.ts.
    • Ensure DEV/HMR-only branch is guarded and does not affect production.
    • Stability of the new HMR test with nested fragments or lists.

Possibly related PRs

Suggested labels

ready to merge

Suggested reviewers

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

Poem

🐇 I hopped through vnodes, soft and spry,
Cached words clung tight, refusing to fly.
HMR tapped — I swapped the twig for new,
Fresh letters sprang where old ones grew.
A rabbit cheers: hot reloads true.

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 'fix(hmr): handle cached text node update' accurately describes the main change in the changeset, which focuses on fixing HMR behavior for cached text nodes.
Linked Issues check ✅ Passed The PR changes implement HMR handling for cached text nodes [#14127], adding logic to detect and replace cached text during updates and test coverage to verify the fix works correctly.
Out of Scope Changes check ✅ Passed All changes are scoped to HMR handling for cached text nodes in the renderer and related test coverage; no unrelated modifications were introduced.
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
  • Commit unit tests in branch edison/fix/14127

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

@github-actions
Copy link

github-actions bot commented Nov 24, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 103 kB (+29 B) 39.1 kB (+22 B) 35.2 kB (+40 B)
vue.global.prod.js 161 kB (+29 B) 59 kB (+28 B) 52.5 kB (-14 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47 kB (+29 B) 18.3 kB (+14 B) 16.8 kB (+14 B)
createApp 55.1 kB (+29 B) 21.4 kB (+17 B) 19.6 kB (+15 B)
createSSRApp 59.3 kB (+29 B) 23.2 kB (+21 B) 21.1 kB (+6 B)
defineCustomElement 60.7 kB (+29 B) 23.1 kB (+17 B) 21.1 kB (+20 B)
overall 69.5 kB (+29 B) 26.7 kB (+15 B) 24.3 kB (+49 B)

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 24, 2025

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14134
npm i https://pkg.pr.new/@vue/compiler-core@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14134
npm i https://pkg.pr.new/@vue/compiler-dom@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14134
npm i https://pkg.pr.new/@vue/compiler-sfc@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14134
npm i https://pkg.pr.new/@vue/compiler-ssr@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14134
npm i https://pkg.pr.new/@vue/reactivity@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14134
npm i https://pkg.pr.new/@vue/runtime-core@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14134
npm i https://pkg.pr.new/@vue/runtime-dom@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14134
npm i https://pkg.pr.new/@vue/server-renderer@14134
yarn add https://pkg.pr.new/@vue/[email protected]

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14134
npm i https://pkg.pr.new/@vue/shared@14134
yarn add https://pkg.pr.new/@vue/[email protected]

vue

pnpm add https://pkg.pr.new/vue@14134
npm i https://pkg.pr.new/vue@14134
yarn add https://pkg.pr.new/[email protected]

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14134
npm i https://pkg.pr.new/@vue/compat@14134
yarn add https://pkg.pr.new/@vue/[email protected]

commit: 8bf4f30

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25ebe3a and c70913b.

📒 Files selected for processing (1)
  • packages/runtime-core/src/renderer.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-core/src/renderer.ts (1)
packages/runtime-core/src/hmr.ts (1)
  • isHmrUpdating (15-15)
🔇 Additional comments (1)
packages/runtime-core/src/renderer.ts (1)

501-501: LGTM: const to let change enables reassignment.

The change from const to let is necessary to allow reassignment of el in the HMR conditional branch below.

@edison1105 edison1105 marked this pull request as draft November 24, 2025 13:46
@edison1105 edison1105 marked this pull request as ready for review November 25, 2025 01:19
@edison1105 edison1105 changed the title fix(hmr): handle text node creation during HMR updates for cached nodes fix(hmr): handle cached text node update Nov 25, 2025
@edison1105 edison1105 added scope: hmr 🔨 p3-minor-bug Priority 3: this fixes a bug, but is an edge case that only affects very specific usage. labels Nov 25, 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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97861cd and 6c11c4d.

📒 Files selected for processing (2)
  • packages/runtime-core/__tests__/hmr.spec.ts (1 hunks)
  • packages/runtime-core/src/renderer.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/runtime-core/src/renderer.ts (3)
packages/runtime-core/src/hmr.ts (1)
  • isHmrUpdating (15-15)
packages/runtime-core/src/vnode.ts (2)
  • Text (69-69)
  • Fragment (63-68)
packages/runtime-core/src/index.ts (2)
  • Text (113-113)
  • Fragment (113-113)
🔇 Additional comments (2)
packages/runtime-core/__tests__/hmr.spec.ts (1)

1044-1095: LGTM! Comprehensive test coverage for cached text node HMR updates.

The test effectively validates the fix for issue #14127 by:

  • Verifying initial render and state updates work correctly
  • Performing two consecutive HMR updates to ensure static text changes are reflected
  • Confirming dynamic state (count) is preserved across HMR updates

The test structure follows existing patterns and thoroughly exercises the interaction between HMR and cached text nodes.

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

2519-2530: Well-designed approach to prevent detached DOM node references.

The logic correctly addresses the issue:

  • Non-cached text nodes inherit el from the previous node (preserving existing behavior)
  • Cached text nodes store __elIndex instead to avoid retaining references to detached DOM nodes
  • The Fragment offset (+1) correctly accounts for the fragment start anchor at index 0

This index-based approach elegantly avoids stale DOM references while enabling HMR to locate and replace cached text nodes by their position in the parent's child list.

Copilot AI review requested due to automatic review settings December 18, 2025 08:18
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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c11c4d and 8bf4f30.

📒 Files selected for processing (1)
  • packages/runtime-core/src/renderer.ts (2 hunks)
⏰ 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). (1)
  • GitHub Check: Agent
🔇 Additional comments (1)
packages/runtime-core/src/renderer.ts (1)

2519-2530: Logic for cached text node index storage looks correct.

The implementation properly distinguishes between cached and non-cached text nodes:

  • Non-cached text nodes inherit el from the previous vnode (line 2522)
  • Cached text nodes store __elIndex for HMR replacement (lines 2525-2528)

The Fragment offset calculation (n1.type === Fragment ? 1 : 0) correctly accounts for the fragment's start anchor node, ensuring the index maps to the correct DOM child position when the parent is a Fragment.

Comment on lines +503 to +523
// We don't inherit el for cached text nodes in `traverseStaticChildren`
// to avoid retaining detached DOM nodes. However, the text node may be
// changed during HMR. In this case we need to replace the old text node
// with the new one.
if (
__DEV__ &&
isHmrUpdating &&
n2.patchFlag === PatchFlags.CACHED &&
'__elIndex' in n1
) {
const childNodes = __TEST__
? container.children
: container.childNodes
const newChild = hostCreateText(n2.children as string)
const oldChild =
childNodes[((n2 as any).__elIndex = (n1 as any).__elIndex)]
hostInsert(newChild, container, oldChild)
hostRemove(oldChild)
} else {
hostSetText(el, n2.children as string)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the target code in renderer.ts around lines 503-523
head -n 530 packages/runtime-core/src/renderer.ts | tail -n 40

Repository: vuejs/core

Length of output: 1304


🏁 Script executed:

#!/bin/bash
# Search for traverseStaticChildren function and __TEST__ usage with container.children
rg -n "traverseStaticChildren|container\.children" packages/runtime-core/src/renderer.ts -A 5 -B 2

Repository: vuejs/core

Length of output: 2469


🏁 Script executed:

#!/bin/bash
# Search for processText function to see full context
rg -n "const processText" packages/runtime-core/src/renderer.ts -A 30

Repository: vuejs/core

Length of output: 1333


Critical: n2.el is not updated to reference the new text node.

After creating newChild (line 516) and removing oldChild (line 520), n2.el still points to the old, now-detached DOM node assigned on line 501. This means the vnode holds a stale reference to a removed element, which breaks subsequent updates and operations on this text node. Add n2.el = newChild after creating the new child.

Additionally, the __TEST__ path on line 514 uses container.children, which only returns element nodes, not text nodes. Since this branch handles text nodes, use container.childNodes instead to match the production behavior.

🤖 Prompt for AI Agents
In packages/runtime-core/src/renderer.ts around lines 503 to 523, the HMR path
replaces the old text node but fails to update the vnode reference and uses
container.children in the test branch; after creating newChild (line ~516) set
n2.el = newChild so the vnode points to the new DOM node, and change the
__TEST__ branch to use container.childNodes (not container.children) so text
nodes are found in tests the same as production; then proceed to insert and
remove oldChild as existing code does.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes an issue where cached static text nodes were not being correctly updated during Hot Module Replacement (HMR). The fix introduces a mechanism to track the DOM index of cached text nodes and replace them during HMR updates, rather than attempting to update them in place (which fails because their el reference is intentionally not inherited to avoid retaining detached DOM nodes).

Key Changes

  • Introduced __elIndex property on cached text VNodes to track their position in the parent's child nodes
  • Added special handling in processText to replace (rather than update) cached text nodes during HMR
  • Added comprehensive test coverage for cached text node updates across multiple HMR cycles

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/runtime-core/src/renderer.ts Modified processText to handle cached text node replacement during HMR, and updated traverseStaticChildren to cache the DOM index for cached text nodes
packages/runtime-core/__tests__/hmr.spec.ts Added test case verifying cached text nodes update correctly during HMR while preserving dynamic content and handling consecutive HMR updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@edison1105 edison1105 merged commit 69ce3c7 into main Dec 18, 2025
22 checks passed
@edison1105 edison1105 deleted the edison/fix/14127 branch December 18, 2025 08: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. scope: hmr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hot module reload causing error or not updating.

2 participants