diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
index c9a2e3e7cb9..0942b007df2 100644
--- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts
@@ -719,6 +719,23 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
+ test('generate flag on forwarded slots', () => {
+ const { slots } = parseWithSlots(``)
+ expect(slots).toMatchObject({
+ type: NodeTypes.JS_OBJECT_EXPRESSION,
+ properties: [
+ {
+ key: { content: `default` },
+ value: { type: NodeTypes.JS_FUNCTION_EXPRESSION }
+ },
+ {
+ key: { content: `_` },
+ value: { content: `3` } // forwarded
+ }
+ ]
+ })
+ })
+
describe('errors', () => {
test('error on extraneous children w/ named default slot', () => {
const onError = jest.fn()
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index 0cec12745b1..fa8e471f674 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -33,6 +33,7 @@ import {
} from '../utils'
import { CREATE_SLOTS, RENDER_LIST, WITH_CTX } from '../runtimeHelpers'
import { parseForExpression, createForLoopParams } from './vFor'
+import { SlotFlags } from '@vue/shared/src'
const defaultFallback = createSimpleExpression(`undefined`, false)
@@ -321,13 +322,19 @@ export function buildSlots(
}
}
+ const slotFlag = hasDynamicSlots
+ ? SlotFlags.DYNAMIC
+ : hasForwardedSlots(node.children)
+ ? SlotFlags.FORWARDED
+ : SlotFlags.STABLE
+
let slots = createObjectExpression(
slotsProperties.concat(
createObjectProperty(
`_`,
// 2 = compiled but dynamic = can skip normalization, but must run diff
// 1 = compiled and static = can skip normalization AND diff as optimized
- createSimpleExpression(hasDynamicSlots ? `2` : `1`, false)
+ createSimpleExpression('' + slotFlag, false)
)
),
loc
@@ -354,3 +361,19 @@ function buildDynamicSlot(
createObjectProperty(`fn`, fn)
])
}
+
+function hasForwardedSlots(children: TemplateChildNode[]): boolean {
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i]
+ if (child.type === NodeTypes.ELEMENT) {
+ if (
+ child.tagType === ElementTypes.SLOT ||
+ (child.tagType === ElementTypes.ELEMENT &&
+ hasForwardedSlots(child.children))
+ ) {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts
index b0a52b0f505..4a6a1b75375 100644
--- a/packages/runtime-core/src/componentSlots.ts
+++ b/packages/runtime-core/src/componentSlots.ts
@@ -12,7 +12,8 @@ import {
EMPTY_OBJ,
ShapeFlags,
extend,
- def
+ def,
+ SlotFlags
} from '@vue/shared'
import { warn } from './warning'
import { isKeepAlive } from './components/KeepAlive'
@@ -27,24 +28,25 @@ export type InternalSlots = {
export type Slots = Readonly
-export const enum CompiledSlotTypes {
- STATIC = 1,
- DYNAMIC = 2
-}
-
export type RawSlots = {
[name: string]: unknown
// manual render fn hint to skip forced children updates
$stable?: boolean
- // internal, for tracking slot owner instance. This is attached during
- // normalizeChildren when the component vnode is created.
+ /**
+ * for tracking slot owner instance. This is attached during
+ * normalizeChildren when the component vnode is created.
+ * @internal
+ */
_ctx?: ComponentInternalInstance | null
- // internal, indicates compiler generated slots
- // we use a reserved property instead of a vnode patchFlag because the slots
- // object may be directly passed down to a child component in a manual
- // render function, and the optimization hint need to be on the slot object
- // itself to be preserved.
- _?: CompiledSlotTypes
+ /**
+ * indicates compiler generated slots
+ * we use a reserved property instead of a vnode patchFlag because the slots
+ * object may be directly passed down to a child component in a manual
+ * render function, and the optimization hint need to be on the slot object
+ * itself to be preserved.
+ * @internal
+ */
+ _?: SlotFlags
}
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
@@ -141,8 +143,8 @@ export const updateSlots = (
// Parent was HMR updated so slot content may have changed.
// force update slots and mark instance for hmr as well
extend(slots, children as Slots)
- } else if (type === CompiledSlotTypes.STATIC) {
- // compiled AND static.
+ } else if (type === SlotFlags.STABLE) {
+ // compiled AND stable.
// no need to update, and skip stale slots removal.
needDeletionCheck = false
} else {
diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts
index 0cfb6ed694f..8744b4c3053 100644
--- a/packages/runtime-core/src/helpers/renderSlot.ts
+++ b/packages/runtime-core/src/helpers/renderSlot.ts
@@ -1,5 +1,5 @@
import { Data } from '../component'
-import { Slots, RawSlots, CompiledSlotTypes } from '../componentSlots'
+import { Slots, RawSlots } from '../componentSlots'
import {
VNodeArrayChildren,
openBlock,
@@ -7,7 +7,7 @@ import {
Fragment,
VNode
} from '../vnode'
-import { PatchFlags } from '@vue/shared'
+import { PatchFlags, SlotFlags } from '@vue/shared'
import { warn } from '../warning'
/**
@@ -39,7 +39,7 @@ export function renderSlot(
Fragment,
{ key: props.key },
slot ? slot(props) : fallback ? fallback() : [],
- (slots as RawSlots)._ === CompiledSlotTypes.STATIC
+ (slots as RawSlots)._ === SlotFlags.STABLE
? PatchFlags.STABLE_FRAGMENT
: PatchFlags.BAIL
)
diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts
index aa87d440a04..a96519cb94c 100644
--- a/packages/runtime-core/src/vnode.ts
+++ b/packages/runtime-core/src/vnode.ts
@@ -8,7 +8,8 @@ import {
normalizeClass,
normalizeStyle,
PatchFlags,
- ShapeFlags
+ ShapeFlags,
+ SlotFlags
} from '@vue/shared'
import {
ComponentInternalInstance,
@@ -542,10 +543,22 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
return
} else {
type = ShapeFlags.SLOTS_CHILDREN
- if (!(children as RawSlots)._ && !(InternalObjectKey in children!)) {
+ const slotFlag = (children as RawSlots)._
+ if (!slotFlag && !(InternalObjectKey in children!)) {
// if slots are not normalized, attach context instance
// (compiled / normalized slots already have context)
;(children as RawSlots)._ctx = currentRenderingInstance
+ } else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
+ // a child component receives forwarded slots from the parent.
+ // its slot type is determined by its parent's slot type.
+ if (
+ currentRenderingInstance.vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS
+ ) {
+ ;(children as RawSlots)._ = SlotFlags.DYNAMIC
+ vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
+ } else {
+ ;(children as RawSlots)._ = SlotFlags.STABLE
+ }
}
}
} else if (isFunction(children)) {
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 7caf7d78679..d886f074347 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -3,6 +3,7 @@ import { makeMap } from './makeMap'
export { makeMap }
export * from './patchFlags'
export * from './shapeFlags'
+export * from './slotFlags'
export * from './globalsWhitelist'
export * from './codeframe'
export * from './mockWarn'
diff --git a/packages/shared/src/slotFlags.ts b/packages/shared/src/slotFlags.ts
new file mode 100644
index 00000000000..d111b5e6574
--- /dev/null
+++ b/packages/shared/src/slotFlags.ts
@@ -0,0 +1,21 @@
+export const enum SlotFlags {
+ /**
+ * Stable slots that only reference slot props or context state. The slot
+ * can fully capture its own dependencies so when passed down the parent won't
+ * need to force the child to update.
+ */
+ STABLE = 1,
+ /**
+ * Slots that reference scope variables (v-for or an outer slot prop), or
+ * has conditional structure (v-if, v-for). The parent will need to force
+ * the child to update because the slot does not fully capture its dependencies.
+ */
+ DYNAMIC = 2,
+ /**
+ * being forwarded into a child component. Whether the parent needs
+ * to update the child is dependent on what kind of slots the parent itself
+ * received. This has to be refined at runtime, when the child's vnode
+ * is being created (in `normalizeChildren`)
+ */
+ FORWARDED = 3
+}