diff --git a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts index ab5ed7baede..358c0e31c3d 100644 --- a/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/cacheStatic.spec.ts @@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => { { /* _ slot flag */ }, + { + type: NodeTypes.JS_PROPERTY, + key: { content: '__' }, + value: { content: '[0]' }, + }, ], }) }) @@ -197,6 +202,11 @@ 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 8d5961643c1..e5d67380640 100644 --- a/packages/compiler-core/src/transforms/cacheStatic.ts +++ b/packages/compiler-core/src/transforms/cacheStatic.ts @@ -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' @@ -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 && @@ -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[]), ) @@ -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[]), ) @@ -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 diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index db367f39c0c..28625439a47 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -342,7 +342,6 @@ export function buildSlots( : hasForwardedSlots(node.children) ? SlotFlags.FORWARDED : SlotFlags.STABLE - let slots = createObjectExpression( slotsProperties.concat( createObjectProperty( diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 8f8392f1cdb..44246add9b9 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -75,6 +75,11 @@ export type RawSlots = { * @internal */ _?: SlotFlags + /** + * cache indexes for slot content + * @internal + */ + __?: number[] } const isInternalKey = (key: string) => key[0] === '_' || key === '$stable' @@ -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] } } diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 05c4ac345eb..02f6105ef26 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -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) @@ -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)