Skip to content

fix(compiler-core): remove slot cache from parent renderCache during unmounting #13215

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 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => {
{
/* _ slot flag */
},
{
type: NodeTypes.JS_PROPERTY,
key: { content: '__' },
value: { content: '[0]' },
},
],
})
})
Expand Down Expand Up @@ -197,6 +202,11 @@ describe('compiler: cacheStatic transform', () => {
{
/* _ slot flag */
},
{
type: NodeTypes.JS_PROPERTY,
key: { content: '__' },
value: { content: '[0]' },
},
],
})
})
Expand Down
27 changes: 27 additions & 0 deletions packages/compiler-core/src/transforms/cacheStatic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 &&
Expand All @@ -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[]),
)
Expand All @@ -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[]),
)
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion packages/compiler-core/src/transforms/vSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,6 @@ export function buildSlots(
: hasForwardedSlots(node.children)
? SlotFlags.FORWARDED
: SlotFlags.STABLE

let slots = createObjectExpression(
slotsProperties.concat(
createObjectProperty(
Expand Down
7 changes: 6 additions & 1 deletion packages/runtime-core/src/componentSlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export type RawSlots = {
* @internal
*/
_?: SlotFlags
/**
* cache indexes for slot content
* @internal
*/
__?: number[]
}

const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
Expand Down Expand Up @@ -170,7 +175,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]
}
}
Expand Down
19 changes: 18 additions & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2262,7 +2262,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)

Expand All @@ -2271,6 +2281,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)
Expand Down