From ce4915d8bed12f4cdb5fa8ca39bda98d0d3aabb7 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Fri, 27 Nov 2020 00:35:45 +0800 Subject: [PATCH] fix(slots): should render fallback content when slot content contains no valid nodes (#2485) fix #2347, fix #2461 --- .../__tests__/helpers/renderSlot.spec.ts | 30 ++++++++++++++++++- .../runtime-core/src/helpers/renderSlot.ts | 27 +++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts b/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts index e14f4b75..99f2292b 100644 --- a/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts +++ b/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts @@ -5,7 +5,8 @@ import { createVNode, openBlock, createBlock, - Fragment + Fragment, + createCommentVNode } from '../../src' import { PatchFlags } from '@vue/shared/src' @@ -47,4 +48,31 @@ describe('renderSlot', () => { const templateRendered = renderSlot({ default: slot }, 'default') expect(templateRendered.dynamicChildren!.length).toBe(1) }) + + // #2347 #2461 + describe('only render valid slot content', () => { + it('should ignore slots that are all comments', () => { + let fallback + const vnode = renderSlot( + { default: () => [createCommentVNode('foo')] }, + 'default', + undefined, + () => [(fallback = h('fallback'))] + ) + expect(vnode.children).toEqual([fallback]) + expect(vnode.patchFlag).toBe(PatchFlags.BAIL) + }) + + it('should ignore invalid slot content generated by nested slot', () => { + let fallback + const vnode = renderSlot( + { default: () => [renderSlot({}, 'foo')] }, + 'default', + undefined, + () => [(fallback = h('fallback'))] + ) + expect(vnode.children).toEqual([fallback]) + expect(vnode.patchFlag).toBe(PatchFlags.BAIL) + }) + }) }) diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 2e43c628..c20b8a19 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -1,5 +1,6 @@ import { Data } from '../component' import { Slots, RawSlots } from '../componentSlots' +import { Comment, isVNode } from '../vnode' import { VNodeArrayChildren, openBlock, @@ -42,15 +43,31 @@ export function renderSlot( // `renderSlot` we can be sure that it's template-based so we can force // enable it. isRenderingCompiledSlot++ - const rendered = (openBlock(), - createBlock( + openBlock() + const validSlotContent = slot && ensureValidVNode(slot(props)) + const rendered = createBlock( Fragment, { key: props.key }, - slot ? slot(props) : fallback ? fallback() : [], - (slots as RawSlots)._ === SlotFlags.STABLE + validSlotContent || (fallback ? fallback() : []), + validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE ? PatchFlags.STABLE_FRAGMENT : PatchFlags.BAIL - )) + ) isRenderingCompiledSlot-- return rendered } + +function ensureValidVNode(vnodes: VNodeArrayChildren) { + return vnodes.some(child => { + if (!isVNode(child)) return true + if (child.type === Comment) return false + if ( + child.type === Fragment && + !ensureValidVNode(child.children as VNodeArrayChildren) + ) + return false + return true + }) + ? vnodes + : null +}