Skip to content

fix(compiler-core): prevent cached array children from retaining detached dom nodes #13691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 */)
])))
]).slice())))
}
}"
`;
Expand All @@ -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")
Expand All @@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
_createElementVNode("span"),
_createElementVNode("span")
], -1 /* CACHED */)
])))
]).slice())))
}
}"
`;
Expand All @@ -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 */)
])))
]).slice())))
}
}"
`;
Expand All @@ -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 */)
])))
]).slice())))
}
}"
`;
Expand All @@ -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 */)
])))
]).slice())))
}
}"
`;
Expand Down Expand Up @@ -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 */)
])))
]).slice())))
}
}"
`;
Expand All @@ -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 */)
])))
]).slice())))
}
}"
`;
Expand All @@ -177,9 +177,9 @@ return function render(_ctx, _cache) {

return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
return (_openBlock(), _createElementBlock("div", null, [...((_cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
]))]))
]).slice()))]))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
Expand Down Expand Up @@ -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 */)
]))), [
]).slice()))), [
[_directive_foo]
])
]))
Expand Down Expand Up @@ -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 */)
])))
]).slice())))
: _createCommentVNode("v-if", true)
]))
}
Expand All @@ -422,15 +422,15 @@ 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" }, [
_createElementVNode("div", { id: "e" }, "hello")
])
])
], -1 /* CACHED */)
]))
]).slice()))
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
}
}"
Expand All @@ -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 */)
])))
]).slice())))
}), 256 /* UNKEYED_FRAGMENT */))
]))
}
Expand Down
10 changes: 0 additions & 10 deletions packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
{
/* _ slot flag */
},
{
type: NodeTypes.JS_PROPERTY,
key: { content: '__' },
value: { content: '[0]' },
},
],
})
})
Expand Down Expand Up @@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
{
/* _ slot flag */
},
{
type: NodeTypes.JS_PROPERTY,
key: { content: '__' },
value: { content: '[0]' },
},
],
})
})
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ export interface CacheExpression extends Node {
needPauseTracking: boolean
inVOnce: boolean
needArraySpread: boolean
cachedAsArray: boolean
}

export interface MemoExpression extends CallExpression {
Expand Down Expand Up @@ -784,6 +785,7 @@ export function createCacheExpression(
needPauseTracking: needPauseTracking,
inVOnce,
needArraySpread: false,
cachedAsArray: false,
loc: locStub,
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/compiler-core/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,10 +1012,11 @@ function genConditionalExpression(

function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
const { needPauseTracking, needArraySpread } = node
const { needPauseTracking, needArraySpread, cachedAsArray } = node
if (needArraySpread) {
push(`[...(`)
}
if (cachedAsArray) push(`(`)
push(`_cache[${node.index}] || (`)
if (needPauseTracking) {
indent()
Expand All @@ -1027,6 +1028,7 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
if (cachedAsArray) push(`).slice()`)
if (needPauseTracking) {
push(`).cacheIndex = ${node.index},`)
newline()
Expand Down
33 changes: 5 additions & 28 deletions packages/compiler-core/src/transforms/cacheStatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 &&
Expand All @@ -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[]),
)
Expand All @@ -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[]),
)
Expand All @@ -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 {
function getCacheExpression(
value: JSChildNode,
cachedAsArray: boolean = true,
): 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
}
exp.cachedAsArray = cachedAsArray
return exp
}

Expand Down
Loading