Bladeren bron

chore: merge main into minor

daiwei 11 maanden geleden
bovenliggende
commit
77067abd33
41 gewijzigde bestanden met toevoegingen van 967 en 266 verwijderingen
  1. 39 1
      CHANGELOG.md
  2. 1 0
      README.md
  3. 16 33
      package.json
  4. 2 2
      packages-private/sfc-playground/src/download/template/package.json
  5. 10 0
      packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
  6. 1 1
      packages/compiler-core/package.json
  7. 27 0
      packages/compiler-core/src/transforms/cacheStatic.ts
  8. 0 1
      packages/compiler-core/src/transforms/vSlot.ts
  9. 1 1
      packages/compiler-dom/package.json
  10. 19 0
      packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap
  11. 16 0
      packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
  12. 20 16
      packages/compiler-sfc/__tests__/compileStyle.spec.ts
  13. 2 2
      packages/compiler-sfc/package.json
  14. 1 0
      packages/compiler-sfc/src/compileScript.ts
  15. 2 1
      packages/compiler-sfc/src/script/definePropsDestructure.ts
  16. 1 1
      packages/compiler-ssr/package.json
  17. 11 0
      packages/reactivity/__tests__/computed.spec.ts
  18. 1 1
      packages/reactivity/package.json
  19. 94 0
      packages/runtime-core/__tests__/componentSlots.spec.ts
  20. 47 0
      packages/runtime-core/__tests__/components/BaseTransition.spec.ts
  21. 128 1
      packages/runtime-core/__tests__/components/Teleport.spec.ts
  22. 34 1
      packages/runtime-core/__tests__/helpers/renderList.spec.ts
  23. 58 1
      packages/runtime-core/__tests__/hydration.spec.ts
  24. 1 1
      packages/runtime-core/package.json
  25. 4 5
      packages/runtime-core/src/apiCreateApp.ts
  26. 1 1
      packages/runtime-core/src/component.ts
  27. 13 3
      packages/runtime-core/src/componentSlots.ts
  28. 2 3
      packages/runtime-core/src/components/BaseTransition.ts
  29. 2 1
      packages/runtime-core/src/components/Teleport.ts
  30. 9 1
      packages/runtime-core/src/helpers/renderList.ts
  31. 29 6
      packages/runtime-core/src/renderer.ts
  32. 1 1
      packages/runtime-dom/package.json
  33. 2 0
      packages/runtime-dom/src/components/TransitionGroup.ts
  34. 33 1
      packages/server-renderer/__tests__/ssrSlot.spec.ts
  35. 1 1
      packages/server-renderer/package.json
  36. 1 1
      packages/shared/package.json
  37. 1 1
      packages/vue-compat/package.json
  38. 51 0
      packages/vue/__tests__/e2e/TransitionGroup.spec.ts
  39. 1 1
      packages/vue/package.json
  40. 266 173
      pnpm-lock.yaml
  41. 18 3
      pnpm-workspace.yaml

+ 39 - 1
CHANGELOG.md

@@ -1,3 +1,41 @@
+## [3.5.14](https://github.com/vuejs/core/compare/v3.5.13...v3.5.14) (2025-05-15)
+
+
+### Bug Fixes
+
+* **compat:** correct deprecation message for v-bind.sync usage ([#13137](https://github.com/vuejs/core/issues/13137)) ([466b30f](https://github.com/vuejs/core/commit/466b30f4049ec89fb282624ec17d1a93472ab93f)), closes [#13133](https://github.com/vuejs/core/issues/13133)
+* **compiler-core:** remove slot cache from parent renderCache during unmounting ([#13215](https://github.com/vuejs/core/issues/13215)) ([5d166f3](https://github.com/vuejs/core/commit/5d166f3796a03a497435fc079c6a83a4e9c6cf52))
+* **compiler-sfc:** fix scope handling for props destructure in function parameters and catch clauses ([8e34357](https://github.com/vuejs/core/commit/8e3435779a667de485cf9efd78667d0ca14c5f84)), closes [#12790](https://github.com/vuejs/core/issues/12790)
+* **compiler-sfc:** treat the return value of `useTemplateRef` as a definite ref ([#13197](https://github.com/vuejs/core/issues/13197)) ([8ae1122](https://github.com/vuejs/core/commit/8ae11226e8ee938615e17c7b81dc38ae3f7cefb9))
+* **compiler:** fix spelling error in domTagConfig ([#13043](https://github.com/vuejs/core/issues/13043)) ([388295b](https://github.com/vuejs/core/commit/388295b27f3cc69eba25d325bbe60a36a3df831a))
+* **customFormatter:** properly accessing ref value during debugger ([#12948](https://github.com/vuejs/core/issues/12948)) ([fdbd026](https://github.com/vuejs/core/commit/fdbd02658301dd794fe0c84f0018d080a07fca9f))
+* **hmr/teleport:** adjust static children traversal for HMR in dev mode ([#12819](https://github.com/vuejs/core/issues/12819)) ([5e37dd0](https://github.com/vuejs/core/commit/5e37dd009562bcd8080a200c32abde2d6e4f0305)), closes [#12816](https://github.com/vuejs/core/issues/12816)
+* **hmr:** avoid hydration for hmr root reload ([#12450](https://github.com/vuejs/core/issues/12450)) ([1f98a9c](https://github.com/vuejs/core/commit/1f98a9c493d01c21befa90107f0593bc92a58932)), closes [vitejs/vite-plugin-vue#146](https://github.com/vitejs/vite-plugin-vue/issues/146) [vitejs/vite-plugin-vue#477](https://github.com/vitejs/vite-plugin-vue/issues/477)
+* **hmr:** avoid hydration for hmr updating ([#12262](https://github.com/vuejs/core/issues/12262)) ([9c4dbbc](https://github.com/vuejs/core/commit/9c4dbbc5185125835ad3e49baba303bd54676111)), closes [#7706](https://github.com/vuejs/core/issues/7706) [#8170](https://github.com/vuejs/core/issues/8170)
+* **reactivity:** ensure markRaw objects are not reactive ([#12824](https://github.com/vuejs/core/issues/12824)) ([295b5ec](https://github.com/vuejs/core/commit/295b5ec19b6a52c4a56652cc4d6e93a4ea7c14ed)), closes [#12807](https://github.com/vuejs/core/issues/12807)
+* **reactivity:** ensure multiple effectScope on() and off() calls maintains correct active scope ([22dcbf3](https://github.com/vuejs/core/commit/22dcbf3e20eb84f69c8952f6f70d9990136a4a68)), closes [#12631](https://github.com/vuejs/core/issues/12631) [#12632](https://github.com/vuejs/core/issues/12632) [#12641](https://github.com/vuejs/core/issues/12641)
+* **reactivity:** should not recompute if computed does not track reactive data ([#12341](https://github.com/vuejs/core/issues/12341)) ([0b23fd2](https://github.com/vuejs/core/commit/0b23fd23833cf085e7e112bf4435cfc9b360d072)), closes [#12337](https://github.com/vuejs/core/issues/12337)
+* **runtime-core:**  stop tracking deps in setRef during unmount ([#13210](https://github.com/vuejs/core/issues/13210)) ([016c472](https://github.com/vuejs/core/commit/016c472bd2e7604b21c69dee1da8545ce26e4d2f))
+* **runtime-core:**  update __vnode of static nodes when patching along the optimized path ([#13223](https://github.com/vuejs/core/issues/13223)) ([b3ecee3](https://github.com/vuejs/core/commit/b3ecee3da8ed5c55dea89ce6b4b376b2b722b018))
+* **runtime-core:** inherit comment nodes during block patch in production build  ([#10748](https://github.com/vuejs/core/issues/10748)) ([6264505](https://github.com/vuejs/core/commit/626450590d81f79117b34d2a73073b1dc8f551bd)), closes [#10747](https://github.com/vuejs/core/issues/10747) [#12650](https://github.com/vuejs/core/issues/12650)
+* **runtime-core:** prevent unmounted vnode from being inserted during transition leave ([#12862](https://github.com/vuejs/core/issues/12862)) ([d6a6ec1](https://github.com/vuejs/core/commit/d6a6ec13ce521683bfb2a22932778ef7b51f8600)), closes [#12860](https://github.com/vuejs/core/issues/12860)
+* **runtime-core:** respect immutability for readonly reactive arrays in `v-for` ([#13091](https://github.com/vuejs/core/issues/13091)) ([3f27c58](https://github.com/vuejs/core/commit/3f27c58ffbd4309df369bc89493fdc284dc540bb)), closes [#13087](https://github.com/vuejs/core/issues/13087)
+* **runtime-dom:** always treat autocorrect as attribute ([#13001](https://github.com/vuejs/core/issues/13001)) ([1499135](https://github.com/vuejs/core/commit/1499135c227236e037bb746beeb777941b0b58ff)), closes [#5705](https://github.com/vuejs/core/issues/5705)
+* **slots:** properly warn if slot invoked in setup ([#12195](https://github.com/vuejs/core/issues/12195)) ([9196222](https://github.com/vuejs/core/commit/9196222ae1d63b52b35ac5fbf5e71494587ccf05)), closes [#12194](https://github.com/vuejs/core/issues/12194)
+* **ssr:** properly init slots during ssr rendering ([#12441](https://github.com/vuejs/core/issues/12441)) ([2206cd2](https://github.com/vuejs/core/commit/2206cd235a1627c540e795e378b7564a55b47313)), closes [#12438](https://github.com/vuejs/core/issues/12438)
+* **transition:** fix KeepAlive with transition out-in mode behavior in production ([#12468](https://github.com/vuejs/core/issues/12468)) ([343c891](https://github.com/vuejs/core/commit/343c89122448719bd6ed6bd9de986dfb2721d6bf)), closes [#12465](https://github.com/vuejs/core/issues/12465)
+* **TransitionGroup:** reset prevChildren to prevent memory leak ([#13183](https://github.com/vuejs/core/issues/13183)) ([8b848cb](https://github.com/vuejs/core/commit/8b848cbbd2af337d23e19e202f9ab433f8580855)), closes [#13181](https://github.com/vuejs/core/issues/13181)
+* **types:** allow return any for Options API lifecycle hooks ([#5914](https://github.com/vuejs/core/issues/5914)) ([06310e8](https://github.com/vuejs/core/commit/06310e82f5bed62d1b9733dcb18cd8d6edc988de))
+* **types:** the directive's modifiers should be optional ([#12605](https://github.com/vuejs/core/issues/12605)) ([10e54dc](https://github.com/vuejs/core/commit/10e54dcc86a7967f3196d96200bcbd1d3d42082f))
+* **typos:** fix comments referencing transformElement.ts ([#12551](https://github.com/vuejs/core/issues/12551))[ci-skip] ([11c053a](https://github.com/vuejs/core/commit/11c053a5429ad0d27a0e2c78b6b026ea00ace116))
+
+
+### Features
+
+* **types:** add type TemplateRef ([#12645](https://github.com/vuejs/core/issues/12645)) ([636a861](https://github.com/vuejs/core/commit/636a8619f06c71dfd79f7f6412fd130c4f84226f))
+
+
+
 ## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15)
 
 
@@ -8,7 +46,7 @@
 * **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
 * **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
 * **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
-* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
+* **reactivity:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
 * **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
 * **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
 * **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))

+ 1 - 0
README.md

@@ -35,6 +35,7 @@ Please make sure to respect issue requirements and use [the new issue helper](ht
 ## Stay In Touch
 
 - [X](https://x.com/vuejs)
+- [Bluesky](https://bsky.app/profile/vuejs.org)
 - [Blog](https://blog.vuejs.org/)
 - [Job Board](https://vuejobs.com/?ref=vuejs)
 

+ 16 - 33
package.json

@@ -1,7 +1,7 @@
 {
   "private": true,
-  "version": "3.5.13",
-  "packageManager": "pnpm@10.7.0",
+  "version": "3.5.14",
+  "packageManager": "pnpm@10.9.0",
   "type": "module",
   "scripts": {
     "dev": "node scripts/dev.js",
@@ -69,23 +69,23 @@
     "@rollup/plugin-json": "^6.1.0",
     "@rollup/plugin-node-resolve": "^16.0.1",
     "@rollup/plugin-replace": "5.0.4",
-    "@swc/core": "^1.11.13",
+    "@swc/core": "^1.11.24",
     "@types/hash-sum": "^1.0.2",
-    "@types/node": "^22.13.14",
+    "@types/node": "^22.14.1",
     "@types/semver": "^7.7.0",
     "@types/serve-handler": "^6.1.4",
-    "@vitest/coverage-v8": "^3.0.9",
-    "@vitest/eslint-plugin": "^1.1.38",
+    "@vitest/coverage-v8": "^3.1.3",
+    "@vitest/eslint-plugin": "^1.1.44",
     "@vue/consolidate": "1.0.0",
     "conventional-changelog-cli": "^5.0.0",
     "enquirer": "^2.4.1",
-    "esbuild": "^0.25.2",
+    "esbuild": "^0.25.4",
     "esbuild-plugin-polyfill-node": "^0.3.0",
-    "eslint": "^9.23.0",
-    "eslint-plugin-import-x": "^4.9.4",
+    "eslint": "^9.25.1",
+    "eslint-plugin-import-x": "^4.11.0",
     "estree-walker": "catalog:",
-    "jsdom": "^26.0.0",
-    "lint-staged": "^15.5.0",
+    "jsdom": "^26.1.0",
+    "lint-staged": "^15.5.1",
     "lodash": "^4.17.21",
     "magic-string": "^0.30.17",
     "markdown-table": "^3.0.4",
@@ -95,38 +95,21 @@
     "prettier": "^3.5.3",
     "pretty-bytes": "^6.1.1",
     "pug": "^3.0.3",
-    "puppeteer": "~24.4.0",
+    "puppeteer": "~24.8.2",
     "rimraf": "^6.0.1",
-    "rollup": "^4.38.0",
+    "rollup": "^4.40.2",
     "rollup-plugin-dts": "^6.2.1",
     "rollup-plugin-esbuild": "^6.2.1",
     "rollup-plugin-polyfill-node": "^0.13.0",
     "semver": "^7.7.1",
     "serve": "^14.2.4",
     "serve-handler": "^6.1.6",
-    "simple-git-hooks": "^2.12.1",
+    "simple-git-hooks": "^2.13.0",
     "todomvc-app-css": "^2.4.3",
     "tslib": "^2.8.1",
     "typescript": "~5.6.2",
-    "typescript-eslint": "^8.28.0",
+    "typescript-eslint": "^8.31.1",
     "vite": "catalog:",
-    "vitest": "^3.0.9"
-  },
-  "pnpm": {
-    "peerDependencyRules": {
-      "allowedVersions": {
-        "typescript-eslint>eslint": "^9.0.0",
-        "@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
-        "@typescript-eslint/parser>eslint": "^9.0.0",
-        "@typescript-eslint/type-utils>eslint": "^9.0.0",
-        "@typescript-eslint/utils>eslint": "^9.0.0"
-      }
-    },
-    "onlyBuiltDependencies": [
-      "@swc/core",
-      "esbuild",
-      "puppeteer",
-      "simple-git-hooks"
-    ]
+    "vitest": "^3.1.3"
   }
 }

+ 2 - 2
packages-private/sfc-playground/src/download/template/package.json

@@ -11,7 +11,7 @@
     "vue": "latest"
   },
   "devDependencies": {
-    "@vitejs/plugin-vue": "^5.2.3",
-    "vite": "^6.2.3"
+    "@vitejs/plugin-vue": "^5.2.4",
+    "vite": "^6.3.5"
   }
 }

+ 10 - 0
packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts

@@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => {
         {
           /* _ slot flag */
         },
+        {
+          type: NodeTypes.JS_PROPERTY,
+          key: { content: '__' },
+          value: { content: '[0]' },
+        },
       ],
     })
   })
@@ -197,6 +202,11 @@ describe('compiler: cacheStatic transform', () => {
         {
           /* _ slot flag */
         },
+        {
+          type: NodeTypes.JS_PROPERTY,
+          key: { content: '__' },
+          value: { content: '[0]' },
+        },
       ],
     })
   })

+ 1 - 1
packages/compiler-core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-core",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/compiler-core",
   "main": "index.js",
   "module": "dist/compiler-core.esm-bundler.js",

+ 27 - 0
packages/compiler-core/src/transforms/cacheStatic.ts

@@ -12,11 +12,14 @@ import {
   type RootNode,
   type SimpleExpressionNode,
   type SlotFunctionExpression,
+  type SlotsObjectProperty,
   type TemplateChildNode,
   type TemplateNode,
   type TextCallNode,
   type VNodeCall,
   createArrayExpression,
+  createObjectProperty,
+  createSimpleExpression,
   getVNodeBlockHelper,
   getVNodeHelper,
 } from '../ast'
@@ -140,6 +143,7 @@ function walk(
   }
 
   let cachedAsArray = false
+  const slotCacheKeys = []
   if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
     if (
       node.tagType === ElementTypes.ELEMENT &&
@@ -163,6 +167,7 @@ function walk(
       // default slot
       const slot = getSlotNode(node.codegenNode, 'default')
       if (slot) {
+        slotCacheKeys.push(context.cached.length)
         slot.returns = getCacheExpression(
           createArrayExpression(slot.returns as TemplateChildNode[]),
         )
@@ -186,6 +191,7 @@ function walk(
         slotName.arg &&
         getSlotNode(parent.codegenNode, slotName.arg)
       if (slot) {
+        slotCacheKeys.push(context.cached.length)
         slot.returns = getCacheExpression(
           createArrayExpression(slot.returns as TemplateChildNode[]),
         )
@@ -196,10 +202,31 @@ function walk(
 
   if (!cachedAsArray) {
     for (const child of toCache) {
+      slotCacheKeys.push(context.cached.length)
       child.codegenNode = context.cache(child.codegenNode!)
     }
   }
 
+  // put the slot cached keys on the slot object, so that the cache
+  // can be removed when component unmounting to prevent memory leaks
+  if (
+    slotCacheKeys.length &&
+    node.type === NodeTypes.ELEMENT &&
+    node.tagType === ElementTypes.COMPONENT &&
+    node.codegenNode &&
+    node.codegenNode.type === NodeTypes.VNODE_CALL &&
+    node.codegenNode.children &&
+    !isArray(node.codegenNode.children) &&
+    node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
+  ) {
+    node.codegenNode.children.properties.push(
+      createObjectProperty(
+        `__`,
+        createSimpleExpression(JSON.stringify(slotCacheKeys), false),
+      ) as SlotsObjectProperty,
+    )
+  }
+
   function getCacheExpression(value: JSChildNode): CacheExpression {
     const exp = context.cache(value)
     // #6978, #7138, #7114

+ 0 - 1
packages/compiler-core/src/transforms/vSlot.ts

@@ -342,7 +342,6 @@ export function buildSlots(
     : hasForwardedSlots(node.children)
       ? SlotFlags.FORWARDED
       : SlotFlags.STABLE
-
   let slots = createObjectExpression(
     slotsProperties.concat(
       createObjectProperty(

+ 1 - 1
packages/compiler-dom/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-dom",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/compiler-dom",
   "main": "index.js",
   "module": "dist/compiler-dom.esm-bundler.js",

+ 19 - 0
packages/compiler-sfc/__tests__/compileScript/__snapshots__/definePropsDestructure.spec.ts.snap

@@ -192,6 +192,25 @@ return () => {}
 }"
 `;
 
+exports[`sfc reactive props destructure > handle function parameters with same name as destructured props 1`] = `
+"
+export default {
+  setup(__props) {
+
+    
+    function test(value) {
+      try {
+      } catch {
+      }
+    }
+    console.log(__props.value)
+    
+return () => {}
+}
+
+}"
+`;
+
 exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
 "
 export default {

+ 16 - 0
packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts

@@ -358,6 +358,22 @@ describe('sfc reactive props destructure', () => {
     expect(content).toMatch(`props: ['item'],`)
   })
 
+  test('handle function parameters with same name as destructured props', () => {
+    const { content } = compile(`
+    <script setup>
+    const { value } = defineProps()
+    function test(value) {
+      try {
+      } catch {
+      }
+    }
+    console.log(value)
+    </script>
+  `)
+    assertCode(content)
+    expect(content).toMatch(`console.log(__props.value)`)
+  })
+
   test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
     const { content } = compile(`
     <script setup>

+ 20 - 16
packages/compiler-sfc/__tests__/compileStyle.spec.ts

@@ -211,38 +211,42 @@ color: red
     expect(
       compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
     ).toMatchInlineSnapshot(`
-    ".div[data-v-test] { color: red;
-    }
-    .div[data-v-test]:where(:hover) { color: blue;
-    }"`)
+      ".div[data-v-test] { color: red;
+      }
+      .div[data-v-test]:where(:hover) { color: blue;
+      }"
+    `)
 
     expect(
       compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
     ).toMatchInlineSnapshot(`
-    ".div[data-v-test] { color: red;
-    }
-    .div[data-v-test]:is(:hover) { color: blue;
-    }"`)
+      ".div[data-v-test] { color: red;
+      }
+      .div[data-v-test]:is(:hover) { color: blue;
+      }"
+    `)
 
     expect(
       compileScoped(
         `.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
       ),
     ).toMatchInlineSnapshot(`
-    ".div[data-v-test] { color: red;
-    }
-    .div[data-v-test]:where(.foo:hover) { color: blue;
-    }"`)
+      ".div[data-v-test] { color: red;
+      }
+      .div[data-v-test]:where(.foo:hover) { color: blue;
+      }"
+    `)
 
     expect(
       compileScoped(
         `.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
       ),
     ).toMatchInlineSnapshot(`
-    ".div[data-v-test] { color: red;
-    }
-    .div[data-v-test]:is(.foo:hover) { color: blue;
-    }"`)
+      ".div[data-v-test] { color: red;
+      }
+      .div[data-v-test]:is(.foo:hover) { color: blue;
+      }"
+    `)
   })
 
   test('media query', () => {

+ 2 - 2
packages/compiler-sfc/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-sfc",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/compiler-sfc",
   "main": "dist/compiler-sfc.cjs.js",
   "module": "dist/compiler-sfc.esm-browser.js",
@@ -62,6 +62,6 @@
     "postcss-modules": "^6.0.1",
     "postcss-selector-parser": "^7.1.0",
     "pug": "^3.0.3",
-    "sass": "^1.86.0"
+    "sass": "^1.86.3"
   }
 }

+ 1 - 0
packages/compiler-sfc/src/compileScript.ts

@@ -1104,6 +1104,7 @@ function walkDeclaration(
                 m === userImportAliases['shallowRef'] ||
                 m === userImportAliases['customRef'] ||
                 m === userImportAliases['toRef'] ||
+                m === userImportAliases['useTemplateRef'] ||
                 m === DEFINE_MODEL,
             )
           ) {

+ 2 - 1
packages/compiler-sfc/src/script/definePropsDestructure.ts

@@ -291,7 +291,8 @@ export function transformDestructuredProps(
       parent && parentStack.pop()
       if (
         (node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
-        isFunctionType(node)
+        isFunctionType(node) ||
+        node.type === 'CatchClause'
       ) {
         popScope()
       }

+ 1 - 1
packages/compiler-ssr/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-ssr",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/compiler-ssr",
   "main": "dist/compiler-ssr.cjs.js",
   "types": "dist/compiler-ssr.d.ts",

+ 11 - 0
packages/reactivity/__tests__/computed.spec.ts

@@ -1017,6 +1017,17 @@ describe('reactivity/computed', () => {
     expect(cValue.value).toBe(1)
   })
 
+  test('should not recompute if computed does not track reactive data', async () => {
+    const spy = vi.fn()
+    const c1 = computed(() => spy())
+
+    c1.value
+    ref(0).value++ // update globalVersion
+    c1.value
+
+    expect(spy).toBeCalledTimes(1)
+  })
+
   test('computed should remain live after losing all subscribers', () => {
     const state = reactive({ a: 1 })
     const p = computed(() => state.a + 1)

+ 1 - 1
packages/reactivity/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/reactivity",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/reactivity",
   "main": "index.js",
   "module": "dist/reactivity.esm-bundler.js",

+ 94 - 0
packages/runtime-core/__tests__/componentSlots.spec.ts

@@ -324,4 +324,98 @@ describe('component: slots', () => {
       'Slot "default" invoked outside of the render function',
     ).not.toHaveBeenWarned()
   })
+
+  test('basic warn', () => {
+    const Comp = {
+      setup(_: any, { slots }: any) {
+        slots.default && slots.default()
+        return () => null
+      },
+    }
+
+    const App = {
+      setup() {
+        return () => h(Comp, () => h('div'))
+      },
+    }
+
+    createApp(App).mount(nodeOps.createElement('div'))
+    expect(
+      'Slot "default" invoked outside of the render function',
+    ).toHaveBeenWarned()
+  })
+
+  test('basic warn when mounting another app in setup', () => {
+    const Comp = {
+      setup(_: any, { slots }: any) {
+        slots.default?.()
+        return () => null
+      },
+    }
+
+    const mountComp = () => {
+      createApp({
+        setup() {
+          return () => h(Comp, () => 'msg')
+        },
+      }).mount(nodeOps.createElement('div'))
+    }
+
+    const App = {
+      setup() {
+        mountComp()
+        return () => null
+      },
+    }
+
+    createApp(App).mount(nodeOps.createElement('div'))
+    expect(
+      'Slot "default" invoked outside of the render function',
+    ).toHaveBeenWarned()
+  })
+
+  test('should not warn when render in setup', () => {
+    const container = {
+      setup(_: any, { slots }: any) {
+        return () => slots.default && slots.default()
+      },
+    }
+
+    const comp = h(container, null, () => h('div'))
+
+    const App = {
+      setup() {
+        render(h(comp), nodeOps.createElement('div'))
+        return () => null
+      },
+    }
+
+    createApp(App).mount(nodeOps.createElement('div'))
+    expect(
+      'Slot "default" invoked outside of the render function',
+    ).not.toHaveBeenWarned()
+  })
+
+  test('basic warn when render in setup', () => {
+    const container = {
+      setup(_: any, { slots }: any) {
+        slots.default && slots.default()
+        return () => null
+      },
+    }
+
+    const comp = h(container, null, () => h('div'))
+
+    const App = {
+      setup() {
+        render(h(comp), nodeOps.createElement('div'))
+        return () => null
+      },
+    }
+
+    createApp(App).mount(nodeOps.createElement('div'))
+    expect(
+      'Slot "default" invoked outside of the render function',
+    ).toHaveBeenWarned()
+  })
 })

+ 47 - 0
packages/runtime-core/__tests__/components/BaseTransition.spec.ts

@@ -1198,4 +1198,51 @@ describe('BaseTransition', () => {
   test('should not error on KeepAlive w/ function children', () => {
     expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
   })
+
+  // #12465
+  test('mode: "out-in" w/ KeepAlive + fallthrough attrs (prod mode)', async () => {
+    __DEV__ = false
+    async function testOutIn({ trueBranch, falseBranch }: ToggleOptions) {
+      const toggle = ref(true)
+      const { props, cbs } = mockProps({ mode: 'out-in' }, true)
+      const root = nodeOps.createElement('div')
+      const App = {
+        render() {
+          return h(
+            BaseTransition,
+            {
+              ...props,
+              class: 'test',
+            },
+            () =>
+              h(KeepAlive, null, toggle.value ? trueBranch() : falseBranch()),
+          )
+        },
+      }
+      render(h(App), root)
+
+      expect(serializeInner(root)).toBe(`<div class="test">0</div>`)
+
+      // trigger toggle
+      toggle.value = false
+      await nextTick()
+      expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
+      expect(serialize((props.onBeforeLeave as any).mock.calls[0][0])).toBe(
+        `<div class="test">0</div>`,
+      )
+      expect(props.onLeave).toHaveBeenCalledTimes(1)
+      expect(serialize((props.onLeave as any).mock.calls[0][0])).toBe(
+        `<div class="test">0</div>`,
+      )
+      expect(props.onAfterLeave).not.toHaveBeenCalled()
+      // enter should not have started
+      expect(props.onBeforeEnter).not.toHaveBeenCalled()
+      expect(props.onEnter).not.toHaveBeenCalled()
+      expect(props.onAfterEnter).not.toHaveBeenCalled()
+      cbs.doneLeave[`<div class="test">0</div>`]()
+      expect(serializeInner(root)).toBe(`<span class="test">0</span>`)
+    }
+    await runTestWithKeepAlive(testOutIn)
+    __DEV__ = true
+  })
 })

+ 128 - 1
packages/runtime-core/__tests__/components/Teleport.spec.ts

@@ -10,14 +10,28 @@ import {
   markRaw,
   nextTick,
   nodeOps,
+  onMounted,
   h as originalH,
   ref,
   render,
+  serialize,
   serializeInner,
   withDirectives,
 } from '@vue/runtime-test'
-import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
+import {
+  Fragment,
+  createBlock,
+  createCommentVNode,
+  createTextVNode,
+  createVNode,
+  openBlock,
+} from '../../src/vnode'
+import { toDisplayString } from '@vue/shared'
 import { compile, createApp as createDOMApp, render as domRender } from 'vue'
+import type { HMRRuntime } from '../../src/hmr'
+
+declare var __VUE_HMR_RUNTIME__: HMRRuntime
+const { rerender, createRecord } = __VUE_HMR_RUNTIME__
 
 describe('renderer: teleport', () => {
   describe('eager mode', () => {
@@ -243,6 +257,39 @@ describe('renderer: teleport', () => {
       expect(serializeInner(target)).toBe(`teleported`)
     })
 
+    test('should traverse comment node after updating in optimize mode', async () => {
+      const target = nodeOps.createElement('div')
+      const root = nodeOps.createElement('div')
+      const count = ref(0)
+      let teleport
+
+      __DEV__ = false
+      render(
+        h(() => {
+          teleport =
+            (openBlock(),
+            createBlock(Teleport, { to: target }, [
+              createCommentVNode('comment in teleport'),
+            ]))
+          return h('div', null, [
+            createTextVNode(toDisplayString(count.value)),
+            teleport,
+          ])
+        }),
+        root,
+      )
+      const commentNode = teleport!.children[0].el
+      expect(serializeInner(root)).toBe(`<div>0</div>`)
+      expect(serializeInner(target)).toBe(`<!--comment in teleport-->`)
+      expect(serialize(commentNode)).toBe(`<!--comment in teleport-->`)
+
+      count.value = 1
+      await nextTick()
+      __DEV__ = true
+      expect(serializeInner(root)).toBe(`<div>1</div>`)
+      expect(teleport!.children[0].el).toBe(commentNode)
+    })
+
     test('should remove children when unmounted', () => {
       const target = nodeOps.createElement('div')
       const root = nodeOps.createElement('div')
@@ -269,6 +316,34 @@ describe('renderer: teleport', () => {
       testUnmount({ to: null, disabled: true })
     })
 
+    // #10747
+    test('should unmount correctly when using top level comment in teleport', async () => {
+      const target = nodeOps.createElement('div')
+      const root = nodeOps.createElement('div')
+      const count = ref(0)
+
+      __DEV__ = false
+      render(
+        h(() => {
+          return h('div', null, [
+            createTextVNode(toDisplayString(count.value)),
+            (openBlock(),
+            createBlock(Teleport, { to: target }, [
+              createCommentVNode('comment in teleport'),
+            ])),
+          ])
+        }),
+        root,
+      )
+
+      count.value = 1
+
+      await nextTick()
+      __DEV__ = true
+      render(null, root)
+      expect(root.children.length).toBe(0)
+    })
+
     test('component with multi roots should be removed when unmounted', () => {
       const target = nodeOps.createElement('div')
       const root = nodeOps.createElement('div')
@@ -741,4 +816,56 @@ describe('renderer: teleport', () => {
       expect(tRefInMounted).toBe(target.children[1])
     })
   }
+
+  test('handle update and hmr rerender', async () => {
+    const target = document.createElement('div')
+    const root = document.createElement('div')
+
+    const Comp = {
+      setup() {
+        const cls = ref('foo')
+        onMounted(() => {
+          // trigger update
+          cls.value = 'bar'
+        })
+        return { cls, target }
+      },
+      template: `
+        <Teleport :to="target">
+          <div :class="cls">
+            <div>
+              <slot></slot>
+            </div>
+          </div>
+        </Teleport>
+      `,
+    }
+
+    const appId = 'test-app-id'
+    const App = {
+      __hmrId: appId,
+      components: { Comp },
+      render() {
+        return originalH(Comp, null, { default: () => originalH('div', 'foo') })
+      },
+    }
+    createRecord(appId, App)
+
+    domRender(originalH(App), root)
+    expect(target.innerHTML).toBe(
+      '<div class="foo"><div><div>foo</div></div></div>',
+    )
+    await nextTick()
+    expect(target.innerHTML).toBe(
+      '<div class="bar"><div><div>foo</div></div></div>',
+    )
+
+    rerender(appId, () =>
+      originalH(Comp, null, { default: () => originalH('div', 'bar') }),
+    )
+    await nextTick()
+    expect(target.innerHTML).toBe(
+      '<div class="bar"><div><div>bar</div></div></div>',
+    )
+  })
 })

+ 34 - 1
packages/runtime-core/__tests__/helpers/renderList.spec.ts

@@ -1,4 +1,10 @@
-import { isReactive, reactive, shallowReactive } from '../../src/index'
+import {
+  effect,
+  isReactive,
+  reactive,
+  readonly,
+  shallowReactive,
+} from '../../src/index'
 import { renderList } from '../../src/helpers/renderList'
 
 describe('renderList', () => {
@@ -65,4 +71,31 @@ describe('renderList', () => {
     const shallowReactiveArray = shallowReactive([{ foo: 1 }])
     expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
   })
+
+  it('should not allow mutation', () => {
+    const arr = readonly(reactive([{ foo: 1 }]))
+    expect(
+      renderList(arr, item => {
+        ;(item as any).foo = 0
+        return item.foo
+      }),
+    ).toEqual([1])
+    expect(
+      `Set operation on key "foo" failed: target is readonly.`,
+    ).toHaveBeenWarned()
+  })
+
+  it('should trigger effect for deep mutations in readonly reactive arrays', () => {
+    const arr = reactive([{ foo: 1 }])
+    const readonlyArr = readonly(arr)
+
+    let dummy
+    effect(() => {
+      dummy = renderList(readonlyArr, item => item.foo)
+    })
+    expect(dummy).toEqual([1])
+
+    arr[0].foo = 2
+    expect(dummy).toEqual([2])
+  })
 })

+ 58 - 1
packages/runtime-core/__tests__/hydration.spec.ts

@@ -32,10 +32,13 @@ import {
   withCtx,
   withDirectives,
 } from '@vue/runtime-dom'
+import type { HMRRuntime } from '../src/hmr'
 import { type SSRContext, renderToString } from '@vue/server-renderer'
 import { PatchFlags, normalizeStyle } from '@vue/shared'
 import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
-import { expect } from 'vitest'
+
+declare var __VUE_HMR_RUNTIME__: HMRRuntime
+const { createRecord, reload } = __VUE_HMR_RUNTIME__
 
 function mountWithHydration(html: string, render: () => any) {
   const container = document.createElement('div')
@@ -1843,6 +1846,60 @@ describe('SSR hydration', () => {
     }
   })
 
+  test('hmr reload child wrapped in KeepAlive', async () => {
+    const id = 'child-reload'
+    const Child = {
+      __hmrId: id,
+      template: `<div>foo</div>`,
+    }
+    createRecord(id, Child)
+
+    const appId = 'test-app-id'
+    const App = {
+      __hmrId: appId,
+      components: { Child },
+      template: `
+      <div>
+        <KeepAlive>
+          <Child />
+        </KeepAlive>
+      </div>
+      `,
+    }
+
+    const root = document.createElement('div')
+    root.innerHTML = await renderToString(h(App))
+    createSSRApp(App).mount(root)
+    expect(root.innerHTML).toBe('<div><div>foo</div></div>')
+
+    reload(id, {
+      __hmrId: id,
+      template: `<div>bar</div>`,
+    })
+    await nextTick()
+    expect(root.innerHTML).toBe('<div><div>bar</div></div>')
+  })
+
+  test('hmr root reload', async () => {
+    const appId = 'test-app-id'
+    const App = {
+      __hmrId: appId,
+      template: `<div>foo</div>`,
+    }
+
+    const root = document.createElement('div')
+    root.innerHTML = await renderToString(h(App))
+    createSSRApp(App).mount(root)
+    expect(root.innerHTML).toBe('<div>foo</div>')
+
+    reload(appId, {
+      __hmrId: appId,
+      template: `<div>bar</div>`,
+    })
+    await nextTick()
+    expect(root.innerHTML).toBe('<div>bar</div>')
+  })
+
   describe('mismatch handling', () => {
     test('text node', () => {
       const { container } = mountWithHydration(`foo`, () => 'bar')

+ 1 - 1
packages/runtime-core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/runtime-core",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/runtime-core",
   "main": "index.js",
   "module": "dist/runtime-core.esm-bundler.js",

+ 4 - 5
packages/runtime-core/src/apiCreateApp.ts

@@ -377,13 +377,12 @@ export function createAppAPI<HostElement>(
           // HMR root reload
           if (__DEV__) {
             context.reload = () => {
+              const cloned = cloneVNode(vnode)
+              // avoid hydration for hmr updating
+              cloned.el = null
               // casting to ElementNamespace because TS doesn't guarantee type narrowing
               // over function boundaries
-              render(
-                cloneVNode(vnode),
-                rootContainer,
-                namespace as ElementNamespace,
-              )
+              render(cloned, rootContainer, namespace as ElementNamespace)
             }
           }
 

+ 1 - 1
packages/runtime-core/src/component.ts

@@ -806,7 +806,7 @@ export function setupComponent(
   const { props, children } = instance.vnode
   const isStateful = isStatefulComponent(instance)
   initProps(instance, props, isStateful, isSSR)
-  initSlots(instance, children, optimized)
+  initSlots(instance, children, optimized || isSSR)
 
   const setupResult = isStateful
     ? setupStatefulComponent(instance, isSSR)

+ 13 - 3
packages/runtime-core/src/componentSlots.ts

@@ -17,7 +17,11 @@ import {
 } from '@vue/shared'
 import { warn } from './warning'
 import { isKeepAlive } from './components/KeepAlive'
-import { type ContextualRenderFn, withCtx } from './componentRenderContext'
+import {
+  type ContextualRenderFn,
+  currentRenderingInstance,
+  withCtx,
+} from './componentRenderContext'
 import { isHmrUpdating } from './hmr'
 import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
 import { TriggerOpTypes, trigger } from '@vue/reactivity'
@@ -75,6 +79,11 @@ export type RawSlots = {
    * @internal
    */
   _?: SlotFlags
+  /**
+   * cache indexes for slot content
+   * @internal
+   */
+  __?: number[]
 }
 
 const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
@@ -97,7 +106,8 @@ const normalizeSlot = (
     if (
       __DEV__ &&
       currentInstance &&
-      (!ctx || ctx.root === currentInstance.root)
+      !(ctx === null && currentRenderingInstance) &&
+      !(ctx && ctx.root !== currentInstance.root)
     ) {
       warn(
         `Slot "${key}" invoked outside of the render function: ` +
@@ -170,7 +180,7 @@ const assignSlots = (
     // when rendering the optimized slots by manually written render function,
     // do not copy the `slots._` compiler flag so that `renderSlot` creates
     // slot Fragment with BAIL patchFlag to force full updates
-    if (optimized || key !== '_') {
+    if (optimized || !isInternalKey(key)) {
       slots[key] = children[key]
     }
   }

+ 2 - 3
packages/runtime-core/src/components/BaseTransition.ts

@@ -501,9 +501,8 @@ function getInnerChild(vnode: VNode): VNode | undefined {
 
     return vnode
   }
-  // #7121 ensure get the child component subtree in case
-  // it's been replaced during HMR
-  if (__DEV__ && vnode.component) {
+  // #7121,#12465 get the component subtree if it's been mounted
+  if (vnode.component) {
     return vnode.component.subTree
   }
 

+ 2 - 1
packages/runtime-core/src/components/Teleport.ts

@@ -220,7 +220,8 @@ export const TeleportImpl = {
         // even in block tree mode we need to make sure all root-level nodes
         // in the teleport inherit previous DOM references so that they can
         // be moved in future patches.
-        traverseStaticChildren(n1, n2, true)
+        // in dev mode, deep traversal is necessary for HMR
+        traverseStaticChildren(n1, n2, !__DEV__)
       } else if (!optimized) {
         patchChildren(
           n1,

+ 9 - 1
packages/runtime-core/src/helpers/renderList.ts

@@ -1,9 +1,11 @@
 import type { VNode, VNodeChild } from '../vnode'
 import {
   isReactive,
+  isReadonly,
   isShallow,
   shallowReadArray,
   toReactive,
+  toReadonly,
 } from '@vue/reactivity'
 import { isArray, isObject, isString } from '@vue/shared'
 import { warn } from '../warning'
@@ -69,14 +71,20 @@ export function renderList(
   if (sourceIsArray || isString(source)) {
     const sourceIsReactiveArray = sourceIsArray && isReactive(source)
     let needsWrap = false
+    let isReadonlySource = false
     if (sourceIsReactiveArray) {
       needsWrap = !isShallow(source)
+      isReadonlySource = isReadonly(source)
       source = shallowReadArray(source)
     }
     ret = new Array(source.length)
     for (let i = 0, l = source.length; i < l; i++) {
       ret[i] = renderItem(
-        needsWrap ? toReactive(source[i]) : source[i],
+        needsWrap
+          ? isReadonlySource
+            ? toReadonly(toReactive(source[i]))
+            : toReactive(source[i])
+          : source[i],
         i,
         undefined,
         cached && cached[i],

+ 29 - 6
packages/runtime-core/src/renderer.ts

@@ -1208,12 +1208,12 @@ function baseCreateRenderer(
       }
     }
 
+    // avoid hydration for hmr updating
+    if (__DEV__ && isHmrUpdating) initialVNode.el = null
+
     // setup() is async. This component relies on async logic to be resolved
     // before proceeding
     if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
-      // avoid hydration for hmr updating
-      if (__DEV__ && isHmrUpdating) initialVNode.el = null
-
       parentSuspense &&
         parentSuspense.registerDep(instance, setupRenderEffect, optimized)
 
@@ -2099,7 +2099,9 @@ function baseCreateRenderer(
 
     // unset ref
     if (ref != null) {
+      pauseTracking()
       setRef(ref, null, parentSuspense, vnode, true)
+      resetTracking()
     }
 
     // #6593 should clean memo cache when unmount
@@ -2263,7 +2265,17 @@ function baseCreateRenderer(
       unregisterHMR(instance)
     }
 
-    const { bum, scope, job, subTree, um, m, a } = instance
+    const {
+      bum,
+      scope,
+      job,
+      subTree,
+      um,
+      m,
+      a,
+      parent,
+      slots: { __: slotCacheKeys },
+    } = instance
     invalidateMount(m)
     invalidateMount(a)
 
@@ -2272,6 +2284,13 @@ function baseCreateRenderer(
       invokeArrayFns(bum)
     }
 
+    // remove slots content from parent renderCache
+    if (parent && isArray(slotCacheKeys)) {
+      slotCacheKeys.forEach(v => {
+        parent.renderCache[v] = undefined
+      })
+    }
+
     if (
       __COMPAT__ &&
       isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
@@ -2485,11 +2504,15 @@ export function traverseStaticChildren(
       if (c2.type === Text) {
         c2.el = c1.el
       }
-      // also inherit for comment nodes, but not placeholders (e.g. v-if which
+      // #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
       // would have received .el during block patch)
-      if (__DEV__ && c2.type === Comment && !c2.el) {
+      if (c2.type === Comment && !c2.el) {
         c2.el = c1.el
       }
+
+      if (__DEV__) {
+        c2.el && (c2.el.__vnode = c2)
+      }
     }
   }
 }

+ 1 - 1
packages/runtime-dom/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/runtime-dom",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/runtime-dom",
   "main": "index.js",
   "module": "dist/runtime-dom.esm-bundler.js",

+ 2 - 0
packages/runtime-dom/src/components/TransitionGroup.ts

@@ -81,6 +81,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
           moveClass,
         )
       ) {
+        prevChildren = []
         return
       }
 
@@ -110,6 +111,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
         })
         el.addEventListener('transitionend', cb)
       })
+      prevChildren = []
     })
 
     return () => {

+ 33 - 1
packages/server-renderer/__tests__/ssrSlot.spec.ts

@@ -1,4 +1,4 @@
-import { createApp } from 'vue'
+import { createApp, defineAsyncComponent, h } from 'vue'
 import { renderToString } from '../src/renderToString'
 
 const components = {
@@ -154,6 +154,38 @@ describe('ssr: slot', () => {
     ).toBe(`<div><p>1</p><p>2</p></div>`)
   })
 
+  // #12438
+  test('async component slot with v-if true', async () => {
+    const Layout = defineAsyncComponent(() =>
+      Promise.resolve({
+        template: `<div><slot name="header">default header</slot></div>`,
+      }),
+    )
+    const LayoutLoader = {
+      setup(_: any, context: any) {
+        return () => h(Layout, {}, context.slots)
+      },
+    }
+    expect(
+      await renderToString(
+        createApp({
+          components: {
+            LayoutLoader,
+          },
+          template: `
+            <Suspense>
+              <LayoutLoader>
+                <template v-if="true" #header>
+                  new header
+                </template>
+              </LayoutLoader>
+            </Suspense>
+          `,
+        }),
+      ),
+    ).toBe(`<div><!--[--> new header <!--]--></div>`)
+  })
+
   // #11326
   test('dynamic component slot', async () => {
     expect(

+ 1 - 1
packages/server-renderer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/server-renderer",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "@vue/server-renderer",
   "main": "index.js",
   "module": "dist/server-renderer.esm-bundler.js",

+ 1 - 1
packages/shared/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/shared",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "internal utils shared across @vue packages",
   "main": "index.js",
   "module": "dist/shared.esm-bundler.js",

+ 1 - 1
packages/vue-compat/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vue/compat",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "Vue 3 compatibility build for Vue 2",
   "main": "index.js",
   "module": "dist/vue.runtime.esm-bundler.js",

+ 51 - 0
packages/vue/__tests__/e2e/TransitionGroup.spec.ts

@@ -645,4 +645,55 @@ describe('e2e: TransitionGroup', () => {
     },
     E2E_TIMEOUT,
   )
+
+  test(
+    'not leaking after children unmounted',
+    async () => {
+      const client = await page().createCDPSession()
+      await page().evaluate(async () => {
+        const { createApp, ref, nextTick } = (window as any).Vue
+        const show = ref(true)
+
+        createApp({
+          components: {
+            Child: {
+              setup: () => {
+                // Big arrays kick GC earlier
+                const test = ref([...Array(3000)].map((_, i) => ({ i })))
+                // @ts-expect-error - Custom property and same lib as runtime is used
+                window.__REF__ = new WeakRef(test)
+
+                return { test }
+              },
+              template: `
+              <p>{{ test.length }}</p>
+            `,
+            },
+          },
+          template: `
+          <transition-group>
+            <Child v-if="show" />
+          </transition-group>
+        `,
+          setup() {
+            return { show }
+          },
+        }).mount('#app')
+
+        show.value = false
+        await nextTick()
+      })
+
+      const isCollected = async () =>
+        // @ts-expect-error - Custom property
+        await page().evaluate(() => window.__REF__.deref() === undefined)
+
+      while ((await isCollected()) === false) {
+        await client.send('HeapProfiler.collectGarbage')
+      }
+
+      expect(await isCollected()).toBe(true)
+    },
+    E2E_TIMEOUT,
+  )
 })

+ 1 - 1
packages/vue/package.json

@@ -1,6 +1,6 @@
 {
   "name": "vue",
-  "version": "3.5.13",
+  "version": "3.5.14",
   "description": "The progressive JavaScript framework for building modern web UI.",
   "main": "index.js",
   "module": "dist/vue.runtime.esm-bundler.js",

File diff suppressed because it is too large
+ 266 - 173
pnpm-lock.yaml


+ 18 - 3
pnpm-workspace.yaml

@@ -3,10 +3,25 @@ packages:
   - 'packages-private/*'
 
 catalog:
-  '@babel/parser': ^7.27.0
-  '@babel/types': ^7.27.0
+  '@babel/parser': ^7.27.2
+  '@babel/types': ^7.27.1
   'estree-walker': ^2.0.2
   'magic-string': ^0.30.17
   'source-map-js': ^1.2.1
   'vite': ^5.4.15
-  '@vitejs/plugin-vue': ^5.2.3
+  '@vitejs/plugin-vue': ^5.2.4
+
+onlyBuiltDependencies:
+  - '@swc/core'
+  - 'esbuild'
+  - 'puppeteer'
+  - 'simple-git-hooks'
+  - 'unrs-resolver'
+
+peerDependencyRules:
+  allowedVersions:
+    'typescript-eslint>eslint': '^9.0.0'
+    '@typescript-eslint/eslint-plugin>eslint': '^9.0.0'
+    '@typescript-eslint/parser>eslint': '^9.0.0'
+    '@typescript-eslint/type-utils>eslint': '^9.0.0'
+    '@typescript-eslint/utils>eslint': '^9.0.0'

Some files were not shown because too many files changed in this diff