mirror of https://github.com/vuejs/core.git
fix(compiler-core): prevent cached array children from retaining detached dom nodes (#13691)
fix element-plus/element-plus#21408 Re-fix #13211
This commit is contained in:
parent
6e5143d963
commit
7f60ef83e7
|
@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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 */)
|
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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("p", null, [
|
||||||
_createElementVNode("span"),
|
_createElementVNode("span"),
|
||||||
_createElementVNode("span")
|
_createElementVNode("span")
|
||||||
|
@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("span"),
|
_createElementVNode("span"),
|
||||||
_createElementVNode("span")
|
_createElementVNode("span")
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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, [
|
_createElementVNode("div", null, [
|
||||||
_createCommentVNode("comment")
|
_createCommentVNode("comment")
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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 */),
|
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||||
_createTextVNode("foo", -1 /* CACHED */),
|
_createTextVNode("foo", -1 /* CACHED */),
|
||||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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 */)
|
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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 */)
|
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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 */)
|
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
|
||||||
const _directive_foo = _resolveDirective("foo")
|
const _directive_foo = _resolveDirective("foo")
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
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 */)
|
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||||
]))), [
|
]))])), [
|
||||||
[_directive_foo]
|
[_directive_foo]
|
||||||
])
|
])
|
||||||
]))
|
]))
|
||||||
|
@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
ok
|
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 */)
|
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
: _createCommentVNode("v-if", true)
|
: _createCommentVNode("v-if", true)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
@ -422,7 +422,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_createCommentVNode("comment"),
|
_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: "b" }, [
|
||||||
_createElementVNode("div", { id: "c" }, [
|
_createElementVNode("div", { id: "c" }, [
|
||||||
_createElementVNode("div", { id: "d" }, [
|
_createElementVNode("div", { id: "d" }, [
|
||||||
|
@ -430,7 +430,7 @@ return function render(_ctx, _cache) {
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
]))
|
]))])
|
||||||
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
(_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 */)
|
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
const cachedChildrenArrayMatcher = (
|
const cachedChildrenArrayMatcher = (
|
||||||
tags: string[],
|
tags: string[],
|
||||||
needArraySpread = false,
|
needArraySpread = true,
|
||||||
) => ({
|
) => ({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
needArraySpread,
|
needArraySpread,
|
||||||
|
@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: NodeTypes.JS_PROPERTY,
|
|
||||||
key: { content: '__' },
|
|
||||||
value: { content: '[0]' },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: NodeTypes.JS_PROPERTY,
|
|
||||||
key: { content: '__' },
|
|
||||||
value: { content: '[0]' },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,14 +12,11 @@ import {
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
type SlotFunctionExpression,
|
type SlotFunctionExpression,
|
||||||
type SlotsObjectProperty,
|
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type TemplateNode,
|
type TemplateNode,
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type VNodeCall,
|
type VNodeCall,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createObjectProperty,
|
|
||||||
createSimpleExpression,
|
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper,
|
getVNodeHelper,
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
|
@ -157,7 +154,6 @@ function walk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedAsArray = false
|
let cachedAsArray = false
|
||||||
const slotCacheKeys = []
|
|
||||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||||
if (
|
if (
|
||||||
node.tagType === ElementTypes.ELEMENT &&
|
node.tagType === ElementTypes.ELEMENT &&
|
||||||
|
@ -181,7 +177,6 @@ function walk(
|
||||||
// default slot
|
// default slot
|
||||||
const slot = getSlotNode(node.codegenNode, 'default')
|
const slot = getSlotNode(node.codegenNode, 'default')
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -205,7 +200,6 @@ function walk(
|
||||||
slotName.arg &&
|
slotName.arg &&
|
||||||
getSlotNode(parent.codegenNode, slotName.arg)
|
getSlotNode(parent.codegenNode, slotName.arg)
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -216,39 +210,22 @@ function walk(
|
||||||
|
|
||||||
if (!cachedAsArray) {
|
if (!cachedAsArray) {
|
||||||
for (const child of toCache) {
|
for (const child of toCache) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
child.codegenNode = context.cache(child.codegenNode!)
|
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): CacheExpression {
|
||||||
const exp = context.cache(value)
|
const exp = context.cache(value)
|
||||||
// #6978, #7138, #7114
|
// #6978, #7138, #7114
|
||||||
// a cached children array inside v-for can caused HMR errors since
|
// a cached children array inside v-for can caused HMR errors since
|
||||||
// it might be mutated when mounting the first item
|
// it might be mutated when mounting the first item
|
||||||
if (inFor && context.hmr) {
|
// #13221
|
||||||
exp.needArraySpread = true
|
// 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
|
return exp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
||||||
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
||||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 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
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
|
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 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
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 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
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ exports[`stringify static html > should bail for <option> elements with null val
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("select", null, [
|
_createElementVNode("select", null, [
|
||||||
_createElementVNode("option", { value: null }),
|
_createElementVNode("option", { value: null }),
|
||||||
_createElementVNode("option", { value: "1" }),
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
@ -55,7 +55,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("option", { value: "1" }),
|
_createElementVNode("option", { value: "1" }),
|
||||||
_createElementVNode("option", { value: "1" })
|
_createElementVNode("option", { value: "1" })
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ exports[`stringify static html > should bail for <option> elements with number v
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("select", null, [
|
_createElementVNode("select", null, [
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
|
@ -71,7 +71,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
_createElementVNode("option", { value: 1 })
|
_createElementVNode("option", { value: 1 })
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ exports[`stringify static html > should bail on bindings that are cached but not
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", null, [
|
_createElementVNode("div", null, [
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
|
@ -104,7 +104,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
_createElementVNode("img", { src: _imports_0_ })
|
_createElementVNode("img", { src: _imports_0_ })
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -112,9 +112,9 @@ exports[`stringify static html > should work for <option> elements with string v
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -132,9 +132,9 @@ exports[`stringify static html > should work on eligible content (elements > 20)
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -142,9 +142,9 @@ exports[`stringify static html > should work on eligible content (elements with
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -152,9 +152,9 @@ exports[`stringify static html > should work with bindings that are non-static b
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -81,9 +81,9 @@ import _imports_1 from '/bar.png'
|
||||||
|
|
||||||
|
|
||||||
export function render(_ctx, _cache) {
|
export function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -238,8 +238,8 @@ const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
|
||||||
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||||
|
|
||||||
export function render(_ctx, _cache) {
|
export function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -56,14 +56,10 @@ describe('component: slots', () => {
|
||||||
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
expect(slots).toHaveProperty('__')
|
|
||||||
expect(Object.getOwnPropertyDescriptor(slots, '__')!.enumerable).toBe(
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
return h('div')
|
return h('div')
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const slots = { foo: () => {}, _: 1, __: [1] }
|
const slots = { foo: () => {}, _: 1 }
|
||||||
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -79,15 +79,10 @@ export type RawSlots = {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_?: SlotFlags
|
_?: SlotFlags
|
||||||
/**
|
|
||||||
* cache indexes for slot content
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
__?: number[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInternalKey = (key: string) =>
|
const isInternalKey = (key: string) =>
|
||||||
key === '_' || key === '__' || key === '_ctx' || key === '$stable'
|
key === '_' || key === '_ctx' || key === '$stable'
|
||||||
|
|
||||||
const normalizeSlotValue = (value: unknown): VNode[] =>
|
const normalizeSlotValue = (value: unknown): VNode[] =>
|
||||||
isArray(value)
|
isArray(value)
|
||||||
|
@ -194,10 +189,6 @@ export const initSlots = (
|
||||||
): void => {
|
): void => {
|
||||||
const slots = (instance.slots = createInternalObject())
|
const slots = (instance.slots = createInternalObject())
|
||||||
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
|
||||||
const cacheIndexes = (children as RawSlots).__
|
|
||||||
// make cache indexes marker non-enumerable
|
|
||||||
if (cacheIndexes) def(slots, '__', cacheIndexes, true)
|
|
||||||
|
|
||||||
const type = (children as RawSlots)._
|
const type = (children as RawSlots)._
|
||||||
if (type) {
|
if (type) {
|
||||||
assignSlots(slots, children as Slots, optimized)
|
assignSlots(slots, children as Slots, optimized)
|
||||||
|
|
|
@ -2277,17 +2277,7 @@ function baseCreateRenderer(
|
||||||
unregisterHMR(instance)
|
unregisterHMR(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { bum, scope, job, subTree, um, m, a } = instance
|
||||||
bum,
|
|
||||||
scope,
|
|
||||||
job,
|
|
||||||
subTree,
|
|
||||||
um,
|
|
||||||
m,
|
|
||||||
a,
|
|
||||||
parent,
|
|
||||||
slots: { __: slotCacheKeys },
|
|
||||||
} = instance
|
|
||||||
invalidateMount(m)
|
invalidateMount(m)
|
||||||
invalidateMount(a)
|
invalidateMount(a)
|
||||||
|
|
||||||
|
@ -2296,13 +2286,6 @@ function baseCreateRenderer(
|
||||||
invokeArrayFns(bum)
|
invokeArrayFns(bum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove slots content from parent renderCache
|
|
||||||
if (parent && isArray(slotCacheKeys)) {
|
|
||||||
slotCacheKeys.forEach(v => {
|
|
||||||
parent.renderCache[v] = undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
||||||
|
@ -2513,7 +2496,11 @@ export function traverseStaticChildren(
|
||||||
traverseStaticChildren(c1, c2)
|
traverseStaticChildren(c1, c2)
|
||||||
}
|
}
|
||||||
// #6852 also inherit for text nodes
|
// #6852 also inherit for text nodes
|
||||||
if (c2.type === Text) {
|
if (
|
||||||
|
c2.type === Text &&
|
||||||
|
// avoid cached text nodes retaining detached dom nodes
|
||||||
|
c2.patchFlag !== PatchFlags.CACHED
|
||||||
|
) {
|
||||||
c2.el = c1.el
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
// #2324 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
|
||||||
|
|
|
@ -82,4 +82,146 @@ describe('not leaking', async () => {
|
||||||
},
|
},
|
||||||
E2E_TIMEOUT,
|
E2E_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// #13211
|
||||||
|
test(
|
||||||
|
'cached array vnodes should not retaining detached DOM nodes',
|
||||||
|
async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Comp1: {
|
||||||
|
template: `
|
||||||
|
<h1><slot></slot></h1>
|
||||||
|
<div>{{ test.length }}</div>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const test = ref([...Array(3000)].map((_, i) => ({ i })))
|
||||||
|
// @ts-expect-error
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
<Comp1 v-if="toggle">slot content</Comp1>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
return { toggle, click }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<h1>` +
|
||||||
|
`slot content` +
|
||||||
|
`</h1>` +
|
||||||
|
`<div>3000</div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="toggleBtn">button</button><!--v-if-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/element-plus/element-plus/issues/21408
|
||||||
|
test(
|
||||||
|
'cached text nodes in Fragment should not retaining detached DOM nodes',
|
||||||
|
async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Comp: {
|
||||||
|
template: `<div>{{ test.length }}</div>`,
|
||||||
|
setup() {
|
||||||
|
const test = ref([...Array(3000)].map((_, i) => ({ i })))
|
||||||
|
// @ts-expect-error
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button id="addBtn" @click="add">add</button>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
<div v-if="toggle">
|
||||||
|
<template v-for="item in items" :key="item">
|
||||||
|
text
|
||||||
|
<div>{{ item }}</div>
|
||||||
|
</template>
|
||||||
|
<Comp/>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const items = ref([1])
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
const add = () => items.value.push(2)
|
||||||
|
return { toggle, click, items, add }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="addBtn">add</button>` +
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<div>` +
|
||||||
|
` text ` +
|
||||||
|
`<div>1</div>` +
|
||||||
|
`<div>3000</div></div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#addBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="addBtn">add</button>` +
|
||||||
|
`<button id="toggleBtn">button</button>` +
|
||||||
|
`<div>` +
|
||||||
|
` text ` +
|
||||||
|
`<div>1</div>` +
|
||||||
|
` text ` +
|
||||||
|
`<div>2</div>` +
|
||||||
|
`<div>3000</div></div>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await click('#toggleBtn')
|
||||||
|
expect(await html('#app')).toBe(
|
||||||
|
`<button id="addBtn">add</button>` +
|
||||||
|
`<button id="toggleBtn">button</button><!--v-if-->`,
|
||||||
|
)
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue