diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
index 91a82db5bba..7d5e47719eb 100644
--- a/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
+++ b/packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap
@@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("p", null, [
_createElementVNode("span"),
_createElementVNode("span")
@@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
_createElementVNode("span"),
_createElementVNode("span")
], -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", null, [
_createCommentVNode("comment")
], -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */),
_createTextVNode("foo", -1 /* CACHED */),
_createElementVNode("div", null, null, -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
with (_ctx) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
- ])))
+ ]))]))
}
}"
`;
@@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
const _directive_foo = _resolveDirective("foo")
return (_openBlock(), _createElementBlock("div", null, [
- _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
+ _withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
- ]))), [
+ ]))])), [
[_directive_foo]
])
]))
@@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
ok
- ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ ? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */)
- ])))
+ ]))]))
: _createCommentVNode("v-if", true)
]))
}
@@ -422,7 +422,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createCommentVNode("comment"),
- _createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ _createElementVNode("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
_createElementVNode("div", { id: "b" }, [
_createElementVNode("div", { id: "c" }, [
_createElementVNode("div", { id: "d" }, [
@@ -430,7 +430,7 @@ return function render(_ctx, _cache) {
])
])
], -1 /* CACHED */)
- ]))
+ ]))])
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
}
}"
@@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
- return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* CACHED */)
- ])))
+ ]))]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
diff --git a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
index 74f6caca328..1b5b23fec29 100644
--- a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
@@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
const cachedChildrenArrayMatcher = (
tags: string[],
- needArraySpread = false,
+ needArraySpread = true,
) => ({
type: NodeTypes.JS_CACHE_EXPRESSION,
needArraySpread,
@@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
{
/* _ slot flag */
},
- {
- type: NodeTypes.JS_PROPERTY,
- key: { content: '__' },
- value: { content: '[0]' },
- },
],
})
})
@@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
{
/* _ slot flag */
},
- {
- type: NodeTypes.JS_PROPERTY,
- key: { content: '__' },
- value: { content: '[0]' },
- },
],
})
})
diff --git a/packages/compiler-core/src/transforms/cacheStatic.ts b/packages/compiler-core/src/transforms/cacheStatic.ts
index 0f112e19cad..c4d29f71248 100644
--- a/packages/compiler-core/src/transforms/cacheStatic.ts
+++ b/packages/compiler-core/src/transforms/cacheStatic.ts
@@ -12,14 +12,11 @@ import {
type RootNode,
type SimpleExpressionNode,
type SlotFunctionExpression,
- type SlotsObjectProperty,
type TemplateChildNode,
type TemplateNode,
type TextCallNode,
type VNodeCall,
createArrayExpression,
- createObjectProperty,
- createSimpleExpression,
getVNodeBlockHelper,
getVNodeHelper,
} from '../ast'
@@ -157,7 +154,6 @@ function walk(
}
let cachedAsArray = false
- const slotCacheKeys = []
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
if (
node.tagType === ElementTypes.ELEMENT &&
@@ -181,7 +177,6 @@ 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[]),
)
@@ -205,7 +200,6 @@ function walk(
slotName.arg &&
getSlotNode(parent.codegenNode, slotName.arg)
if (slot) {
- slotCacheKeys.push(context.cached.length)
slot.returns = getCacheExpression(
createArrayExpression(slot.returns as TemplateChildNode[]),
)
@@ -216,39 +210,22 @@ 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
// a cached children array inside v-for can caused HMR errors since
// it might be mutated when mounting the first item
- if (inFor && context.hmr) {
- exp.needArraySpread = true
- }
+ // #13221
+ // fix memory leak in cached array:
+ // cached vnodes get replaced by cloned ones during mountChildren,
+ // which bind DOM elements. These DOM references persist after unmount,
+ // preventing garbage collection. Array spread avoids mutating cached
+ // array, preventing memory leaks.
+ exp.needArraySpread = true
return exp
}
diff --git a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
index 5bc40d3fab5..84c3024f6bf 100644
--- a/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
+++ b/packages/compiler-dom/__tests__/transforms/__snapshots__/stringifyStatic.spec.ts.snap
@@ -4,11 +4,11 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("", 20),
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
_createStaticVNode("", 20)
- ])))
+ ]))]))
}"
`;
@@ -16,9 +16,9 @@ exports[`stringify static html > escape 1`] = `
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("
1 + <&1 + <&1 + <&1 + <&1 + <&
", 1)
- ])))
+ ]))]))
}"
`;
@@ -26,9 +26,9 @@ exports[`stringify static html > serializing constant bindings 1`] = `
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("1 + false1 + false1 + false1 + false1 + false
", 1)
- ])))
+ ]))]))
}"
`;
@@ -36,9 +36,9 @@ exports[`stringify static html > serializing template string style 1`] = `
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
return function render(_ctx, _cache) {
- return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
+ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createStaticVNode("1 + false1 + false1 + false1 + false1 + false
", 1)
- ])))
+ ]))]))
}"
`;
@@ -46,7 +46,7 @@ exports[`stringify static html > should bail for