Skip to content

Commit 7f60ef8

Browse files
authored
fix(compiler-core): prevent cached array children from retaining detached dom nodes (#13691)
fix element-plus/element-plus#21408 Re-fix #13211
1 parent 6e5143d commit 7f60ef8

File tree

10 files changed

+208
-125
lines changed

10 files changed

+208
-125
lines changed

packages/compiler-core/__tests__/transforms/__snapshots__/cacheStatic.spec.ts.snap

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
77
with (_ctx) {
88
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
99
10-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
10+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
1111
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
12-
])))
12+
]))]))
1313
}
1414
}"
1515
`;
@@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
2121
with (_ctx) {
2222
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
2323
24-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
24+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
2525
_createElementVNode("p", null, [
2626
_createElementVNode("span"),
2727
_createElementVNode("span")
@@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
3030
_createElementVNode("span"),
3131
_createElementVNode("span")
3232
], -1 /* CACHED */)
33-
])))
33+
]))]))
3434
}
3535
}"
3636
`;
@@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
4242
with (_ctx) {
4343
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
4444
45-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
45+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
4646
_createElementVNode("div", null, [
4747
_createCommentVNode("comment")
4848
], -1 /* CACHED */)
49-
])))
49+
]))]))
5050
}
5151
}"
5252
`;
@@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
5858
with (_ctx) {
5959
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
6060
61-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
61+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
6262
_createElementVNode("span", null, null, -1 /* CACHED */),
6363
_createTextVNode("foo", -1 /* CACHED */),
6464
_createElementVNode("div", null, null, -1 /* CACHED */)
65-
])))
65+
]))]))
6666
}
6767
}"
6868
`;
@@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
7474
with (_ctx) {
7575
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
7676
77-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
77+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
7878
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
79-
])))
79+
]))]))
8080
}
8181
}"
8282
`;
@@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
147147
with (_ctx) {
148148
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
149149
150-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
150+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
151151
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
152-
])))
152+
]))]))
153153
}
154154
}"
155155
`;
@@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
161161
with (_ctx) {
162162
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
163163
164-
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
164+
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
165165
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
166-
])))
166+
]))]))
167167
}
168168
}"
169169
`;
@@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
215215
const _directive_foo = _resolveDirective("foo")
216216
217217
return (_openBlock(), _createElementBlock("div", null, [
218-
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
218+
_withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
219219
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
220-
]))), [
220+
]))])), [
221221
[_directive_foo]
222222
])
223223
]))
@@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
401401
402402
return (_openBlock(), _createElementBlock("div", null, [
403403
ok
404-
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
404+
? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
405405
_createElementVNode("span", null, null, -1 /* CACHED */)
406-
])))
406+
]))]))
407407
: _createCommentVNode("v-if", true)
408408
]))
409409
}
@@ -422,15 +422,15 @@ return function render(_ctx, _cache) {
422422
423423
return (_openBlock(), _createElementBlock(_Fragment, null, [
424424
_createCommentVNode("comment"),
425-
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
425+
_createElementVNode("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
426426
_createElementVNode("div", { id: "b" }, [
427427
_createElementVNode("div", { id: "c" }, [
428428
_createElementVNode("div", { id: "d" }, [
429429
_createElementVNode("div", { id: "e" }, "hello")
430430
])
431431
])
432432
], -1 /* CACHED */)
433-
]))
433+
]))])
434434
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
435435
}
436436
}"
@@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
448448
449449
return (_openBlock(), _createElementBlock("div", null, [
450450
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
451-
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
451+
return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
452452
_createElementVNode("span", null, null, -1 /* CACHED */)
453-
])))
453+
]))]))
454454
}), 256 /* UNKEYED_FRAGMENT */))
455455
]))
456456
}

packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
2727

2828
const cachedChildrenArrayMatcher = (
2929
tags: string[],
30-
needArraySpread = false,
30+
needArraySpread = true,
3131
) => ({
3232
type: NodeTypes.JS_CACHE_EXPRESSION,
3333
needArraySpread,
@@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
170170
{
171171
/* _ slot flag */
172172
},
173-
{
174-
type: NodeTypes.JS_PROPERTY,
175-
key: { content: '__' },
176-
value: { content: '[0]' },
177-
},
178173
],
179174
})
180175
})
@@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
202197
{
203198
/* _ slot flag */
204199
},
205-
{
206-
type: NodeTypes.JS_PROPERTY,
207-
key: { content: '__' },
208-
value: { content: '[0]' },
209-
},
210200
],
211201
})
212202
})

packages/compiler-core/src/transforms/cacheStatic.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@ import {
1212
type RootNode,
1313
type SimpleExpressionNode,
1414
type SlotFunctionExpression,
15-
type SlotsObjectProperty,
1615
type TemplateChildNode,
1716
type TemplateNode,
1817
type TextCallNode,
1918
type VNodeCall,
2019
createArrayExpression,
21-
createObjectProperty,
22-
createSimpleExpression,
2320
getVNodeBlockHelper,
2421
getVNodeHelper,
2522
} from '../ast'
@@ -157,7 +154,6 @@ function walk(
157154
}
158155

159156
let cachedAsArray = false
160-
const slotCacheKeys = []
161157
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
162158
if (
163159
node.tagType === ElementTypes.ELEMENT &&
@@ -181,7 +177,6 @@ function walk(
181177
// default slot
182178
const slot = getSlotNode(node.codegenNode, 'default')
183179
if (slot) {
184-
slotCacheKeys.push(context.cached.length)
185180
slot.returns = getCacheExpression(
186181
createArrayExpression(slot.returns as TemplateChildNode[]),
187182
)
@@ -205,7 +200,6 @@ function walk(
205200
slotName.arg &&
206201
getSlotNode(parent.codegenNode, slotName.arg)
207202
if (slot) {
208-
slotCacheKeys.push(context.cached.length)
209203
slot.returns = getCacheExpression(
210204
createArrayExpression(slot.returns as TemplateChildNode[]),
211205
)
@@ -216,39 +210,22 @@ function walk(
216210

217211
if (!cachedAsArray) {
218212
for (const child of toCache) {
219-
slotCacheKeys.push(context.cached.length)
220213
child.codegenNode = context.cache(child.codegenNode!)
221214
}
222215
}
223216

224-
// put the slot cached keys on the slot object, so that the cache
225-
// can be removed when component unmounting to prevent memory leaks
226-
if (
227-
slotCacheKeys.length &&
228-
node.type === NodeTypes.ELEMENT &&
229-
node.tagType === ElementTypes.COMPONENT &&
230-
node.codegenNode &&
231-
node.codegenNode.type === NodeTypes.VNODE_CALL &&
232-
node.codegenNode.children &&
233-
!isArray(node.codegenNode.children) &&
234-
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
235-
) {
236-
node.codegenNode.children.properties.push(
237-
createObjectProperty(
238-
`__`,
239-
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
240-
) as SlotsObjectProperty,
241-
)
242-
}
243-
244217
function getCacheExpression(value: JSChildNode): CacheExpression {
245218
const exp = context.cache(value)
246219
// #6978, #7138, #7114
247220
// a cached children array inside v-for can caused HMR errors since
248221
// it might be mutated when mounting the first item
249-
if (inFor && context.hmr) {
250-
exp.needArraySpread = true
251-
}
222+
// #13221
223+
// fix memory leak in cached array:
224+
// cached vnodes get replaced by cloned ones during mountChildren,
225+
// which bind DOM elements. These DOM references persist after unmount,
226+
// preventing garbage collection. Array spread avoids mutating cached
227+
// array, preventing memory leaks.
228+
exp.needArraySpread = true
252229
return exp
253230
}
254231

0 commit comments

Comments
 (0)