Skip to content

fix(compiler-core): preserve vnode lifecycle in stable v-for - #11682

Merged
edison1105 merged 7 commits into
vuejs:mainfrom
edison1105:fix/9239
Aug 5, 2026
Merged

fix(compiler-core): preserve vnode lifecycle in stable v-for#11682
edison1105 merged 7 commits into
vuejs:mainfrom
edison1105:fix/9239

Conversation

@edison1105

@edison1105 edison1105 commented Aug 22, 2024

Copy link
Copy Markdown
Member

close #9239
close #12569

I’m not sure if this change is correct. Here is my understanding:
When an element uses ref, vnodeHook, or custom directives, the patchFlag should be NEED_PATCH. However, when they are used together with v-for, the patchFlag gets lost.
related commit 1c9a481

playground
playground with this PR

The following problems will be fixed

The last two are from #9240 (comment) created by skirtles-code

Summary by CodeRabbit

  • Bug Fixes

    • Improved update detection for elements inside v-for loops using refs, custom directives, and vnode hooks.
    • Preserved required updates for stable repeated content, including directive and lifecycle hook behavior.
    • Improved cleanup of refs and unmount callbacks when repeated or conditional content is removed.
    • Corrected update handling when other patch information is already present.
  • Tests

    • Added coverage for patching, ref cleanup, callback handling, and update-hook ordering across these scenarios.

@edison1105 edison1105 changed the title fix(compiler-sfc): properly analyze patch flag of vFor fix(compiler-sfc): properly analyze patch flag of vFor + (ref or vnodeHook or dir) Aug 22, 2024
@github-actions

github-actions Bot commented Aug 22, 2024

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 108 kB 40.7 kB 36.5 kB
vue.global.prod.js 167 kB (+305 B) 60.9 kB (+124 B) 54.2 kB (+165 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 49.7 kB 19.3 kB 17.7 kB
createApp 57.8 kB 22.3 kB 20.4 kB
createSSRApp 62.5 kB 24.3 kB 22.1 kB
defineCustomElement 64.1 kB 24.3 kB 22.1 kB
overall 72.1 kB 27.5 kB 25.1 kB

@pkg-pr-new

pkg-pr-new Bot commented Aug 22, 2024

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

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

@vue/compiler-dom

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

@vue/compiler-sfc

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

@vue/compiler-ssr

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

@vue/reactivity

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

@vue/runtime-core

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

@vue/runtime-dom

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

@vue/server-renderer

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

@vue/shared

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

vue

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

@vue/compat

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

commit: a6e60cb

@coderabbitai

coderabbitai Bot commented May 14, 2025

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The compiler now separates patch requirements from block requirements for stable v-for elements. The v-for transform preserves required blocks and adds NEED_PATCH where needed. Tests cover refs, directives, vnode hooks, and lifecycle cleanup.

Changes

Compiler patch flag analysis

Layer / File(s) Summary
Patch flag detection
packages/compiler-core/src/ast.ts, packages/compiler-core/src/transforms/transformElement.ts
VNodeCall records needsPatch and isBlockRequired. Element transformation tracks refs, vnode hooks, runtime directives, and block requirements separately.
Stable v-for block selection
packages/compiler-core/src/transforms/vFor.ts
Stable v-for children retain required blocks. Non-block children that require patching receive NEED_PATCH.
Compiler and runtime validation
packages/compiler-core/__tests__/transforms/transformElement.spec.ts, packages/vue/__tests__/index.spec.ts
Tests cover patch flags, block status, ref cleanup, function-ref nulling, directive and vnode unmount hooks, and update-hook ordering.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant transformElement
  participant buildProps
  participant vFor
  transformElement->>buildProps: analyze refs, hooks, directives, and block requirements
  buildProps-->>transformElement: return needsPatch and isBlockRequired
  transformElement->>vFor: provide annotated child VNode call
  vFor->>vFor: retain required blocks or add NEED_PATCH
Loading

Possibly related PRs

  • vuejs/core#12411: Modifies runtime fragment diffing for stable v-for and fragment patching, while this change modifies compiler block and patch-flag generation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address ref cleanup in v-for and directive unmounted hooks, covering the objectives in [#9239] and [#12569].
Out of Scope Changes check ✅ Passed The compiler changes and lifecycle tests remain within the stated v-for patch-flag and cleanup objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main compiler change: preserving vnode lifecycle behavior in stable v-for rendering.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@edison1105

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

1 similar comment
@edison1105

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Track lifecycle-sensitive vnodes when stable v-for converts block roots
to regular vnodes, while preserving blocks required for parent-before-child
hook ordering.

@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.

Actionable comments posted: 1

🧹 Nitpick comments (6)
packages/compiler-core/__tests__/transforms/transformElement.spec.ts (2)

1089-1096: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add prefixIdentifiers for consistency with the sibling tests.

The other new tests pass { prefixIdentifiers: true }. This test omits it, so it exercises a different codegen mode. The assertion still holds, but the difference is not intentional-looking.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/compiler-core/__tests__/transforms/transformElement.spec.ts` around
lines 1089 - 1096, Add `{ prefixIdentifiers: true }` to the
`parseWithForTransform` options in the `preserve block for stable v-for + custom
directive with children` test, matching the sibling tests while preserving its
existing assertions.

1098-1112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the camelCase hook alias.

This test covers @vue:before-update. transformOn also accepts @vue:beforeUpdate, and the block-requirement check in transformElement.ts (lines 599-604) matches only the hyphenated form. Add a test with @vue:beforeUpdate to lock the behavior after that check is normalized.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/compiler-core/__tests__/transforms/transformElement.spec.ts` around
lines 1098 - 1112, Extend the existing “preserve block for stable v-for +
before-update vnode hook” test coverage with a camelCase `@vue:beforeUpdate`
variant, using the same transform options and assertions for
`getVForChild(node)`, `isBlock`, and `patchFlag`. This should validate the
normalized block-requirement check in `transformElement.ts` for both hook
aliases.
packages/vue/__tests__/index.spec.ts (2)

8-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The new Function hint is a false positive here.

Every call site passes a literal template that is written in this test file, so no untrusted input reaches compile. The helper mirrors the production compileToFunction in packages/vue/src/index.ts. No change is needed.

One optional simplification: Vue.compile in the full build already applies the same steps and sets _rc. Reusing it would remove the duplicated harness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/__tests__/index.spec.ts` around lines 8 - 16, No code change is
required for the new Function usage in compileToFunction because all test
templates are literals and the helper intentionally mirrors the production
implementation. Optionally simplify the test harness by reusing Vue.compile,
which already performs compilation and sets _rc.

Source: Linters/SAST tools


407-412: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the full call sequence for the function ref.

values[values.length - 1] passes even if the ref function is invoked extra times during the update. An exact assertion locks the expected sequence of one element followed by one null.

♻️ Proposed stricter assertion
       app.mount(container)
-      expect(values).toHaveLength(1)
-      expect(values[0]).toBeInstanceOf(HTMLDivElement)
+      expect(values).toHaveLength(1)
+      expect(values[0]).toBeInstanceOf(HTMLDivElement)
 
       show.value = false
       await nextTick()
-      expect(values[values.length - 1]).toBeNull()
+      expect(values).toHaveLength(2)
+      expect(values[1]).toBeNull()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/vue/__tests__/index.spec.ts` around lines 407 - 412, Strengthen the
assertions in the function-ref test around the show.value toggle so it verifies
the complete values sequence, exactly one element reference followed by one
null, rather than checking only the last entry. Keep the existing initial
element validation and nextTick update flow unchanged.
packages/compiler-core/src/transforms/vFor.ts (1)

211-217: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the patch-flag merge.

The undefined branch exists only to satisfy the type of |. A nullish default removes the branch.

♻️ Proposed simplification
             if (childBlock.needsPatch) {
-              childBlock.patchFlag =
-                childBlock.patchFlag === undefined
-                  ? PatchFlags.NEED_PATCH
-                  : ((childBlock.patchFlag |
-                      PatchFlags.NEED_PATCH) as PatchFlags)
+              childBlock.patchFlag =
+                ((childBlock.patchFlag ?? 0) | PatchFlags.NEED_PATCH) as PatchFlags
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/compiler-core/src/transforms/vFor.ts` around lines 211 - 217, In the
childBlock patch-flag update within the vFor transform, simplify the undefined
handling by defaulting childBlock.patchFlag to the empty numeric flag before
OR-ing PatchFlags.NEED_PATCH. Remove the conditional branch while preserving the
resulting NEED_PATCH flag for both undefined and existing patch flags.
packages/compiler-core/src/transforms/transformElement.ts (1)

760-765: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

needsPatch can stay true after transformElement adds more patch flags.

buildProps computes needsPatch from the props-only patchFlag. transformElement can later add TEXT or DYNAMIC_SLOTS to the same flag (lines 158, 181, 194). For a stable v-for child with a vnode hook and a dynamic text child, needsPatch stays true, so vFor ORs NEED_PATCH onto TEXT when it demotes the block. The result is still correct, because any non-zero patch flag makes the parent block track the vnode, but NEED_PATCH is then redundant in the generated code. Consider clearing needsPatch in transformElement when the final patchFlag already requires patching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/compiler-core/src/transforms/transformElement.ts` around lines 760 -
765, Recompute or clear needsPatch in transformElement after adding TEXT or
DYNAMIC_SLOTS to patchFlag, so it is false when the final patchFlag is already
non-zero and does not require NEED_PATCH. Update the needsPatch handling near
the final patchFlag logic while preserving NEED_PATCH for cases whose completed
patchFlag still indicates no other patching.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/compiler-core/src/transforms/transformElement.ts`:
- Around line 599-604: The before-update block check in transformElement must
recognize both hyphenated and camelCase aliases. In
packages/compiler-core/src/transforms/transformElement.ts:599-604, normalize the
static arg with camelize before comparing it to vue:beforeUpdate; in
packages/compiler-core/__tests__/transforms/transformElement.spec.ts:1098-1112,
add a sibling test using `@vue`:beforeUpdate and assert child.isBlock === true.

---

Nitpick comments:
In `@packages/compiler-core/__tests__/transforms/transformElement.spec.ts`:
- Around line 1089-1096: Add `{ prefixIdentifiers: true }` to the
`parseWithForTransform` options in the `preserve block for stable v-for + custom
directive with children` test, matching the sibling tests while preserving its
existing assertions.
- Around line 1098-1112: Extend the existing “preserve block for stable v-for +
before-update vnode hook” test coverage with a camelCase `@vue:beforeUpdate`
variant, using the same transform options and assertions for
`getVForChild(node)`, `isBlock`, and `patchFlag`. This should validate the
normalized block-requirement check in `transformElement.ts` for both hook
aliases.

In `@packages/compiler-core/src/transforms/transformElement.ts`:
- Around line 760-765: Recompute or clear needsPatch in transformElement after
adding TEXT or DYNAMIC_SLOTS to patchFlag, so it is false when the final
patchFlag is already non-zero and does not require NEED_PATCH. Update the
needsPatch handling near the final patchFlag logic while preserving NEED_PATCH
for cases whose completed patchFlag still indicates no other patching.

In `@packages/compiler-core/src/transforms/vFor.ts`:
- Around line 211-217: In the childBlock patch-flag update within the vFor
transform, simplify the undefined handling by defaulting childBlock.patchFlag to
the empty numeric flag before OR-ing PatchFlags.NEED_PATCH. Remove the
conditional branch while preserving the resulting NEED_PATCH flag for both
undefined and existing patch flags.

In `@packages/vue/__tests__/index.spec.ts`:
- Around line 8-16: No code change is required for the new Function usage in
compileToFunction because all test templates are literals and the helper
intentionally mirrors the production implementation. Optionally simplify the
test harness by reusing Vue.compile, which already performs compilation and sets
_rc.
- Around line 407-412: Strengthen the assertions in the function-ref test around
the show.value toggle so it verifies the complete values sequence, exactly one
element reference followed by one null, rather than checking only the last
entry. Keep the existing initial element validation and nextTick update flow
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f115176-c5ff-46ba-8a7d-2e87a7712f1f

📥 Commits

Reviewing files that changed from the base of the PR and between 6f0803f and 88d2699.

📒 Files selected for processing (5)
  • packages/compiler-core/__tests__/transforms/transformElement.spec.ts
  • packages/compiler-core/src/ast.ts
  • packages/compiler-core/src/transforms/transformElement.ts
  • packages/compiler-core/src/transforms/vFor.ts
  • packages/vue/__tests__/index.spec.ts

Comment thread packages/compiler-core/src/transforms/transformElement.ts
Normalize before-update hook aliases when preserving blocks and avoid
emitting redundant NEED_PATCH flags when another patch flag is present.
@edison1105 edison1105 changed the title fix(compiler-sfc): properly analyze patch flag of vFor + (ref or vnodeHook or dir) fix(compiler-core): preserve vnode lifecycle in stable v-for Aug 5, 2026
@edison1105

Copy link
Copy Markdown
Member Author

/ecosystem-ci run

@vuejs vuejs deleted a comment from edison1105 Aug 5, 2026
@vue-bot

vue-bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📝 Ran ecosystem CI: Open

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

@edison1105
edison1105 merged commit 02421cd into vuejs:main Aug 5, 2026
14 checks passed
@edison1105
edison1105 deleted the fix/9239 branch August 5, 2026 01:43
zhiyuanzmj added a commit to vuejs/vue-jsx-vapor that referenced this pull request Aug 11, 2026
ahacop added a commit to changebot-ai/widgets that referenced this pull request Aug 24, 2026
[cb skip]

vue is a dev dependency of @changebot/widgets-vue. The peer range
stays ^3.0.0.

== [3.5.41](vuejs/core@v3.5.40...v3.5.41) (2026-08-05)

== Bug Fixes

* **compiler-core:** preserve vnode lifecycle in stable v-for ([#11682](vuejs/core#11682)) ([02421cd](vuejs/core@02421cd)), closes [#9239](vuejs/core#9239) [#12569](vuejs/core#12569)
* **compiler-sfc:** handle transformed template AST after cache invalidation ([#15136](vuejs/core#15136)) ([a4440c0](vuejs/core@a4440c0)), closes [#15126](vuejs/core#15126) [#15128](vuejs/core#15128)
* **custom-element:** preserve nested async mount order ([#15154](vuejs/core#15154)) ([71f9ff5](vuejs/core@71f9ff5)), closes [#15153](vuejs/core#15153)
* **custom-element:** warn when props override native properties ([#12125](vuejs/core#12125)) ([22b53ea](vuejs/core@22b53ea)), closes [#12124](vuejs/core#12124)
* **runtime-core:** avoid re-fetching resource props with unchanged values ([#15105](vuejs/core#15105)) ([a7513a1](vuejs/core@a7513a1))
* **runtime-core:** restore SSR setup state when handling async setup result ([#15114](vuejs/core#15114)) ([b6191cb](vuejs/core@b6191cb)), closes [#15113](vuejs/core#15113)
* **scheduler:** avoid stack overflow when flushing very large post cb arrays ([#15143](vuejs/core#15143)) ([c04a45d](vuejs/core@c04a45d)), closes [#15142](vuejs/core#15142)
* **slots:** handle nullish v-bind slot props ([#15177](vuejs/core#15177)) ([2506700](vuejs/core@2506700))
* **ssr:** normalize hidden states during hydration ([#13125](vuejs/core#13125)) ([4e467d7](vuejs/core@4e467d7))
* **transition:** support transition to teleport component child ([#11959](vuejs/core#11959)) ([77061fe](vuejs/core@77061fe)), closes [#11910](vuejs/core#11910)
* **types:** preserve defineModel inference with factory defaults ([#15097](vuejs/core#15097)) ([0516c43](vuejs/core@0516c43)), closes [#15096](vuejs/core#15096)
* **v-model:** preserve text input before hydration ([#14411](vuejs/core#14411)) ([2468464](vuejs/core@2468464)), closes [#14403](vuejs/core#14403)

== [3.5.40](vuejs/core@v3.5.39...v3.5.40) (2026-07-16)

== Bug Fixes

* **compiler-core:** avoid leaking slot branch keys ([#15051](vuejs/core#15051)) ([20c9d26](vuejs/core@20c9d26)), closes [#15048](vuejs/core#15048)
* **hydration:** pass namespace when patching dynamic props ([#15082](vuejs/core#15082)) ([e0d2723](vuejs/core@e0d2723)), closes [#15081](vuejs/core#15081) [#15050](vuejs/core#15050)
* **reactivity:** handle effect removal during scope stop ([#15084](vuejs/core#15084)) ([378f978](vuejs/core@378f978)), closes [#15083](vuejs/core#15083)
* **runtime-core:** skip lazy hydration for detached roots ([#15092](vuejs/core#15092)) ([97f3525](vuejs/core@97f3525)), closes [#15091](vuejs/core#15091)
* **runtime-core:** unwind dangling blocks when slot content throws ([#15071](vuejs/core#15071)) ([ddc132d](vuejs/core@ddc132d)), closes [#15070](vuejs/core#15070)
* **runtime-dom:** respect current select model type ([#15010](vuejs/core#15010)) ([eb89e93](vuejs/core@eb89e93)), closes [#15009](vuejs/core#15009)
* **server-renderer:** handle errors in optimized component renders ([#12601](vuejs/core#12601)) ([474907c](vuejs/core@474907c)), closes [#12575](vuejs/core#12575)
* **server-renderer:** remove package dependency cycle ([#15063](vuejs/core#15063)) ([4d35eca](vuejs/core@4d35eca))
* **shared:** prevent SSR comment escaping from creating closing delimiters ([#15045](vuejs/core#15045)) ([bd962bb](vuejs/core@bd962bb))
* **types:** don't constrain component $el type to Element ([#15040](vuejs/core#15040)) ([164460a](vuejs/core@164460a))

== [3.5.39](vuejs/core@v3.5.38...v3.5.39) (2026-06-25)


== Bug Fixes

* **compiler-core:** correct filter rewrite recursion ([#14959](vuejs/core#14959)) ([be7ce31](vuejs/core@be7ce31))
* **hydration:** force patch dynamic props when hydrating ([#9083](vuejs/core#9083)) ([024cf06](vuejs/core@024cf06)), closes [#9033](vuejs/core#9033)
* **hydration:** respect data-allow-mismatch on conditional branches ([#12801](vuejs/core#12801)) ([164af63](vuejs/core@164af63)), closes [#12782](vuejs/core#12782)
* **reactivity:** avoid triggering effects when set fails ([#14964](vuejs/core#14964)) ([e450973](vuejs/core@e450973))
* **runtime-core:** handle non-isomorphic block element update ([#15002](vuejs/core#15002)) ([932ddd0](vuejs/core@932ddd0)), closes [#6385](vuejs/core#6385)
* **runtime-core:** normalize function children for elements and Teleport ([#9108](vuejs/core#9108)) ([2f374cd](vuejs/core@2f374cd)), closes [#9107](vuejs/core#9107)
* **runtime-core:** pause tracking when invoking function refs ([#14985](vuejs/core#14985)) ([3ac052b](vuejs/core@3ac052b))
* **runtime-core:** preserve once event listener name ([#8341](vuejs/core#8341)) ([87b73b6](vuejs/core@87b73b6)), closes [#8342](vuejs/core#8342)
* **runtime-dom:** preserve option modifier event names ([#8338](vuejs/core#8338)) ([4b659e6](vuejs/core@4b659e6)), closes [#8334](vuejs/core#8334)
* **ssr:** dedupe inherited scope ids during vnode rendering ([#15005](vuejs/core#15005)) ([027da6b](vuejs/core@027da6b)), closes [#12159](vuejs/core#12159) [#12175](vuejs/core#12175)
* **ssr:** resolve nested async teleport content ([#9431](vuejs/core#9431)) ([31d0f23](vuejs/core@31d0f23)), closes [#6207](vuejs/core#6207)
* **teleport:** handle teleport unmount edge case ([#12705](vuejs/core#12705)) ([671997a](vuejs/core@671997a)), closes [#12702](vuejs/core#12702)
* **types:** support named tuple emits ([#12676](vuejs/core#12676)) ([232f402](vuejs/core@232f402)), closes [#12673](vuejs/core#12673)
* **types:** validate defineModel defaults ([#14968](vuejs/core#14968)) ([747f57e](vuejs/core@747f57e)), closes [#14966](vuejs/core#14966)

== [3.5.38](vuejs/core@v3.5.37...v3.5.38) (2026-06-11)

== [3.5.37](vuejs/core@v3.5.36...v3.5.37) (2026-06-11)

== [3.5.36](vuejs/core@v3.5.35...v3.5.36) (2026-06-11)


== Bug Fixes

* **compiler-core:** avoid crash on CDATA at the document root ([#14916](vuejs/core#14916)) ([0ea17e2](vuejs/core@0ea17e2))
* **compiler-core:** prefix dynamic keys on v-memo elements ([#14922](vuejs/core#14922)) ([68e978e](vuejs/core@68e978e)), closes [#14920](vuejs/core#14920)
* **compiler-sfc:** handle vue-ignore on leading intersection/union type ([#14950](vuejs/core#14950)) ([0dcd225](vuejs/core@0dcd225)), closes [#12254](vuejs/core#12254)
* **compiler-sfc:** respect var hoisting in props destructure ([48ad452](vuejs/core@48ad452))
* **reactivity:** preserve watch callback return value when wrapped for `once: true` ([#14902](vuejs/core#14902)) ([450a8a8](vuejs/core@450a8a8))
* **runtime-core:** add dev warning for silent catch in compat mode and fix test description typo ([#14891](vuejs/core#14891)) ([db3e117](vuejs/core@db3e117))
* **runtime-core:** force model update when reverted before sync ([#14897](vuejs/core#14897)) ([7f76378](vuejs/core@7f76378)), closes [#13524](vuejs/core#13524)
* **runtime-core:** skip async component callbacks after unmount ([#14911](vuejs/core#14911)) ([5300ead](vuejs/core@5300ead))
* **transition:** avoid move transition for hidden v-show group children ([#14895](vuejs/core#14895)) ([c11f6ee](vuejs/core@c11f6ee)), closes [#14894](vuejs/core#14894)
* **watch:** trigger immediate callback for empty sources ([#14914](vuejs/core#14914)) ([1f2ca7e](vuejs/core@1f2ca7e)), closes [#14898](vuejs/core#14898)

== [3.5.35](vuejs/core@v3.5.34...v3.5.35) (2026-05-27)


== Bug Fixes

* **compiler-core:** avoid double processing v-for keys with v-memo ([#14861](vuejs/core#14861)) ([34a0ded](vuejs/core@34a0ded)), closes [#14859](vuejs/core#14859)
* **compiler-sfc:** resolve top-level exports from files registered as global types ([#14805](vuejs/core#14805)) ([3d077f2](vuejs/core@3d077f2)), closes [nuxt/nuxt#33694](nuxt/nuxt#33694)
* **runtime-core:** avoid repeated hydration mismatch checks ([#14857](vuejs/core#14857)) ([170fc95](vuejs/core@170fc95)), closes [#14855](vuejs/core#14855)
* **runtime-core:** skip idle persisted transition hooks in keep-alive moves ([#14865](vuejs/core#14865)) ([80fc139](vuejs/core@80fc139)), closes [#14031](vuejs/core#14031)
* **server-renderer:** propagate sync errors from `ssrRenderSuspense` ([#14804](vuejs/core#14804)) ([4760997](vuejs/core@4760997)), closes [nuxt/nuxt#28162](nuxt/nuxt#28162)
* **teleport:** skip child unmount when pending mount discarded ([#14876](vuejs/core#14876)) ([#14877](vuejs/core#14877)) ([584beb1](vuejs/core@584beb1))


== Performance Improvements

* **reactivity:** skip type checks for cached proxies ([#14860](vuejs/core#14860)) ([5734fe9](vuejs/core@5734fe9))
* **runtime-dom:** optimize array event handler dispatch ([#14828](vuejs/core#14828)) ([bb18dc8](vuejs/core@bb18dc8))
* **server-renderer:** avoid materializing iterables in ssrRenderList ([#14821](vuejs/core#14821)) ([1b7a2cc](vuejs/core@1b7a2cc))

== [3.5.34](vuejs/core@v3.5.33...v3.5.34) (2026-05-06)


== Bug Fixes

* **compiler-sfc:** infer Vue ref wrapper types when source is unresolvable ([#14758](vuejs/core#14758)) ([7f46fd4](vuejs/core@7f46fd4)), closes [#14729](vuejs/core#14729)
* **compiler-sfc:** preserve hash hrefs on `<image>` elements ([#14756](vuejs/core#14756)) ([090b2e3](vuejs/core@090b2e3))
* **compiler-sfc:** resolve type re-exports inside declare global ([#14766](vuejs/core#14766)) ([acfffe3](vuejs/core@acfffe3))
* **reactivity:** prevent orphan effect when created in a stopped scope ([#14778](vuejs/core#14778)) ([c8e2d4a](vuejs/core@c8e2d4a)), closes [#14777](vuejs/core#14777)
* **runtime-core:** avoid symbol coercion during props validation ([#8539](vuejs/core#8539)) ([23d4fb5](vuejs/core@23d4fb5)), closes [#8487](vuejs/core#8487)
* **suspense:** avoid DOM leak with out-in transition in v-if fragment ([#14762](vuejs/core#14762)) ([9667e0d](vuejs/core@9667e0d)), closes [#14761](vuejs/core#14761)

== [3.5.33](vuejs/core@v3.5.32...v3.5.33) (2026-04-22)


== Bug Fixes

* **compiler-sfc:** handle nested :deep in selector pseudos ([#14725](vuejs/core#14725)) ([bb9d265](vuejs/core@bb9d265)), closes [#14724](vuejs/core#14724)
* **reactivity:** unlink effect scopes on out-of-order off ([#14734](vuejs/core#14734)) ([e7659be](vuejs/core@e7659be)), closes [#14733](vuejs/core#14733)
* **runtime-dom:** preserve textarea resize dimensions ([#14747](vuejs/core#14747)) ([11fb2fd](vuejs/core@11fb2fd)), closes [#14741](vuejs/core#14741)
* **teleport:** don't move teleport children if not mounted ([#14702](vuejs/core#14702)) ([6a61f44](vuejs/core@6a61f44)), closes [#14701](vuejs/core#14701)
* **transition:** preserve placeholder for conditional explicit default slots ([#14748](vuejs/core#14748)) ([45990ce](vuejs/core@45990ce)), closes [#14727](vuejs/core#14727)



== [3.5.32](vuejs/core@v3.5.31...v3.5.32) (2026-04-03)


== Bug Fixes

* **runtime-core:** prevent currentInstance leak into sibling render during async setup re-entry ([#14668](vuejs/core#14668)) ([f166353](vuejs/core@f166353)), closes [#14667](vuejs/core#14667)
* **teleport:** handle updates before deferred mount ([#14642](vuejs/core#14642)) ([32b44f1](vuejs/core@32b44f1)), closes [#14640](vuejs/core#14640)
* **types:** allow customRef to have different getter/setter types ([#14639](vuejs/core#14639)) ([e20ddb0](vuejs/core@e20ddb0))
* **types:** use private branding for shallowReactive ([#14641](vuejs/core#14641)) ([302c47a](vuejs/core@302c47a)), closes [#14638](vuejs/core#14638) [#14493](vuejs/core#14493)


== Reverts

* Revert "fix(server-renderer): cleanup component effect scopes after SSR render" (#14674) ([219d83b](vuejs/core@219d83b)), closes [#14674](vuejs/core#14674) [#14669](vuejs/core#14669)



== [3.5.31](vuejs/core@v3.5.30...v3.5.31) (2026-03-25)


== Bug Fixes

* **compiler-sfc:** allow Node.js subpath imports patterns in asset urls ([#13045](vuejs/core#13045)) ([95c3356](vuejs/core@95c3356)), closes [#9919](vuejs/core#9919)
* **compiler-sfc:** support template literal as defineModel name ([#14622](vuejs/core#14622)) ([bd7eef0](vuejs/core@bd7eef0)), closes [#14621](vuejs/core#14621)
* **reactivity:** normalize toRef property keys before dep lookup + improve types ([#14625](vuejs/core#14625)) ([1bb28d0](vuejs/core@1bb28d0)), closes [#12427](vuejs/core#12427) [#12431](vuejs/core#12431)
* **runtime-core:** invalidate detached v-for memo vnodes after unmount ([#14624](vuejs/core#14624)) ([560def4](vuejs/core@560def4)), closes [#12708](vuejs/core#12708) [#12710](vuejs/core#12710)
* **runtime-core:** preserve nullish event handlers in mergeProps ([#14550](vuejs/core#14550)) ([5725222](vuejs/core@5725222))
* **runtime-core:** prevent merging model listener when value is null or undefined ([#14629](vuejs/core#14629)) ([b39e032](vuejs/core@b39e032))
* **runtime-dom:** defer teleport mount/update until suspense resolves ([#8619](vuejs/core#8619)) ([88ed045](vuejs/core@88ed045)), closes [#8603](vuejs/core#8603)
* **runtime-dom:** handle activeElement check in Shadow DOM for v-model ([#14196](vuejs/core#14196)) ([959ded2](vuejs/core@959ded2))
* **server-renderer:** cleanup component effect scopes after SSR render ([#14548](vuejs/core#14548)) ([862f11e](vuejs/core@862f11e))
* **suspense:** avoid unmount activeBranch twice if wrapped in transition ([#9392](vuejs/core#9392)) ([908c6ad](vuejs/core@908c6ad)), closes [#7966](vuejs/core#7966)
* **suspense:** update suspense vnode's el during branch self-update ([#12922](vuejs/core#12922)) ([a2c1700](vuejs/core@a2c1700)), closes [#12920](vuejs/core#12920)
* **transition:** skip enter guard while hmr updating ([#14611](vuejs/core#14611)) ([be0a2f1](vuejs/core@be0a2f1)), closes [#14608](vuejs/core#14608)
* **types:** prevent shallowReactive marker from leaking into value unions ([#14493](vuejs/core#14493)) ([3b561db](vuejs/core@3b561db)), closes [#14490](vuejs/core#14490)



== [3.5.30](vuejs/core@v3.5.29...v3.5.30) (2026-03-09)


== Bug Fixes

* **compat:** add `entities` to @vue/compat deps to fix CJS edge cases ([#12514](vuejs/core#12514)) ([e725a67](vuejs/core@e725a67)), closes [#10609](vuejs/core#10609)
* **custom-element:** ensure child component styles are injected in correct order before parent styles ([#13374](vuejs/core#13374)) ([1398bf8](vuejs/core@1398bf8)), closes [#13029](vuejs/core#13029)
* **custom-element:** properly locate parent when slotted in shadow dom ([#12480](vuejs/core#12480)) ([f06c81a](vuejs/core@f06c81a)), closes [#12479](vuejs/core#12479)
* **custom-element:** should properly patch as props for vue custom elements ([#12409](vuejs/core#12409)) ([740983e](vuejs/core@740983e)), closes [#12408](vuejs/core#12408)
* **reactivity:** avoid duplicate raw/proxy entries in Set.add ([#14545](vuejs/core#14545)) ([d943612](vuejs/core@d943612))
* **reactivity:** fix reduce on reactive arrays to preserve reactivity ([#12737](vuejs/core#12737)) ([16ef165](vuejs/core@16ef165)), closes [#12735](vuejs/core#12735)
* **reactivity:** handle `Set` with initial reactive values edge case ([#12393](vuejs/core#12393)) ([5dc27ca](vuejs/core@5dc27ca)), closes [#8647](vuejs/core#8647)
* **runtime-core:** warn about negative number in v-for ([#12308](vuejs/core#12308)) ([9438cc5](vuejs/core@9438cc5))
* **ssr:** prevent watch from firing after async setup await ([#14547](vuejs/core#14547)) ([6cda71d](vuejs/core@6cda71d)), closes [#14546](vuejs/core#14546)
* **types:** make generics with runtime props in defineComponent work (fix [#11374](vuejs/core#11374)) ([#13119](vuejs/core#13119)) ([cea3cf7](vuejs/core@cea3cf7)), closes [#13763](vuejs/core#13763)
* **types:** narrow useAttrs class/style typing for TSX ([#14492](vuejs/core#14492)) ([bbb8977](vuejs/core@bbb8977)), closes [#14489](vuejs/core#14489)



== [3.5.29](vuejs/core@v3.5.28...v3.5.29) (2026-02-24)


== Bug Fixes

* **runtime-core:** prevent instance leak in withAsyncContext ([#14445](vuejs/core#14445)) ([702284f](vuejs/core@702284f)), closes [nuxt/nuxt#33644](nuxt/nuxt#33644)
* **server-renderer:** render className as escaped string ([#14469](vuejs/core#14469)) ([da6690c](vuejs/core@da6690c))
* **transition:** prevent enter if leave is in progress ([#14443](vuejs/core#14443)) ([df059f8](vuejs/core@df059f8)), closes [#12091](vuejs/core#12091) [#12133](vuejs/core#12133)



== [3.5.28](vuejs/core@v3.5.27...v3.5.28) (2026-02-09)


== Bug Fixes

* **transition:** avoid unexpected `cancelled` parameter in transition `done` callback ([#14391](vuejs/core#14391)) ([6798853](vuejs/core@6798853))
* **compiler-sfc:** add resolution trying for `.mts/.cts` files ([#14402](vuejs/core#14402)) ([c09d41f](vuejs/core@c09d41f)), closes [vuejs/router#2611](vuejs/router#2611)
* **compiler-sfc:** no params were generated when using withDefaults ([#12823](vuejs/core#12823)) ([b0a1f05](vuejs/core@b0a1f05)), closes [#12822](vuejs/core#12822)
* **reactivity:** add `__v_skip` flag to `EffectScope` to prevent reactive conversion ([#14359](vuejs/core#14359)) ([48b7552](vuejs/core@48b7552)), closes [#14357](vuejs/core#14357)
* **runtime-core:** avoid retaining el on cached text vnodes during static traversal ([#14419](vuejs/core#14419)) ([4ace79a](vuejs/core@4ace79a)), closes [#14134](vuejs/core#14134)
* **runtime-core:** prevent child component updates when style remains unchanged ([#12825](vuejs/core#12825)) ([57866b5](vuejs/core@57866b5)), closes [#12826](vuejs/core#12826)
* **runtime-core:** properly handle async component update before resolve ([#11619](vuejs/core#11619)) ([e71c26c](vuejs/core@e71c26c)), closes [#11617](vuejs/core#11617)
* **runtime-dom:** handle null/undefined handler in withModifiers ([#14362](vuejs/core#14362)) ([261de54](vuejs/core@261de54)), closes [#14361](vuejs/core#14361)
* **teleport:** properly handling disabled teleport target anchor ([#14417](vuejs/core#14417)) ([d7bcd85](vuejs/core@d7bcd85)), closes [#14412](vuejs/core#14412)
* **transition-group:** correct move translation under scale via element rect ([#14360](vuejs/core#14360)) ([0243a79](vuejs/core@0243a79)), closes [#14356](vuejs/core#14356)
* **useTemplateRef:** don't update setup ref for useTemplateRef key ([#12756](vuejs/core#12756)) ([fc40ca0](vuejs/core@fc40ca0)), closes [#12749](vuejs/core#12749)



== [3.5.27](vuejs/core@v3.5.26...v3.5.27) (2026-01-19)


== Bug Fixes

* **compile-sfc:** correctly handle variable shadowing in for loop for `defineProps` destructuring. ([#14296](vuejs/core#14296)) ([6a1bb50](vuejs/core@6a1bb50)), closes [#14294](vuejs/core#14294)
* **compiler-sfc:** handle indexed access types in declare global blocks ([#14260](vuejs/core#14260)) ([e4091fe](vuejs/core@e4091fe)), closes [#14236](vuejs/core#14236)
* **compiler-sfc:** use correct scope when resolving indexed access types from external files ([#14297](vuejs/core#14297)) ([f0f0a21](vuejs/core@f0f0a21)), closes [#14292](vuejs/core#14292)
* **reactivity:** collection iteration should inherit iterator instance methods ([#12644](vuejs/core#12644)) ([3c8b2fc](vuejs/core@3c8b2fc)), closes [#12615](vuejs/core#12615)
* **runtime-core:** skip patching reserved props for custom elements ([#14275](vuejs/core#14275)) ([19cc7e2](vuejs/core@19cc7e2)), closes [#14274](vuejs/core#14274)
* **server-renderer:** use ssrRenderClass helper for className attribute ([#14327](vuejs/core#14327)) ([a4708f3](vuejs/core@a4708f3))
* **ssr:** handle v-bind modifiers during render attrs ([#14263](vuejs/core#14263)) ([c2f5964](vuejs/core@c2f5964)), closes [#14262](vuejs/core#14262)



== [3.5.26](vuejs/core@v3.5.25...v3.5.26) (2025-12-18)


== Bug Fixes

* **compat:** fix compat handler of draggable ([#12445](vuejs/core#12445)) ([ed85953](vuejs/core@ed85953)), closes [#12444](vuejs/core#12444)
* **compat:** handle v-model deprecation warning with missing appContext ([#14203](vuejs/core#14203)) ([945a543](vuejs/core@945a543)), closes [#14202](vuejs/core#14202)
* **compiler-sfc:** demote const reactive bindings used in v-model ([#14214](vuejs/core#14214)) ([e24ff7d](vuejs/core@e24ff7d)), closes [#11265](vuejs/core#11265) [#11275](vuejs/core#11275)
* **compiler-ssr:** handle ssr attr fallthrough when preserve whitespace ([#12304](vuejs/core#12304)) ([4783118](vuejs/core@4783118)), closes [#8072](vuejs/core#8072)
* **hmr:** handle cached text node update ([#14134](vuejs/core#14134)) ([69ce3c7](vuejs/core@69ce3c7)), closes [#14127](vuejs/core#14127)
* **keep-alive:** use resolved component name for async components in cache pruning ([#14212](vuejs/core#14212)) ([dfe667c](vuejs/core@dfe667c)), closes [#14210](vuejs/core#14210)
* **runtime-core:** ensure correct anchor el for deeper unresolved async components ([#14182](vuejs/core#14182)) ([f5b3bf2](vuejs/core@f5b3bf2)), closes [#14173](vuejs/core#14173)
* **runtime-core:** handle patch stable fragment edge case ([#12411](vuejs/core#12411)) ([94aeb64](vuejs/core@94aeb64)), closes [#12410](vuejs/core#12410)
* **runtime-core:** pass component instance to flushPreFlushCbs on unmount ([#14221](vuejs/core#14221)) ([e857e12](vuejs/core@e857e12)), closes [#14215](vuejs/core#14215)


== Performance Improvements

* **compiler-core:** use binary-search to get line and column ([#14222](vuejs/core#14222)) ([1904053](vuejs/core@1904053))



== [3.5.25](vuejs/core@v3.5.24...v3.5.25) (2025-11-24)


== Bug Fixes

* **compiler:** share logic for comments and whitespace ([#13550](vuejs/core#13550)) ([2214f7a](vuejs/core@2214f7a))
* **provide:** warn when using `provide` after mounting ([#13954](vuejs/core#13954)) ([247b2c2](vuejs/core@247b2c2)), closes [#13921](vuejs/core#13921) [#13924](vuejs/core#13924)
* **reactivity:** correctly wrap iterated array items to preserve their readonly status ([#14120](vuejs/core#14120)) ([301020b](vuejs/core@301020b))
* **reactivity:** toRef edge cases for ref unwrapping ([#12420](vuejs/core#12420)) ([0d2357e](vuejs/core@0d2357e))
* **runtime-core:** keep options API typing intact when expose is used ([#14118](vuejs/core#14118)) ([8f82f23](vuejs/core@8f82f23)), closes [#14117](vuejs/core#14117) [vuejs/language-tools#5069](vuejs/language-tools#5069)
* **suspense:** defer clearing fallback vnode el in case it has dirs ([#14080](vuejs/core#14080)) ([c0f63dd](vuejs/core@c0f63dd)), closes [#14078](vuejs/core#14078)



== [3.5.24](vuejs/core@v3.5.23...v3.5.24) (2025-11-07)


== Reverts

* Revert "fix(compiler-core): correctly handle ts type assertions in expression…" (#14062) ([11ec51a](vuejs/core@11ec51a)), closes [#14062](vuejs/core#14062) [#14060](vuejs/core#14060)



== [3.5.23](vuejs/core@v3.5.22...v3.5.23) (2025-11-06)


== Bug Fixes

* **compiler-core:** correctly handle ts type assertions in expressions ([#13397](vuejs/core#13397)) ([e6544ac](vuejs/core@e6544ac)), closes [#13395](vuejs/core#13395)
* **compiler-core:** fix v-bind shorthand handling for in-DOM templates ([#13933](vuejs/core#13933)) ([b3cca26](vuejs/core@b3cca26)), closes [#13930](vuejs/core#13930)
* **compiler-sfc:** resolve numeric literals and template literals without expressions as static property key ([#13998](vuejs/core#13998)) ([75d44c7](vuejs/core@75d44c7))
* **compiler-ssr:** textarea with v-text directive SSR ([#13975](https://github.com/vuejs/core/issues/13975)) ([006a0c1](vuejs/core@006a0c1))
* **compiler:** using guard instead of non-nullish assertion ([#13982](https://github.com/vuejs/core/issues/13982)) ([dcc6f36](vuejs/core@dcc6f36))
* **custom-element:** batch custom element prop patching ([#13478](https://github.com/vuejs/core/issues/13478)) ([c13e674](vuejs/core@c13e674)), closes [#12619](https://github.com/vuejs/core/issues/12619)
* **custom-element:** optimize slot retrieval to avoid duplicates ([#13961](https://github.com/vuejs/core/issues/13961)) ([84ca349](vuejs/core@84ca349)), closes [#13955](https://github.com/vuejs/core/issues/13955)
* **hydration:** avoid mismatch during hydrate text with newlines in interpolation ([#9232](https://github.com/vuejs/core/issues/9232)) ([6cbdf78](vuejs/core@6cbdf78)), closes [#9229](https://github.com/vuejs/core/issues/9229)
* **runtime-core:** pass props and children to loadingComponent ([#13997](https://github.com/vuejs/core/issues/13997)) ([40c4b2a](vuejs/core@40c4b2a))
* **runtime-dom:** ensure iframe sandbox is handled as an attribute to prevent unintended behavior ([#13950](https://github.com/vuejs/core/issues/13950)) ([5689884](vuejs/core@5689884)), closes [#13946](https://github.com/vuejs/core/issues/13946)
* **suspense:** clear placeholder and fallback el after resolve to enable GC ([#13928](https://github.com/vuejs/core/issues/13928)) ([f411c66](vuejs/core@f411c66))
* **transition-group:** use offsetLeft and offsetTop instead of getBoundingClientRect  to avoid transform scale affect animation ([#6108](https://github.com/vuejs/core/issues/6108)) ([dc4dd59](vuejs/core@dc4dd59)), closes [#6105](https://github.com/vuejs/core/issues/6105)
* **v-model:** handle number modifier on change ([#13959](https://github.com/vuejs/core/issues/13959)) ([8fbe48f](vuejs/core@8fbe48f)), closes [#13958](https://github.com/vuejs/core/issues/13958)




Release notes: https://github.com/vuejs/core/blob/main/CHANGELOG.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 bug Something isn't working ready for review This PR requires more reviews scope: sfc

Projects

None yet

2 participants