From dde70761200653e86c27e5cb0e41d54eda99bd02 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 29 May 2025 17:04:49 +0800 Subject: [PATCH 1/4] feat(compiler-vapor): add support for forwarded slots --- packages/compiler-vapor/src/generate.ts | 7 +++++ .../src/generators/slotOutlet.ts | 6 +++-- packages/compiler-vapor/src/ir/index.ts | 2 ++ packages/compiler-vapor/src/transform.ts | 1 + .../src/transforms/transformSlotOutlet.ts | 27 +++++++++++++++++++ packages/runtime-vapor/src/componentSlots.ts | 16 ++++++++++- packages/runtime-vapor/src/index.ts | 2 +- 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 193a0f5da..ff3806611 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -18,6 +18,7 @@ import { genCall, } from './generators/utils' import { setTemplateRefIdent } from './generators/templateRef' +import { createForwardedSlotIdent } from './generators/slotOutlet' export type CodegenOptions = Omit @@ -129,6 +130,12 @@ export function generate( `const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`, ) } + if (ir.hasForwardedSlot) { + push( + NEWLINE, + `const ${createForwardedSlotIdent} = ${context.helper('forwardedSlotCreator')}()`, + ) + } push(...genBlockContent(ir.block, context, true)) push(INDENT_END, NEWLINE) diff --git a/packages/compiler-vapor/src/generators/slotOutlet.ts b/packages/compiler-vapor/src/generators/slotOutlet.ts index 3221cbbd2..dc992ae23 100644 --- a/packages/compiler-vapor/src/generators/slotOutlet.ts +++ b/packages/compiler-vapor/src/generators/slotOutlet.ts @@ -5,12 +5,14 @@ import { genExpression } from './expression' import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils' import { genRawProps } from './component' +export const createForwardedSlotIdent = `_createForwardedSlot` + export function genSlotOutlet( oper: SlotOutletIRNode, context: CodegenContext, ): CodeFragment[] { const { helper } = context - const { id, name, fallback } = oper + const { id, name, fallback, forwarded } = oper const [frag, push] = buildCodeFragment() const nameExpr = name.isStatic @@ -26,7 +28,7 @@ export function genSlotOutlet( NEWLINE, `const n${id} = `, ...genCall( - helper('createSlot'), + forwarded ? createForwardedSlotIdent : helper('createSlot'), nameExpr, genRawProps(oper.props, context) || 'null', fallbackArg, diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index da6361132..086f77ca6 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -66,6 +66,7 @@ export interface RootIRNode { directive: Set block: BlockIRNode hasTemplateRef: boolean + hasForwardedSlot: boolean } export interface IfIRNode extends BaseIRNode { @@ -209,6 +210,7 @@ export interface SlotOutletIRNode extends BaseIRNode { name: SimpleExpressionNode props: IRProps[] fallback?: BlockIRNode + forwarded?: boolean parent?: number anchor?: number } diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 76563899d..93488ae95 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -230,6 +230,7 @@ export function transform( directive: new Set(), block: newBlock(node), hasTemplateRef: false, + hasForwardedSlot: false, } const context = new TransformContext(ir, node, options) diff --git a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts index 83b4aa2d2..159d70c38 100644 --- a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts +++ b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts @@ -5,6 +5,7 @@ import { ErrorCodes, NodeTypes, type SimpleExpressionNode, + type TemplateChildNode, createCompilerError, createSimpleExpression, isStaticArgOf, @@ -99,6 +100,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { } return () => { + let forwarded = false + const slotNode = context.block.node + if (slotNode.type === NodeTypes.ELEMENT) { + forwarded = hasForwardedSlots(slotNode.children) + } + if (forwarded) context.ir.hasForwardedSlot = true + exitBlock && exitBlock() context.dynamic.operation = { type: IRNodeTypes.SLOT_OUTLET_NODE, @@ -106,6 +114,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { name: slotName, props: irProps, fallback, + forwarded, } } } @@ -131,3 +140,21 @@ function createFallback( context.reference() return [fallback, exitBlock] } + +// TODO +function hasForwardedSlots(children: TemplateChildNode[]): boolean { + for (let i = 0; i < children.length; i++) { + const child = children[i] + switch (child.type) { + case NodeTypes.ELEMENT: + if ( + child.tagType === ElementTypes.SLOT || + hasForwardedSlots(child.children) + ) { + return true + } + break + } + } + return false +} diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 74296e094..00ae4ea29 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -87,10 +87,24 @@ export function getSlot( } } +export function forwardedSlotCreator(): ( + name: string | (() => string), + rawProps?: LooseRawProps | null, + fallback?: VaporSlot, +) => Block { + const instance = currentInstance as VaporComponentInstance + return ( + name: string | (() => string), + rawProps?: LooseRawProps | null, + fallback?: VaporSlot, + ) => createSlot(name, rawProps, fallback, instance) +} + export function createSlot( name: string | (() => string), rawProps?: LooseRawProps | null, fallback?: VaporSlot, + i?: VaporComponentInstance, ): Block { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor @@ -98,7 +112,7 @@ export function createSlot( locateHydrationNode() } - const instance = currentInstance as VaporComponentInstance + const instance = i || (currentInstance as VaporComponentInstance) const rawSlots = instance.rawSlots const slotProps = rawProps ? new Proxy(rawProps, rawPropsProxyHandlers) diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index 682532fa4..10d0aa633 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -9,7 +9,7 @@ export { insert, prepend, remove, isFragment, VaporFragment } from './block' export { setInsertionState } from './insertionState' export { createComponent, createComponentWithFallback } from './component' export { renderEffect } from './renderEffect' -export { createSlot } from './componentSlots' +export { createSlot, forwardedSlotCreator } from './componentSlots' export { template } from './dom/template' export { createTextNode, child, nthChild, next } from './dom/node' export { From a952b0335897e18f4bf2dc68c22bfeb8a8993d1f Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 29 May 2025 21:45:00 +0800 Subject: [PATCH 2/4] test: add tests --- .../__snapshots__/vSlot.spec.ts.snap | 91 +++++++++++++++++++ .../__tests__/transforms/vSlot.spec.ts | 29 ++++++ packages/compiler-vapor/src/transform.ts | 1 + .../src/transforms/transformSlotOutlet.ts | 14 +-- .../compiler-vapor/src/transforms/vSlot.ts | 9 +- .../__tests__/componentSlots.spec.ts | 54 ++++++++++- 6 files changed, 190 insertions(+), 8 deletions(-) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 4ecd8c76a..d1d80d4d6 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -103,6 +103,97 @@ export function render(_ctx) { }" `; +exports[`compiler: transform slot > forwarded slots > 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n1 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createForwardedSlot("default", null) + return n0 + } + }) + return n1 + } + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag only 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n1 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createForwardedSlot("default", null) + return n0 + } + }, true) + return n1 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag w/ template 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n2 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createForwardedSlot("default", null) + return n0 + } + }, true) + return n2 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag w/ v-for 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createFor as _createFor, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n3 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createFor(() => (_ctx.b), (_for_item0) => { + const n2 = _createForwardedSlot("default", null) + return n2 + }) + return n0 + } + }, true) + return n3 +}" +`; + +exports[`compiler: transform slot > forwarded slots > tag w/ v-if 1`] = ` +"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createIf as _createIf, createComponentWithFallback as _createComponentWithFallback } from 'vue'; + +export function render(_ctx) { + const _createForwardedSlot = _forwardedSlotCreator() + const _component_Comp = _resolveComponent("Comp") + const n3 = _createComponentWithFallback(_component_Comp, null, { + "default": () => { + const n0 = _createIf(() => (_ctx.ok), () => { + const n2 = _createForwardedSlot("default", null) + return n2 + }) + return n0 + } + }, true) + return n3 +}" +`; + exports[`compiler: transform slot > implicit default slot 1`] = ` "import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue'; const t0 = _template("
") diff --git a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts index 84ddb2e5d..a7da3d542 100644 --- a/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts @@ -409,6 +409,35 @@ describe('compiler: transform slot', () => { }) }) + describe('forwarded slots', () => { + test(' tag only', () => { + const { code } = compileWithSlots(``) + expect(code).toMatchSnapshot() + }) + + test(' tag w/ v-if', () => { + const { code } = compileWithSlots(``) + expect(code).toMatchSnapshot() + }) + + test(' tag w/ v-for', () => { + const { code } = compileWithSlots(``) + expect(code).toMatchSnapshot() + }) + + test(' tag w/ template', () => { + const { code } = compileWithSlots( + ``, + ) + expect(code).toMatchSnapshot() + }) + + test('', () => { + const { code } = compileWithSlots(``) + expect(code).toMatchSnapshot() + }) + }) + describe('errors', () => { test('error on extraneous children w/ named default slot', () => { const onError = vi.fn() diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 93488ae95..6d07ebcaf 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -76,6 +76,7 @@ export class TransformContext { inVOnce: boolean = false inVFor: number = 0 + inSlot: number = 0 comment: CommentNode[] = [] component: Set = this.ir.component diff --git a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts index 159d70c38..a281c90a7 100644 --- a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts +++ b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts @@ -100,11 +100,14 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { } return () => { - let forwarded = false - const slotNode = context.block.node - if (slotNode.type === NodeTypes.ELEMENT) { - forwarded = hasForwardedSlots(slotNode.children) - } + const { + block: { node: slotNode }, + inSlot, + } = context + const forwarded = + inSlot !== 0 && + slotNode.type === NodeTypes.ELEMENT && + hasForwardedSlots(slotNode.children) if (forwarded) context.ir.hasForwardedSlot = true exitBlock && exitBlock() @@ -141,7 +144,6 @@ function createFallback( return [fallback, exitBlock] } -// TODO function hasForwardedSlots(children: TemplateChildNode[]): boolean { for (let i = 0; i < children.length; i++) { const child = children[i] diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts index d1bf1c6b0..2e767cb41 100644 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -237,7 +237,14 @@ function createSlotBlock( const block: SlotBlockIRNode = newBlock(slotNode) block.props = dir && dir.exp const exitBlock = context.enterBlock(block) - return [block, exitBlock] + context.inSlot++ + return [ + block, + () => { + context.inSlot-- + exitBlock() + }, + ] } function isNonWhitespaceContent(node: TemplateChildNode): boolean { diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 58076fff9..46bfc3d93 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -7,6 +7,7 @@ import { createSlot, createVaporApp, defineVaporComponent, + forwardedSlotCreator, insert, prepend, renderEffect, @@ -15,7 +16,7 @@ import { import { currentInstance, nextTick, ref } from '@vue/runtime-dom' import { makeRender } from './_utils' import type { DynamicSlot } from '../src/componentSlots' -import { setElementText } from '../src/dom/prop' +import { setElementText, setText } from '../src/dom/prop' const define = makeRender() @@ -503,4 +504,55 @@ describe('component: slots', () => { expect(host.innerHTML).toBe('

') }) }) + + describe('forwarded slot', () => { + test('should work', async () => { + const Child = defineVaporComponent({ + setup() { + return createSlot('foo', null) + }, + }) + const Parent = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + Child, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const foo = ref('foo') + const { host } = define({ + setup() { + const n2 = createComponent( + Parent, + null, + { + foo: () => { + const n0 = template(' ')() as any + renderEffect(() => setText(n0, foo.value)) + return n0 + }, + }, + true, + ) + return n2 + }, + }).render() + + expect(host.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(host.innerHTML).toBe('bar') + }) + }) }) From c23d63582e3ddc569b84102b22757549b97ea8f0 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 30 May 2025 09:17:04 +0800 Subject: [PATCH 3/4] chore: update --- packages/compiler-vapor/src/transform.ts | 2 +- .../src/transforms/transformSlotOutlet.ts | 31 +---------- .../compiler-vapor/src/transforms/vSlot.ts | 4 +- .../__tests__/componentSlots.spec.ts | 51 +++++++++++++++++++ packages/runtime-vapor/src/componentSlots.ts | 7 +-- 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 6d07ebcaf..763e9612c 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -76,7 +76,7 @@ export class TransformContext { inVOnce: boolean = false inVFor: number = 0 - inSlot: number = 0 + inSlot: boolean = false comment: CommentNode[] = [] component: Set = this.ir.component diff --git a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts index a281c90a7..dc2b620dd 100644 --- a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts +++ b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts @@ -5,7 +5,6 @@ import { ErrorCodes, NodeTypes, type SimpleExpressionNode, - type TemplateChildNode, createCompilerError, createSimpleExpression, isStaticArgOf, @@ -100,16 +99,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { } return () => { - const { - block: { node: slotNode }, - inSlot, - } = context - const forwarded = - inSlot !== 0 && - slotNode.type === NodeTypes.ELEMENT && - hasForwardedSlots(slotNode.children) - if (forwarded) context.ir.hasForwardedSlot = true - + if (context.inSlot) context.ir.hasForwardedSlot = true exitBlock && exitBlock() context.dynamic.operation = { type: IRNodeTypes.SLOT_OUTLET_NODE, @@ -117,7 +107,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { name: slotName, props: irProps, fallback, - forwarded, + forwarded: context.inSlot, } } } @@ -143,20 +133,3 @@ function createFallback( context.reference() return [fallback, exitBlock] } - -function hasForwardedSlots(children: TemplateChildNode[]): boolean { - for (let i = 0; i < children.length; i++) { - const child = children[i] - switch (child.type) { - case NodeTypes.ELEMENT: - if ( - child.tagType === ElementTypes.SLOT || - hasForwardedSlots(child.children) - ) { - return true - } - break - } - } - return false -} diff --git a/packages/compiler-vapor/src/transforms/vSlot.ts b/packages/compiler-vapor/src/transforms/vSlot.ts index 2e767cb41..525fa323d 100644 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@ -237,11 +237,11 @@ function createSlotBlock( const block: SlotBlockIRNode = newBlock(slotNode) block.props = dir && dir.exp const exitBlock = context.enterBlock(block) - context.inSlot++ + context.inSlot = true return [ block, () => { - context.inSlot-- + context.inSlot = false exitBlock() }, ] diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 46bfc3d93..bdbd96036 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -554,5 +554,56 @@ describe('component: slots', () => { await nextTick() expect(host.innerHTML).toBe('bar') }) + + test('mixed with non-forwarded slot', async () => { + const Child = defineVaporComponent({ + setup() { + return [createSlot('foo', null)] + }, + }) + const Parent = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent(Child, null, { + foo: () => { + const n0 = createForwardedSlot('foo', null) + return n0 + }, + }) + const n3 = createSlot('default', null) + return [n2, n3] + }, + }) + + const foo = ref('foo') + const { host } = define({ + setup() { + const n2 = createComponent( + Parent, + null, + { + foo: () => { + const n0 = template(' ')() as any + renderEffect(() => setText(n0, foo.value)) + return n0 + }, + default: () => { + const n3 = template(' ')() as any + renderEffect(() => setText(n3, foo.value)) + return n3 + }, + }, + true, + ) + return n2 + }, + }).render() + + expect(host.innerHTML).toBe('foofoo') + + foo.value = 'bar' + await nextTick() + expect(host.innerHTML).toBe('barbar') + }) }) }) diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 00ae4ea29..19e9b5b6d 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -93,11 +93,8 @@ export function forwardedSlotCreator(): ( fallback?: VaporSlot, ) => Block { const instance = currentInstance as VaporComponentInstance - return ( - name: string | (() => string), - rawProps?: LooseRawProps | null, - fallback?: VaporSlot, - ) => createSlot(name, rawProps, fallback, instance) + return (name, rawProps, fallback) => + createSlot(name, rawProps, fallback, instance) } export function createSlot( From dcf927ff8c1a7662771b30b7f76cbb63afa8ebe7 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 30 May 2025 16:27:02 +0800 Subject: [PATCH 4/4] fix(vdomInterop): handle forwarded vapor slots during render VDOM slot --- packages/runtime-vapor/src/componentProps.ts | 3 ++- packages/runtime-vapor/src/vdomInterop.ts | 28 ++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index a5e9daad2..7a0e9ed92 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -210,7 +210,8 @@ export function hasAttrFromRawProps(rawProps: RawProps, key: string): boolean { if (dynamicSources) { let i = dynamicSources.length while (i--) { - if (hasOwn(resolveSource(dynamicSources[i]), key)) { + const source = resolveSource(dynamicSources[i]) + if (source && hasOwn(source, key)) { return true } } diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 77228fd72..e7c7e02e0 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -26,7 +26,14 @@ import { mountComponent, unmountComponent, } from './component' -import { type Block, VaporFragment, insert, remove } from './block' +import { + type Block, + VaporFragment, + insert, + isFragment, + isValidBlock, + remove, +} from './block' import { EMPTY_OBJ, extend, isFunction } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' @@ -230,7 +237,24 @@ function renderVDOMSlot( isFunction(name) ? name() : name, props, ) - if ((vnode.children as any[]).length) { + let isValidSlotContent + let children = vnode.children as any[] + + // TODO add tests + // handle forwarded vapor slot + let vaporSlot + if (children.length === 1 && (vaporSlot = children[0].vs)) { + const block = vaporSlot.slot(props) + isValidSlotContent = + isValidBlock(block) || + // if block is a vapor fragment with insert, it indicates a forwarded VDOM slot + (isFragment(block) && block.insert) + } + // vnode children + else { + isValidSlotContent = children.length > 0 + } + if (isValidSlotContent) { if (fallbackNodes) { remove(fallbackNodes, parentNode) fallbackNodes = undefined