diff --git a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
index 26fee92f..73e566c9 100644
--- a/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
+++ b/packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
@@ -11,9 +11,12 @@ import {
serializeInner as inner,
VNode,
ref,
- nextTick
+ nextTick,
+ defineComponent,
+ withCtx,
+ renderSlot
} from '@vue/runtime-test'
-import { PatchFlags } from '@vue/shared'
+import { PatchFlags, SlotFlags } from '@vue/shared'
describe('renderer: optimized mode', () => {
let root: TestElement
@@ -398,4 +401,52 @@ describe('renderer: optimized mode', () => {
expect(inner(root)).toBe('
bar
')
expect(block!.dynamicChildren).toBe(null)
})
+
+ // #1980
+ test('dynamicChildren should be tracked correctly when normalizing slots to plain children', async () => {
+ let block: VNode
+ const Comp = defineComponent({
+ setup(_props, { slots }) {
+ return () => {
+ const vnode = (openBlock(),
+ (block = createBlock('div', null, {
+ default: withCtx(() => [renderSlot(slots, 'default')]),
+ _: SlotFlags.FORWARDED
+ })))
+
+ return vnode
+ }
+ }
+ })
+
+ const foo = ref(0)
+ const App = {
+ setup() {
+ return () => {
+ return createVNode(Comp, null, {
+ default: withCtx(() => [
+ createVNode('p', null, foo.value, PatchFlags.TEXT)
+ ]),
+ // Indicates that this is a stable slot to avoid bail out
+ _: SlotFlags.STABLE
+ })
+ }
+ }
+ }
+
+ render(h(App), root)
+ expect(inner(root)).toBe('')
+ expect(block!.dynamicChildren!.length).toBe(1)
+ expect(block!.dynamicChildren![0].type).toBe(Fragment)
+ expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
+ expect(
+ serialize(block!.dynamicChildren![0].dynamicChildren![0]
+ .el as TestElement)
+ ).toBe('0
')
+
+ foo.value++
+ await nextTick()
+
+ expect(inner(root)).toBe('')
+ })
})
diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts
index ad357561..2ee1872a 100644
--- a/packages/runtime-core/__tests__/vnode.spec.ts
+++ b/packages/runtime-core/__tests__/vnode.spec.ts
@@ -130,10 +130,10 @@ describe('vnode', () => {
})
test('object', () => {
- const vnode = createVNode('p', null, { foo: 'foo' })
+ const vnode = createVNode({}, null, { foo: 'foo' })
expect(vnode.children).toMatchObject({ foo: 'foo' })
expect(vnode.shapeFlag).toBe(
- ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN
+ ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.SLOTS_CHILDREN
)
})
diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts
index be6dbba4..2e43c628 100644
--- a/packages/runtime-core/src/helpers/renderSlot.ts
+++ b/packages/runtime-core/src/helpers/renderSlot.ts
@@ -11,6 +11,8 @@ import { PatchFlags, SlotFlags } from '@vue/shared'
import { warn } from '../warning'
export let isRenderingCompiledSlot = 0
+export const setCompiledSlotRendering = (n: number) =>
+ (isRenderingCompiledSlot += n)
/**
* Compiler runtime helper for rendering ``
diff --git a/packages/runtime-core/src/helpers/withRenderContext.ts b/packages/runtime-core/src/helpers/withRenderContext.ts
index 4ac273f5..88a29ae3 100644
--- a/packages/runtime-core/src/helpers/withRenderContext.ts
+++ b/packages/runtime-core/src/helpers/withRenderContext.ts
@@ -16,7 +16,7 @@ export function withCtx(
ctx: ComponentInternalInstance | null = currentRenderingInstance
) {
if (!ctx) return fn
- return function renderFnWithContext() {
+ const renderFnWithContext = (...args: any[]) => {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we need to push a null block to
// avoid that. This isn't necessary if rendering a compiled ``.
@@ -25,11 +25,13 @@ export function withCtx(
}
const owner = currentRenderingInstance
setCurrentRenderingInstance(ctx)
- const res = fn.apply(null, arguments as any)
+ const res = fn(...args)
setCurrentRenderingInstance(owner)
if (!isRenderingCompiledSlot) {
closeBlock()
}
return res
}
+ renderFnWithContext._c = true
+ return renderFnWithContext
}
diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts
index 0f20a534..de329ff9 100644
--- a/packages/runtime-core/src/vnode.ts
+++ b/packages/runtime-core/src/vnode.ts
@@ -36,6 +36,7 @@ import { currentRenderingInstance } from './componentRenderUtils'
import { RendererNode, RendererElement } from './renderer'
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
import { hmrDirtyComponents } from './hmr'
+import { setCompiledSlotRendering } from './helpers/renderSlot'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true
@@ -539,12 +540,15 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
} else if (isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN
} else if (typeof children === 'object') {
- // Normalize slot to plain children
- if (
- (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) &&
- (children as any).default
- ) {
- normalizeChildren(vnode, (children as any).default())
+ if (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) {
+ // Normalize slot to plain children for plain element and Teleport
+ const slot = (children as any).default
+ if (slot) {
+ // _c marker is added by withCtx() indicating this is a compiled slot
+ slot._c && setCompiledSlotRendering(1)
+ normalizeChildren(vnode, slot())
+ slot._c && setCompiledSlotRendering(-1)
+ }
return
} else {
type = ShapeFlags.SLOTS_CHILDREN