From 10ce5e2928cd804f1bd01a7cfd1bb276dad32c5e Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Mon, 26 Oct 2020 14:02:06 +0800 Subject: [PATCH 1/3] fix(runtime-core): only render valid slot content --- .../__tests__/helpers/renderSlot.spec.ts | 30 +++++++++++++++- .../runtime-core/src/helpers/renderSlot.ts | 35 +++++++++++++++++-- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts b/packages/runtime-core/__tests__/helpers/renderSlot.spec.ts index e14f4b75025..99f2292b771 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 2e43c628678..86b8021eeba 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, @@ -37,6 +38,8 @@ export function renderSlot( slot = () => [] } + let validSlotContent: VNodeArrayChildren | null = null + // a compiled slot disables block tracking by default to avoid manual // invocation interfering with template-based block tracking, but in // `renderSlot` we can be sure that it's template-based so we can force @@ -46,11 +49,37 @@ export function renderSlot( createBlock( Fragment, { key: props.key }, - slot ? slot(props) : fallback ? fallback() : [], - (slots as RawSlots)._ === SlotFlags.STABLE - ? PatchFlags.STABLE_FRAGMENT + slot + ? (validSlotContent = filterValidVNode(slot(props))) + ? validSlotContent + : fallback + ? fallback() + : [] + : fallback + ? fallback() + : [], + + validSlotContent + ? (slots as RawSlots)._ === SlotFlags.STABLE + ? PatchFlags.STABLE_FRAGMENT + : PatchFlags.BAIL : PatchFlags.BAIL )) + isRenderingCompiledSlot-- return rendered } + +function filterValidVNode(vnodes: VNodeArrayChildren) { + const filtered = vnodes.filter(child => { + if (!isVNode(child)) return true + if (child.type === Comment) return false + if ( + child.type === Fragment && + !filterValidVNode(child.children as VNodeArrayChildren) + ) + return false + return true + }) + return filtered.length ? vnodes : null +} From 020e21b1298d29009dfdf3ce93bd702b96dd95a8 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 26 Nov 2020 11:23:17 -0500 Subject: [PATCH 2/3] refactor: simplify conditions --- .../runtime-core/src/helpers/renderSlot.ts | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 86b8021eeba..8b0f0bdd3af 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -38,34 +38,21 @@ export function renderSlot( slot = () => [] } - let validSlotContent: VNodeArrayChildren | null = null - // a compiled slot disables block tracking by default to avoid manual // invocation interfering with template-based block tracking, but in // `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 && filterValidVNode(slot(props)) + const rendered = createBlock( Fragment, { key: props.key }, - slot - ? (validSlotContent = filterValidVNode(slot(props))) - ? validSlotContent - : fallback - ? fallback() - : [] - : fallback - ? fallback() - : [], - - validSlotContent - ? (slots as RawSlots)._ === SlotFlags.STABLE - ? PatchFlags.STABLE_FRAGMENT - : PatchFlags.BAIL + validSlotContent || (fallback ? fallback() : []), + validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE + ? PatchFlags.STABLE_FRAGMENT : PatchFlags.BAIL - )) - + ) isRenderingCompiledSlot-- return rendered } From 0e870493e64c497894af9c8f611cf621997a60ba Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 26 Nov 2020 11:30:52 -0500 Subject: [PATCH 3/3] perf: use some instead of filter --- packages/runtime-core/src/helpers/renderSlot.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 8b0f0bdd3af..c20b8a191dc 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -44,7 +44,7 @@ export function renderSlot( // enable it. isRenderingCompiledSlot++ openBlock() - const validSlotContent = slot && filterValidVNode(slot(props)) + const validSlotContent = slot && ensureValidVNode(slot(props)) const rendered = createBlock( Fragment, { key: props.key }, @@ -57,16 +57,17 @@ export function renderSlot( return rendered } -function filterValidVNode(vnodes: VNodeArrayChildren) { - const filtered = vnodes.filter(child => { +function ensureValidVNode(vnodes: VNodeArrayChildren) { + return vnodes.some(child => { if (!isVNode(child)) return true if (child.type === Comment) return false if ( child.type === Fragment && - !filterValidVNode(child.children as VNodeArrayChildren) + !ensureValidVNode(child.children as VNodeArrayChildren) ) return false return true }) - return filtered.length ? vnodes : null + ? vnodes + : null }