Skip to content

Commit 5b82c48

Browse files
authored
fix(runtime-core): enable block tracking when normalizing plain element with slot children (#1987)
fix #1980
1 parent 706b52a commit 5b82c48

File tree

5 files changed

+71
-12
lines changed

5 files changed

+71
-12
lines changed

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts

+53-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import {
1111
serializeInner as inner,
1212
VNode,
1313
ref,
14-
nextTick
14+
nextTick,
15+
defineComponent,
16+
withCtx,
17+
renderSlot
1518
} from '@vue/runtime-test'
16-
import { PatchFlags } from '@vue/shared'
19+
import { PatchFlags, SlotFlags } from '@vue/shared'
1720

1821
describe('renderer: optimized mode', () => {
1922
let root: TestElement
@@ -398,4 +401,52 @@ describe('renderer: optimized mode', () => {
398401
expect(inner(root)).toBe('<div><i>bar</i></div>')
399402
expect(block!.dynamicChildren).toBe(null)
400403
})
404+
405+
// #1980
406+
test('dynamicChildren should be tracked correctly when normalizing slots to plain children', async () => {
407+
let block: VNode
408+
const Comp = defineComponent({
409+
setup(_props, { slots }) {
410+
return () => {
411+
const vnode = (openBlock(),
412+
(block = createBlock('div', null, {
413+
default: withCtx(() => [renderSlot(slots, 'default')]),
414+
_: SlotFlags.FORWARDED
415+
})))
416+
417+
return vnode
418+
}
419+
}
420+
})
421+
422+
const foo = ref(0)
423+
const App = {
424+
setup() {
425+
return () => {
426+
return createVNode(Comp, null, {
427+
default: withCtx(() => [
428+
createVNode('p', null, foo.value, PatchFlags.TEXT)
429+
]),
430+
// Indicates that this is a stable slot to avoid bail out
431+
_: SlotFlags.STABLE
432+
})
433+
}
434+
}
435+
}
436+
437+
render(h(App), root)
438+
expect(inner(root)).toBe('<div><p>0</p></div>')
439+
expect(block!.dynamicChildren!.length).toBe(1)
440+
expect(block!.dynamicChildren![0].type).toBe(Fragment)
441+
expect(block!.dynamicChildren![0].dynamicChildren!.length).toBe(1)
442+
expect(
443+
serialize(block!.dynamicChildren![0].dynamicChildren![0]
444+
.el as TestElement)
445+
).toBe('<p>0</p>')
446+
447+
foo.value++
448+
await nextTick()
449+
450+
expect(inner(root)).toBe('<div><p>1</p></div>')
451+
})
401452
})

packages/runtime-core/__tests__/vnode.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,10 @@ describe('vnode', () => {
130130
})
131131

132132
test('object', () => {
133-
const vnode = createVNode('p', null, { foo: 'foo' })
133+
const vnode = createVNode({}, null, { foo: 'foo' })
134134
expect(vnode.children).toMatchObject({ foo: 'foo' })
135135
expect(vnode.shapeFlag).toBe(
136-
ShapeFlags.ELEMENT | ShapeFlags.SLOTS_CHILDREN
136+
ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.SLOTS_CHILDREN
137137
)
138138
})
139139

packages/runtime-core/src/helpers/renderSlot.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { PatchFlags, SlotFlags } from '@vue/shared'
1111
import { warn } from '../warning'
1212

1313
export let isRenderingCompiledSlot = 0
14+
export const setCompiledSlotRendering = (n: number) =>
15+
(isRenderingCompiledSlot += n)
1416

1517
/**
1618
* Compiler runtime helper for rendering `<slot/>`

packages/runtime-core/src/helpers/withRenderContext.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function withCtx(
1616
ctx: ComponentInternalInstance | null = currentRenderingInstance
1717
) {
1818
if (!ctx) return fn
19-
return function renderFnWithContext() {
19+
const renderFnWithContext = (...args: any[]) => {
2020
// If a user calls a compiled slot inside a template expression (#1745), it
2121
// can mess up block tracking, so by default we need to push a null block to
2222
// avoid that. This isn't necessary if rendering a compiled `<slot>`.
@@ -25,11 +25,13 @@ export function withCtx(
2525
}
2626
const owner = currentRenderingInstance
2727
setCurrentRenderingInstance(ctx)
28-
const res = fn.apply(null, arguments as any)
28+
const res = fn(...args)
2929
setCurrentRenderingInstance(owner)
3030
if (!isRenderingCompiledSlot) {
3131
closeBlock()
3232
}
3333
return res
3434
}
35+
renderFnWithContext._c = true
36+
return renderFnWithContext
3537
}

packages/runtime-core/src/vnode.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { currentRenderingInstance } from './componentRenderUtils'
3636
import { RendererNode, RendererElement } from './renderer'
3737
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
3838
import { hmrDirtyComponents } from './hmr'
39+
import { setCompiledSlotRendering } from './helpers/renderSlot'
3940

4041
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
4142
__isFragment: true
@@ -539,12 +540,15 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
539540
} else if (isArray(children)) {
540541
type = ShapeFlags.ARRAY_CHILDREN
541542
} else if (typeof children === 'object') {
542-
// Normalize slot to plain children
543-
if (
544-
(shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) &&
545-
(children as any).default
546-
) {
547-
normalizeChildren(vnode, (children as any).default())
543+
if (shapeFlag & ShapeFlags.ELEMENT || shapeFlag & ShapeFlags.TELEPORT) {
544+
// Normalize slot to plain children for plain element and Teleport
545+
const slot = (children as any).default
546+
if (slot) {
547+
// _c marker is added by withCtx() indicating this is a compiled slot
548+
slot._c && setCompiledSlotRendering(1)
549+
normalizeChildren(vnode, slot())
550+
slot._c && setCompiledSlotRendering(-1)
551+
}
548552
return
549553
} else {
550554
type = ShapeFlags.SLOTS_CHILDREN

0 commit comments

Comments
 (0)