From dde70761200653e86c27e5cb0e41d54eda99bd02 Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 29 May 2025 17:04:49 +0800 Subject: [PATCH 01/10] 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 193a0f5da77..ff3806611ad 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 3221cbbd2c7..dc992ae2334 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 da636113224..086f77ca612 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 76563899d2b..93488ae95a1 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 83b4aa2d2e4..159d70c3814 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 74296e09466..00ae4ea29ac 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 682532fa4d8..10d0aa63384 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 02/10] 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 4ecd8c76a7e..d1d80d4d620 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 84ddb2e5d04..a7da3d542f7 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 93488ae95a1..6d07ebcaf52 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 159d70c3814..a281c90a70d 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 d1bf1c6b05f..2e767cb41cd 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 58076fff9ee..46bfc3d938d 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 03/10] 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 6d07ebcaf52..763e9612cdc 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 a281c90a70d..dc2b620ddb2 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 2e767cb41cd..525fa323d3a 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 46bfc3d938d..bdbd960363d 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 00ae4ea29ac..19e9b5b6d1a 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 04/10] 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 a5e9daad229..7a0e9ed9286 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 77228fd72a0..e7c7e02e0bd 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 From b5f6f01b4d76899491fe73e81e84423fa538b0bc Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 6 Jun 2025 15:36:36 +0800 Subject: [PATCH 05/10] wip: refactor --- .../runtime-vapor/__tests__/scopeId.spec.ts | 464 ++++++++++++++++++ .../src/apiCreateDynamicComponent.ts | 11 +- packages/runtime-vapor/src/block.ts | 35 ++ packages/runtime-vapor/src/component.ts | 24 +- packages/runtime-vapor/src/vdomInterop.ts | 6 + 5 files changed, 535 insertions(+), 5 deletions(-) create mode 100644 packages/runtime-vapor/__tests__/scopeId.spec.ts diff --git a/packages/runtime-vapor/__tests__/scopeId.spec.ts b/packages/runtime-vapor/__tests__/scopeId.spec.ts new file mode 100644 index 00000000000..657936352d3 --- /dev/null +++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts @@ -0,0 +1,464 @@ +import { createApp, h } from '@vue/runtime-dom' +import { + createComponent, + createDynamicComponent, + createSlot, + defineVaporComponent, + setInsertionState, + template, + vaporInteropPlugin, +} from '../src' +import { makeRender } from './_utils' + +const define = makeRender() + +describe('scopeId', () => { + test('should attach scopeId to child component', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const { html } = define({ + __scopeId: 'parent', + setup() { + return createComponent(Child) + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should attach scopeId to child component with insertion state', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const { html } = define({ + __scopeId: 'parent', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createComponent(Child) + return n1 + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should attach scopeId to nested child component', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const Parent = defineVaporComponent({ + __scopeId: 'parent', + setup() { + return createComponent(Child) + }, + }) + + const { html } = define({ + __scopeId: 'app', + setup() { + return createComponent(Parent) + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should not attach scopeId to nested multiple root components', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const Parent = defineVaporComponent({ + __scopeId: 'parent', + setup() { + const n0 = template('
')() + const n1 = createComponent(Child) + return [n0, n1] + }, + }) + + const { html } = define({ + __scopeId: 'app', + setup() { + return createComponent(Parent) + }, + }).render() + expect(html()).toBe(`
`) + }) + + test('should attach scopeId to nested child component with insertion state', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + return template('
', true)() + }, + }) + + const Parent = defineVaporComponent({ + __scopeId: 'parent', + setup() { + return createComponent(Child) + }, + }) + + const { html } = define({ + __scopeId: 'app', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createComponent(Parent) + return n1 + }, + }).render() + expect(html()).toBe( + `
`, + ) + }) + + test('should attach scopeId to dynamic component', () => { + const { html } = define({ + __scopeId: 'parent', + setup() { + return createDynamicComponent(() => 'button') + }, + }).render() + expect(html()).toBe(``) + }) + + test('should attach scopeId to dynamic component with insertion state', () => { + const { html } = define({ + __scopeId: 'parent', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createDynamicComponent(() => 'button') + return n1 + }, + }).render() + expect(html()).toBe( + `
`, + ) + }) + + test('should attach scopeId to nested dynamic component', () => { + const Comp = defineVaporComponent({ + __scopeId: 'child', + setup() { + return createDynamicComponent(() => 'button', null, null, true) + }, + }) + const { html } = define({ + __scopeId: 'parent', + setup() { + return createComponent(Comp, null, null, true) + }, + }).render() + expect(html()).toBe( + ``, + ) + }) + + test('should attach scopeId to nested dynamic component with insertion state', () => { + const Comp = defineVaporComponent({ + __scopeId: 'child', + setup() { + return createDynamicComponent(() => 'button', null, null, true) + }, + }) + const { html } = define({ + __scopeId: 'parent', + setup() { + const t0 = template('
', true) + const n1 = t0() as any + setInsertionState(n1) + createComponent(Comp, null, null, true) + return n1 + }, + }).render() + expect(html()).toBe( + `
`, + ) + }) + + test.todo('should attach scopeId to suspense content', async () => {}) + + // :slotted basic + test.todo('should work on slots', () => { + const Child = defineVaporComponent({ + __scopeId: 'child', + setup() { + const n1 = template('
', true)() as any + setInsertionState(n1) + createSlot('default', null) + return n1 + }, + }) + + const Child2 = defineVaporComponent({ + __scopeId: 'child2', + setup() { + return template('', true)() + }, + }) + + const { html } = define({ + __scopeId: 'parent', + setup() { + const n2 = createComponent( + Child, + null, + { + default: () => { + const n0 = template('
')() + const n1 = createComponent(Child2) + return [n0, n1] + }, + }, + true, + ) + return n2 + }, + }).render() + + expect(html()).toBe( + `
` + + `
` + + // component inside slot should have: + // - scopeId from template context + // - slotted scopeId from slot owner + // - its own scopeId + `` + + `` + + `
`, + ) + }) + + test.todo(':slotted on forwarded slots', async () => {}) +}) + +describe('vdom interop', () => { + test('vdom parent > vapor child', () => { + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return template('', true)() + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VdomChild) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vdom parent > vapor > vdom child', () => { + const InnerVdomChild = { + __scopeId: 'inner-vdom-child', + setup() { + return () => h('button') + }, + } + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(InnerVdomChild as any, null, null, true) + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VdomChild) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vdom parent > vapor dynamic child', () => { + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createDynamicComponent(() => 'button', null, null, true) + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(VaporChild as any) + }, + } + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VdomChild) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vdom child', () => { + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h('button') + }, + } + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(VdomChild as any, null, null, true) + }, + }) + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vdom > vapor child', () => { + const InnerVaporChild = defineVaporComponent({ + __scopeId: 'inner-vapor-child', + setup() { + return template('', true)() + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(InnerVaporChild as any) + }, + } + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(VdomChild as any, null, null, true) + }, + }) + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test.todo('vapor parent > vdom > vdom > vapor child', () => { + const InnerVaporChild = defineVaporComponent({ + __scopeId: 'inner-vapor-child', + setup() { + return template('', true)() + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(InnerVaporChild as any) + }, + } + + const VdomChild2 = { + __scopeId: 'vdom-child2', + setup() { + return () => h(VdomChild as any) + }, + } + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(VdomChild2 as any, null, null, true) + }, + }) + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VaporChild as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) +}) diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 2126611d718..33697b4ca76 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -1,9 +1,11 @@ import { resolveDynamicComponent } from '@vue/runtime-dom' -import { DynamicFragment, type VaporFragment } from './block' +import { DynamicFragment, type VaporFragment, insert } from './block' import { createComponentWithFallback } from './component' import { renderEffect } from './renderEffect' import type { RawProps } from './componentProps' import type { RawSlots } from './componentSlots' +import { isHydrating } from './dom/hydration' +import { insertionAnchor, insertionParent } from './insertionState' export function createDynamicComponent( getter: () => any, @@ -11,6 +13,9 @@ export function createDynamicComponent( rawSlots?: RawSlots | null, isSingleRoot?: boolean, ): VaporFragment { + const _insertionParent = insertionParent + const _insertionAnchor = insertionAnchor + const frag = __DEV__ ? new DynamicFragment('dynamic-component') : new DynamicFragment() @@ -27,5 +32,9 @@ export function createDynamicComponent( value, ) }) + + if (!isHydrating && _insertionParent) { + insert(frag, _insertionParent, _insertionAnchor) + } return frag } diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index b782afd38d3..c094642dfca 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -187,3 +187,38 @@ export function normalizeBlock(block: Block): Node[] { } return nodes } + +export function setScopeId(block: Block, scopeId: string): void { + if (block instanceof Element) { + block.setAttribute(scopeId, '') + } else if (isVaporComponent(block)) { + setScopeId(block.block, scopeId) + } else if (isArray(block)) { + for (const b of block) { + setScopeId(b, scopeId) + } + } else if (isFragment(block)) { + setScopeId(block.nodes, scopeId) + } +} + +export function setComponentScopeId(instance: VaporComponentInstance): void { + const parent = instance.parent + if (!parent) return + + if (isArray(instance.block) && instance.block.length > 1) return + + const scopeId = parent.type.__scopeId + if (scopeId) { + setScopeId(instance.block, scopeId) + } + + // vdom parent + if ( + parent.subTree && + (parent.subTree.component as any) === instance && + parent.vnode!.scopeId + ) { + setScopeId(instance.block, parent.vnode!.scopeId) + } +} diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 548babebf8b..ea01450a119 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -25,7 +25,14 @@ import { unregisterHMR, warn, } from '@vue/runtime-dom' -import { type Block, insert, isBlock, remove } from './block' +import { + type Block, + insert, + isBlock, + remove, + setComponentScopeId, + setScopeId, +} from './block' import { type ShallowRef, markRaw, @@ -59,7 +66,11 @@ import { } from './componentSlots' import { hmrReload, hmrRerender } from './hmr' import { isHydrating, locateHydrationNode } from './dom/hydration' -import { insertionAnchor, insertionParent } from './insertionState' +import { + insertionAnchor, + insertionParent, + resetInsertionState, +} from './insertionState' export { currentInstance } from '@vue/runtime-dom' @@ -142,6 +153,8 @@ export function createComponent( const _insertionAnchor = insertionAnchor if (isHydrating) { locateHydrationNode() + } else { + resetInsertionState() } // vdom interop enabled and component is not an explicit vapor component @@ -270,9 +283,8 @@ export function createComponent( onScopeDispose(() => unmountComponent(instance), true) if (!isHydrating && _insertionParent) { - insert(instance.block, _insertionParent, _insertionAnchor) + mountComponent(instance, _insertionParent, _insertionAnchor) } - return instance } @@ -474,6 +486,9 @@ export function createComponentWithFallback( // mark single root ;(el as any).$root = isSingleRoot + const scopeId = currentInstance!.type.__scopeId + if (scopeId) setScopeId(el, scopeId) + if (rawProps) { renderEffect(() => { setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)]) @@ -501,6 +516,7 @@ export function mountComponent( } if (instance.bm) invokeArrayFns(instance.bm) insert(instance.block, parent, anchor) + setComponentScopeId(instance) if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!)) instance.isMounted = true if (__DEV__) { diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 77228fd72a0..0e4e1c492ad 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -61,6 +61,7 @@ const vaporInteropImpl: Omit< instance.rawPropsRef = propsRef instance.rawSlotsRef = slotsRef mountComponent(instance, container, selfAnchor) + vnode.el = instance.block simpleSetCurrentInstance(prev) return instance }, @@ -175,6 +176,8 @@ function createVDOMComponent( internals.umt(vnode.component!, null, !!parentNode) } + vnode.scopeId = parentInstance.type.__scopeId! + frag.insert = (parentNode, anchor) => { if (!isMounted) { internals.mt( @@ -198,6 +201,9 @@ function createVDOMComponent( parentInstance as any, ) } + + // update the fragment nodes + frag.nodes = vnode.el as Block } frag.remove = unmount From 4aaa69ae496cfb16b90570f27e12d149a50cafb1 Mon Sep 17 00:00:00 2001 From: daiwei Date: Sun, 8 Jun 2025 09:04:58 +0800 Subject: [PATCH 06/10] wip: save --- packages/runtime-core/src/index.ts | 6 +- packages/runtime-core/src/renderer.ts | 78 +++++++++++++------ .../runtime-vapor/__tests__/scopeId.spec.ts | 46 ++++++++++- packages/runtime-vapor/src/block.ts | 7 +- 4 files changed, 110 insertions(+), 27 deletions(-) diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index e309554f2f6..527bead3ecc 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -505,7 +505,11 @@ export { type VaporInteropInterface } from './apiCreateApp' /** * @internal */ -export { type RendererInternals, MoveType } from './renderer' +export { + type RendererInternals, + MoveType, + getInheritedScopeIds, +} from './renderer' /** * @internal */ diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 5a18d62a8e1..cdf7462449d 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -764,30 +764,9 @@ function baseCreateRenderer( hostSetScopeId(el, slotScopeIds[i]) } } - let subTree = parentComponent && parentComponent.subTree - if (subTree) { - if ( - __DEV__ && - subTree.patchFlag > 0 && - subTree.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT - ) { - subTree = - filterSingleRoot(subTree.children as VNodeArrayChildren) || subTree - } - if ( - vnode === subTree || - (isSuspense(subTree.type) && - (subTree.ssContent === vnode || subTree.ssFallback === vnode)) - ) { - const parentVNode = parentComponent!.vnode! - setScopeId( - el, - parentVNode, - parentVNode.scopeId, - parentVNode.slotScopeIds, - parentComponent!.parent, - ) - } + const inheritedScopeIds = getInheritedScopeIds(vnode, parentComponent) + for (let i = 0; i < inheritedScopeIds.length; i++) { + hostSetScopeId(el, inheritedScopeIds[i]) } } @@ -2656,3 +2635,54 @@ function getVaporInterface( } return res! } + +/** + * shared between vdom and vapor + */ +export function getInheritedScopeIds( + vnode: VNode, + parentComponent: GenericComponentInstance | null, +): string[] { + const inheritedScopeIds: string[] = [] + + let currentParent = parentComponent + let currentVNode = vnode + + while (currentParent) { + let subTree = currentParent.subTree + if (!subTree) break + + if ( + __DEV__ && + subTree.patchFlag > 0 && + subTree.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT + ) { + subTree = + filterSingleRoot(subTree.children as VNodeArrayChildren) || subTree + } + + if ( + currentVNode === subTree || + (isSuspense(subTree.type) && + (subTree.ssContent === currentVNode || + subTree.ssFallback === currentVNode)) + ) { + const parentVNode = currentParent.vnode! + + if (parentVNode.scopeId) { + inheritedScopeIds.push(parentVNode.scopeId) + } + + if (parentVNode.slotScopeIds) { + inheritedScopeIds.push(...parentVNode.slotScopeIds) + } + + currentVNode = parentVNode + currentParent = currentParent.parent + } else { + break + } + } + + return inheritedScopeIds +} diff --git a/packages/runtime-vapor/__tests__/scopeId.spec.ts b/packages/runtime-vapor/__tests__/scopeId.spec.ts index 657936352d3..827ea501f23 100644 --- a/packages/runtime-vapor/__tests__/scopeId.spec.ts +++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts @@ -321,6 +321,50 @@ describe('vdom interop', () => { ) }) + test('vdom parent > vapor > vapor > vdom child', () => { + const InnerVdomChild = { + __scopeId: 'inner-vdom-child', + setup() { + return () => h('button') + }, + } + + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', + setup() { + return createComponent(InnerVdomChild as any, null, null, true) + }, + }) + + const VaporChild2 = defineVaporComponent({ + __scopeId: 'vapor-child2', + setup() { + return createComponent(VaporChild as any, null, null, true) + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h(VaporChild2 as any) + }, + } + + const App = { + __scopeId: 'parent', + setup() { + return () => h(VdomChild) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + test('vdom parent > vapor dynamic child', () => { const VaporChild = defineVaporComponent({ __scopeId: 'vapor-child', @@ -418,7 +462,7 @@ describe('vdom interop', () => { ) }) - test.todo('vapor parent > vdom > vdom > vapor child', () => { + test('vapor parent > vdom > vdom > vapor child', () => { const InnerVaporChild = defineVaporComponent({ __scopeId: 'inner-vapor-child', setup() { diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index c094642dfca..a67e43543e9 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -8,6 +8,7 @@ import { import { createComment, createTextNode } from './dom/node' import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' import { isHydrating } from './dom/hydration' +import { getInheritedScopeIds } from '@vue/runtime-dom' export type Block = | Node @@ -213,12 +214,16 @@ export function setComponentScopeId(instance: VaporComponentInstance): void { setScopeId(instance.block, scopeId) } - // vdom parent + // inherit scopeId from vdom parent if ( parent.subTree && (parent.subTree.component as any) === instance && parent.vnode!.scopeId ) { setScopeId(instance.block, parent.vnode!.scopeId) + const scopeIds = getInheritedScopeIds(parent.vnode!, parent.parent) + for (const id of scopeIds) { + setScopeId(instance.block, id) + } } } From 9772a4c6c4ac869c5c4d47d4fdb2b2c859ddac94 Mon Sep 17 00:00:00 2001 From: daiwei Date: Sun, 8 Jun 2025 16:22:30 +0800 Subject: [PATCH 07/10] wip: slotScopeIds --- .../src/generators/component.ts | 3 +- packages/compiler-vapor/src/ir/index.ts | 1 + .../src/transforms/transformElement.ts | 1 + packages/runtime-core/src/apiCreateApp.ts | 7 +- .../runtime-vapor/__tests__/scopeId.spec.ts | 243 +++++++++++++----- packages/runtime-vapor/src/apiCreateApp.ts | 4 + .../src/apiCreateDynamicComponent.ts | 4 + packages/runtime-vapor/src/block.ts | 6 +- packages/runtime-vapor/src/component.ts | 18 +- packages/runtime-vapor/src/componentSlots.ts | 26 +- packages/runtime-vapor/src/vdomInterop.ts | 3 +- 11 files changed, 247 insertions(+), 69 deletions(-) diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index 7c232db754b..05b16077b08 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -47,7 +47,7 @@ export function genCreateComponent( const { helper } = context const tag = genTag() - const { root, props, slots, once } = operation + const { root, props, slots, once, scopeId } = operation const rawSlots = genRawSlots(slots, context) const [ids, handlers] = processInlineHandlers(props, context) const rawProps = context.withId(() => genRawProps(props, context), ids) @@ -75,6 +75,7 @@ export function genCreateComponent( rawSlots, root ? 'true' : false, once && 'true', + scopeId && JSON.stringify(scopeId), ), ...genDirectivesForElement(operation.id, context), ] diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 086f77ca612..e6394ad4661 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -197,6 +197,7 @@ export interface CreateComponentIRNode extends BaseIRNode { dynamic?: SimpleExpressionNode parent?: number anchor?: number + scopeId?: string | null } export interface DeclareOldRefIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index dceb3fd6121..fbb48d82018 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -133,6 +133,7 @@ function transformComponentElement( root: singleRoot, slots: [...context.slots], once: context.inVOnce, + scopeId: context.inSlot ? context.options.scopeId : undefined, dynamic: dynamicComponent, } context.slots = [] diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 5bdd204cfad..531496fb743 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -188,7 +188,12 @@ export interface VaporInteropInterface { move(vnode: VNode, container: any, anchor: any): void slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void - vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any + vdomMount: ( + component: ConcreteComponent, + props?: any, + slots?: any, + scopeId?: string, + ) => any vdomUnmount: UnmountComponentFn vdomSlot: ( slots: any, diff --git a/packages/runtime-vapor/__tests__/scopeId.spec.ts b/packages/runtime-vapor/__tests__/scopeId.spec.ts index 827ea501f23..48051b1497f 100644 --- a/packages/runtime-vapor/__tests__/scopeId.spec.ts +++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts @@ -4,6 +4,7 @@ import { createDynamicComponent, createSlot, defineVaporComponent, + forwardedSlotCreator, setInsertionState, template, vaporInteropPlugin, @@ -200,7 +201,7 @@ describe('scopeId', () => { test.todo('should attach scopeId to suspense content', async () => {}) // :slotted basic - test.todo('should work on slots', () => { + test('should work on slots', () => { const Child = defineVaporComponent({ __scopeId: 'child', setup() { @@ -227,7 +228,14 @@ describe('scopeId', () => { { default: () => { const n0 = template('
')() - const n1 = createComponent(Child2) + const n1 = createComponent( + Child2, + null, + null, + undefined, + undefined, + 'parent', + ) return [n0, n1] }, }, @@ -244,13 +252,69 @@ describe('scopeId', () => { // - scopeId from template context // - slotted scopeId from slot owner // - its own scopeId - `` + + `` + `` + ``, ) }) - test.todo(':slotted on forwarded slots', async () => {}) + test(':slotted on forwarded slots', async () => { + const Wrapper = defineVaporComponent({ + __scopeId: 'wrapper', + setup() { + //
+ const n1 = template('
', true)() as any + setInsertionState(n1) + createSlot('default', null) + return n1 + }, + }) + + const Slotted = defineVaporComponent({ + __scopeId: 'slotted', + setup() { + // + const _createForwardedSlot = forwardedSlotCreator() + const n1 = createComponent( + Wrapper, + null, + { + default: () => { + const n0 = _createForwardedSlot('default', null) + return n0 + }, + }, + true, + ) + return n1 + }, + }) + + const { html } = define({ + __scopeId: 'root', + setup() { + //
+ const n2 = createComponent( + Slotted, + null, + { + default: () => { + return template('
')() + }, + }, + true, + ) + return n2 + }, + }).render() + + expect(html()).toBe( + `
` + + `
` + + `` + + `
`, + ) + }) }) describe('vdom interop', () => { @@ -262,17 +326,16 @@ describe('vdom interop', () => { }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -280,13 +343,13 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vdom parent > vapor > vdom child', () => { - const InnerVdomChild = { - __scopeId: 'inner-vdom-child', + const VdomChild = { + __scopeId: 'vdom-child', setup() { return () => h('button') }, @@ -295,21 +358,20 @@ describe('vdom interop', () => { const VaporChild = defineVaporComponent({ __scopeId: 'vapor-child', setup() { - return createComponent(InnerVdomChild as any, null, null, true) + return createComponent(VdomChild as any, null, null, true) }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -317,43 +379,42 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vdom parent > vapor > vapor > vdom child', () => { - const InnerVdomChild = { - __scopeId: 'inner-vdom-child', + const VdomChild = { + __scopeId: 'vdom-child', setup() { return () => h('button') }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const NestedVaporChild = defineVaporComponent({ + __scopeId: 'nested-vapor-child', setup() { - return createComponent(InnerVdomChild as any, null, null, true) + return createComponent(VdomChild as any, null, null, true) }, }) - const VaporChild2 = defineVaporComponent({ - __scopeId: 'vapor-child2', + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', setup() { - return createComponent(VaporChild as any, null, null, true) + return createComponent(NestedVaporChild as any, null, null, true) }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { - return () => h(VaporChild2 as any) + return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -361,7 +422,7 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) @@ -373,17 +434,16 @@ describe('vdom interop', () => { }, }) - const VdomChild = { - __scopeId: 'vdom-child', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VaporChild as any) }, } const App = { - __scopeId: 'parent', setup() { - return () => h(VdomChild) + return () => h(VdomParent) }, } @@ -391,7 +451,7 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) @@ -403,17 +463,16 @@ describe('vdom interop', () => { }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', setup() { return createComponent(VdomChild as any, null, null, true) }, }) const App = { - __scopeId: 'parent', setup() { - return () => h(VaporChild as any) + return () => h(VaporParent as any) }, } @@ -421,36 +480,35 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vapor parent > vdom > vapor child', () => { - const InnerVaporChild = defineVaporComponent({ - __scopeId: 'inner-vapor-child', + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', setup() { - return template('', true)() + return template('', true)() }, }) const VdomChild = { __scopeId: 'vdom-child', setup() { - return () => h(InnerVaporChild as any) + return () => h(VaporChild as any) }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', setup() { return createComponent(VdomChild as any, null, null, true) }, }) const App = { - __scopeId: 'parent', setup() { - return () => h(VaporChild as any) + return () => h(VaporParent as any) }, } @@ -458,43 +516,100 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + ``, ) }) test('vapor parent > vdom > vdom > vapor child', () => { - const InnerVaporChild = defineVaporComponent({ - __scopeId: 'inner-vapor-child', + const VaporChild = defineVaporComponent({ + __scopeId: 'vapor-child', setup() { - return template('', true)() + return template('', true)() }, }) const VdomChild = { __scopeId: 'vdom-child', setup() { - return () => h(InnerVaporChild as any) + return () => h(VaporChild as any) }, } - const VdomChild2 = { - __scopeId: 'vdom-child2', + const VdomParent = { + __scopeId: 'vdom-parent', setup() { return () => h(VdomChild as any) }, } - const VaporChild = defineVaporComponent({ - __scopeId: 'vapor-child', + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', setup() { - return createComponent(VdomChild2 as any, null, null, true) + return createComponent(VdomParent as any, null, null, true) }, }) const App = { - __scopeId: 'parent', setup() { - return () => h(VaporChild as any) + return () => h(VaporParent as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + + expect(root.innerHTML).toBe( + ``, + ) + }) + + test('vapor parent > vapor slot > vdom child', () => { + const VaporSlot = defineVaporComponent({ + __scopeId: 'vapor-slot', + setup() { + const n1 = template('
', true)() as any + setInsertionState(n1) + createSlot('default', null) + return n1 + }, + }) + + const VdomChild = { + __scopeId: 'vdom-child', + setup() { + return () => h('span') + }, + } + + const VaporParent = defineVaporComponent({ + __scopeId: 'vapor-parent', + setup() { + const n2 = createComponent( + VaporSlot, + null, + { + default: () => { + const n0 = template('
')() + const n1 = createComponent( + VdomChild, + undefined, + undefined, + undefined, + undefined, + 'vapor-parent', + ) + return [n0, n1] + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => h(VaporParent as any) }, } @@ -502,7 +617,11 @@ describe('vdom interop', () => { createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe( - ``, + `
` + + `
` + + `` + + `` + + `
`, ) }) }) diff --git a/packages/runtime-vapor/src/apiCreateApp.ts b/packages/runtime-vapor/src/apiCreateApp.ts index 834437ee350..1a9ed06a735 100644 --- a/packages/runtime-vapor/src/apiCreateApp.ts +++ b/packages/runtime-vapor/src/apiCreateApp.ts @@ -41,6 +41,8 @@ const mountApp: AppMountFn = (app, container) => { app._props as RawProps, null, false, + false, + undefined, app._context, ) mountComponent(instance, container) @@ -61,6 +63,8 @@ const hydrateApp: AppMountFn = (app, container) => { app._props as RawProps, null, false, + false, + undefined, app._context, ) mountComponent(instance, container) diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 33697b4ca76..e436f77e291 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -12,6 +12,8 @@ export function createDynamicComponent( rawProps?: RawProps | null, rawSlots?: RawSlots | null, isSingleRoot?: boolean, + once?: boolean, + scopeId?: string, ): VaporFragment { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor @@ -28,6 +30,8 @@ export function createDynamicComponent( rawProps, rawSlots, isSingleRoot, + once, + scopeId, ), value, ) diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index a67e43543e9..a5a63807ee1 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -35,6 +35,11 @@ export class DynamicFragment extends VaporFragment { scope: EffectScope | undefined current?: BlockFn fallback?: BlockFn + /** + * slot only + * indicates forwarded slot + */ + forwarded?: boolean constructor(anchorLabel?: string) { super([]) @@ -206,7 +211,6 @@ export function setScopeId(block: Block, scopeId: string): void { export function setComponentScopeId(instance: VaporComponentInstance): void { const parent = instance.parent if (!parent) return - if (isArray(instance.block) && instance.block.length > 1) return const scopeId = parent.type.__scopeId diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index ea01450a119..4708db61885 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -145,6 +145,8 @@ export function createComponent( rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, isSingleRoot?: boolean, + once?: boolean, // TODO once support + scopeId?: string, appContext: GenericAppContext = (currentInstance && currentInstance.appContext) || emptyContext, @@ -163,6 +165,7 @@ export function createComponent( component as any, rawProps, rawSlots, + scopeId, ) if (!isHydrating && _insertionParent) { insert(frag, _insertionParent, _insertionAnchor) @@ -282,6 +285,8 @@ export function createComponent( onScopeDispose(() => unmountComponent(instance), true) + if (scopeId) setScopeId(instance.block, scopeId) + if (!isHydrating && _insertionParent) { mountComponent(instance, _insertionParent, _insertionAnchor) } @@ -477,16 +482,25 @@ export function createComponentWithFallback( rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, isSingleRoot?: boolean, + once?: boolean, + scopeId?: string, ): HTMLElement | VaporComponentInstance { if (!isString(comp)) { - return createComponent(comp, rawProps, rawSlots, isSingleRoot) + return createComponent( + comp, + rawProps, + rawSlots, + isSingleRoot, + once, + scopeId, + ) } const el = document.createElement(comp) // mark single root ;(el as any).$root = isSingleRoot - const scopeId = currentInstance!.type.__scopeId + scopeId = scopeId || currentInstance!.type.__scopeId if (scopeId) setScopeId(el, scopeId) if (rawProps) { diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 19e9b5b6d1a..32dd235a0e7 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,5 +1,11 @@ import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared' -import { type Block, type BlockFn, DynamicFragment, insert } from './block' +import { + type Block, + type BlockFn, + DynamicFragment, + insert, + setScopeId, +} from './block' import { rawPropsProxyHandlers } from './componentProps' import { currentInstance, isRef } from '@vue/runtime-dom' import type { LooseRawProps, VaporComponentInstance } from './component' @@ -156,9 +162,27 @@ export function createSlot( } } + if (i) fragment.forwarded = true + if (i || !hasForwardedSlot(fragment.nodes)) { + const scopeId = instance!.type.__scopeId + if (scopeId) setScopeId(fragment, `${scopeId}-s`) + } + if (!isHydrating && _insertionParent) { insert(fragment, _insertionParent, _insertionAnchor) } return fragment } + +function isForwardedSlot(block: Block): block is DynamicFragment { + return block instanceof DynamicFragment && !!block.forwarded +} + +function hasForwardedSlot(block: Block): block is DynamicFragment { + if (isArray(block)) { + return block.some(isForwardedSlot) + } else { + return isForwardedSlot(block) + } +} diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index e4d3c8de3b3..af86b291b27 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -155,6 +155,7 @@ function createVDOMComponent( component: ConcreteComponent, rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, + scopeId?: string, ): VaporFragment { const frag = new VaporFragment([]) const vnode = createVNode( @@ -183,7 +184,7 @@ function createVDOMComponent( internals.umt(vnode.component!, null, !!parentNode) } - vnode.scopeId = parentInstance.type.__scopeId! + vnode.scopeId = scopeId || parentInstance.type.__scopeId! frag.insert = (parentNode, anchor) => { if (!isMounted) { From 7cfec7fcfbc2801c785a445e1814e8e8319f3827 Mon Sep 17 00:00:00 2001 From: daiwei Date: Wed, 11 Jun 2025 17:34:31 +0800 Subject: [PATCH 08/10] wip: vdom slot interop --- .../runtime-core/src/helpers/renderSlot.ts | 21 + packages/runtime-core/src/index.ts | 4 + packages/runtime-core/src/renderer.ts | 2 +- .../__tests__/componentSlots.spec.ts | 2127 ++++++++++++++++- packages/runtime-vapor/src/block.ts | 2 +- packages/runtime-vapor/src/componentSlots.ts | 39 +- packages/runtime-vapor/src/vdomInterop.ts | 112 +- 7 files changed, 2275 insertions(+), 32 deletions(-) diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index 152c5a4b81c..c17cb1e3105 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -81,6 +81,10 @@ export function renderSlot( } openBlock() const validSlotContent = slot && ensureValidVNode(slot(props)) + + // handle forwarded vapor slot fallback + ensureVaporSlotFallback(validSlotContent, fallback) + const slotKey = props.key || // slot content array of a dynamic conditional slot may have a branch @@ -124,3 +128,20 @@ export function ensureValidVNode( ? vnodes : null } + +export function ensureVaporSlotFallback( + vnodes: VNodeArrayChildren | null | undefined, + fallback?: () => VNodeArrayChildren, +): void { + let vaporSlot: any + if ( + vnodes && + vnodes.length === 1 && + isVNode(vnodes[0]) && + (vaporSlot = vnodes[0].vs) + ) { + if (!vaporSlot.fallback && fallback) { + vaporSlot.fallback = fallback + } + } +} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index e309554f2f6..f921eb0a2cf 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -557,3 +557,7 @@ export { startMeasure, endMeasure } from './profiling' * @internal */ export { initFeatureFlags } from './featureFlags' +/** + * @internal + */ +export { ensureVaporSlotFallback } from './helpers/renderSlot' diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 5a18d62a8e1..a1468816789 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2622,7 +2622,7 @@ export function traverseStaticChildren( function locateNonHydratedAsyncRoot( instance: ComponentInternalInstance, ): ComponentInternalInstance | undefined { - const subComponent = instance.subTree.component + const subComponent = instance.vapor ? null : instance.subTree.component if (subComponent) { if (subComponent.asyncDep && !subComponent.asyncResolved) { return subComponent diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index bdbd960363d..642be2a139f 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -12,8 +12,17 @@ import { prepend, renderEffect, template, + vaporInteropPlugin, } from '../src' -import { currentInstance, nextTick, ref } from '@vue/runtime-dom' +import { + createApp, + createSlots, + currentInstance, + h, + nextTick, + ref, + renderSlot, +} from '@vue/runtime-dom' import { makeRender } from './_utils' import type { DynamicSlot } from '../src/componentSlots' import { setElementText, setText } from '../src/dom/prop' @@ -470,6 +479,43 @@ describe('component: slots', () => { expect(html()).toBe('content') }) + test('use fallback on initial render', async () => { + const Child = { + setup() { + return createSlot('default', null, () => + document.createTextNode('fallback'), + ) + }, + } + + const toggle = ref(false) + + const { html } = define({ + setup() { + return createComponent(Child, null, { + default: () => { + return createIf( + () => toggle.value, + () => { + return document.createTextNode('content') + }, + ) + }, + }) + }, + }).render() + + expect(html()).toBe('fallback') + + toggle.value = true + await nextTick() + expect(html()).toBe('content') + + toggle.value = false + await nextTick() + expect(html()).toBe('fallback') + }) + test('dynamic slot work with v-if', async () => { const val = ref('header') const toggle = ref(false) @@ -605,5 +651,2084 @@ describe('component: slots', () => { await nextTick() expect(host.innerHTML).toBe('barbar') }) + + describe('vdom interop', () => { + test('vdom slot > vapor forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlotWithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
forwarded fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlotWithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
forwarded fallback
') + }) + + test('vdom slot > vapor forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VaporForwardedSlotWithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
forwarded fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlotWithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
forwarded fallback
') + }) + + test('vdom slot > vapor forwarded slot > vdom forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlot = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlot = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlotWithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
forwarded fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlotWithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
forwarded fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlotWithFallback = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => { + return [h('div', 'vdom fallback')] + }), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlotWithFallback as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vdom fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot(empty) > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => { + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlotWithFallback = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => { + return [h('div', 'vdom fallback')] + }), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlotWithFallback as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => h(VaporForwardedSlot as any) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('
vdom fallback
') + }) + + test('vdom slot > vapor forwarded slot > vdom forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VdomForwardedSlot = { + render(this: any) { + return h(VdomSlot, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vdom forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VdomForwardedSlot = { + render(this: any) { + return h(VdomSlot, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlotWithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlotWithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vapor fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot > vdom forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VdomForwardedSlotWithFallback = { + render(this: any) { + return h(VdomSlot, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlotWithFallback as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vdom fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VdomForwardedSlot = { + render(this: any) { + return h(VdomSlot, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot2 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot1, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VdomForwardedSlotWithFallback = { + render(this: any) { + return h(VdomSlot, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VaporForwardedSlot2 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomForwardedSlotWithFallback as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot1, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe( + '
vdom fallback
', + ) + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlot = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot > vapor forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VdomForwardedSlot = { + render(this: any) { + return h(VaporForwardedSlot as any, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VdomForwardedSlot2 = { + render(this: any) { + return h(VaporForwardedSlot as any, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot1 = { + render(this: any) { + return h(VdomForwardedSlot2, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot = { + render(this: any) { + return h(VdomForwardedSlot1, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlot = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const VdomForwardedSlot2 = { + render(this: any) { + return h(VaporForwardedSlot as any, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot1 = { + render(this: any) { + return h(VdomForwardedSlot2, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot = { + render(this: any) { + return h(VdomForwardedSlot1, null, { + foo: () => [renderSlot(this.$slots, 'foo')], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vapor fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot > vapor forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VaporForwardedSlot2 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot1 as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VaporForwardedSlot2 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1WithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor1 fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot1WithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vapor1 fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot > vapor forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VaporForwardedSlot2WithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor2 fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2WithFallback, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot1 as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vapor2 fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlot2 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1 = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2, + null, + { + foo: () => { + return createForwardedSlot('foo', null) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot1 as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VaporForwardedSlot2WithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VdomSlot as any, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor2 fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1WithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2WithFallback, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor1 fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot1WithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vapor1 fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VaporForwardedSlot2WithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporSlot, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor2 fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const VaporForwardedSlot1WithFallback = defineVaporComponent({ + setup() { + const createForwardedSlot = forwardedSlotCreator() + const n2 = createComponent( + VaporForwardedSlot2WithFallback, + null, + { + foo: () => { + return createForwardedSlot('foo', null, () => { + const n2 = template('
vapor1 fallback
')() + return n2 + }) + }, + }, + true, + ) + return n2 + }, + }) + + const App = { + setup() { + return () => + h( + VaporForwardedSlot1WithFallback as any, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe( + '
vapor1 fallback
', + ) + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlot2WithFallback = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom2 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot1WithFallback = { + render(this: any) { + return h(VdomForwardedSlot2WithFallback, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom1 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot1WithFallback, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vdom1 fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vdom slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VdomSlot = { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'fallback'), + ]) + }, + } + + const VdomForwardedSlot2WithFallback = { + render(this: any) { + return h(VdomSlot, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom2 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot1WithFallback = { + render(this: any) { + return h(VdomForwardedSlot2WithFallback, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom1 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot1WithFallback, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vdom1 fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) (multiple) > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = defineVaporComponent({ + setup() { + const n0 = createSlot('foo', null, () => { + const n2 = template('
fallback
')() + return n2 + }) + return n0 + }, + }) + + const VdomForwardedSlot3WithFallback = { + render(this: any) { + return h(VaporSlot as any, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom3 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot2WithFallback = { + render(this: any) { + return h(VdomForwardedSlot3WithFallback, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom2 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const VdomForwardedSlot1WithFallback = { + render(this: any) { + return h(VdomForwardedSlot2WithFallback, null, { + foo: () => [ + renderSlot(this.$slots, 'foo', {}, () => [ + h('div', 'vdom1 fallback'), + ]), + ], + _: 3 /* FORWARDED */, + }) + }, + } + + const App = { + setup() { + return () => + h( + VdomForwardedSlot1WithFallback, + null, + createSlots({ _: 2 /* DYNAMIC */ } as any, [ + show.value + ? { + name: 'foo', + fn: () => [h('span', foo.value)], + key: '0', + } + : undefined, + ]), + ) + }, + } + + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') + + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') + + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
vdom1 fallback
') + + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + }) }) }) diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index b782afd38d3..b2d0ca04b67 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -23,6 +23,7 @@ export class VaporFragment { anchor?: Node insert?: (parent: ParentNode, anchor: Node | null) => void remove?: (parent?: ParentNode) => void + fallback?: BlockFn constructor(nodes: Block) { this.nodes = nodes @@ -33,7 +34,6 @@ export class DynamicFragment extends VaporFragment { anchor: Node scope: EffectScope | undefined current?: BlockFn - fallback?: BlockFn constructor(anchorLabel?: string) { super([]) diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 19e9b5b6d1a..b8af4c91629 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,5 +1,12 @@ import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared' -import { type Block, type BlockFn, DynamicFragment, insert } from './block' +import { + type Block, + type BlockFn, + DynamicFragment, + type VaporFragment, + insert, + isFragment, +} from './block' import { rawPropsProxyHandlers } from './componentProps' import { currentInstance, isRef } from '@vue/runtime-dom' import type { LooseRawProps, VaporComponentInstance } from './component' @@ -138,8 +145,27 @@ export function createSlot( (slot._bound = () => { const slotContent = slot(slotProps) if (slotContent instanceof DynamicFragment) { - slotContent.fallback = fallback + let nodes = slotContent.nodes + if ( + (slotContent.fallback = fallback) && + isArray(nodes) && + nodes.length === 0 + ) { + // use fallback if the slot content is invalid + slotContent.update(fallback) + } else { + while (isFragment(nodes)) { + ensureVaporSlotFallback(nodes, fallback) + nodes = nodes.nodes + } + } } + // forwarded vdom slot, if there is no fallback provide, try use the fallback + // provided by the slot outlet. + else if (isFragment(slotContent)) { + ensureVaporSlotFallback(slotContent, fallback) + } + return slotContent }), ) @@ -162,3 +188,12 @@ export function createSlot( return fragment } + +function ensureVaporSlotFallback( + block: VaporFragment, + fallback?: VaporSlot, +): void { + if (block.insert && !block.fallback && fallback) { + block.fallback = fallback + } +} diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index e7c7e02e0bd..a6b115f7474 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -4,7 +4,9 @@ import { type ConcreteComponent, MoveType, type Plugin, + type RendererElement, type RendererInternals, + type RendererNode, type ShallowRef, type Slots, type VNode, @@ -12,6 +14,8 @@ import { createVNode, currentInstance, ensureRenderer, + ensureVaporSlotFallback, + isVNode, onScopeDispose, renderSlot, shallowRef, @@ -28,13 +32,13 @@ import { } from './component' import { type Block, + DynamicFragment, VaporFragment, insert, isFragment, - isValidBlock, remove, } from './block' -import { EMPTY_OBJ, extend, isFunction } from '@vue/shared' +import { EMPTY_OBJ, extend, isArray, isFunction } from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' import { renderEffect } from './renderEffect' @@ -106,7 +110,22 @@ const vaporInteropImpl: Omit< // TODO fallback for slot with v-if content // fallback is a vnode slot function here, and slotBlock, if a DynamicFragment, // expects a Vapor BlockFn as fallback - fallback + // fallback + + // forwarded vdom slot without its own fallback, use the fallback provided by + // the slot outlet + if (slotBlock instanceof DynamicFragment) { + // vapor slot's nodes is a forwarded vdom slot + let nodes = slotBlock.nodes + while (isFragment(nodes)) { + ensureVDOMSlotFallback(nodes, fallback) + nodes = nodes.nodes + } + } else if (isFragment(slotBlock)) { + ensureVDOMSlotFallback(slotBlock, fallback) + } + + // TODO use fragment's anchor as selfAnchor? insert((n2.vb = slotBlock), container, selfAnchor) } else { // update @@ -229,51 +248,56 @@ function renderVDOMSlot( let fallbackNodes: Block | undefined let oldVNode: VNode | null = null + frag.fallback = fallback frag.insert = (parentNode, anchor) => { if (!isMounted) { renderEffect(() => { - const vnode = renderSlot( - slotsRef.value, - isFunction(name) ? name() : name, - props, - ) - let isValidSlotContent - let children = vnode.children as any[] + let vnode: VNode | undefined + let isValidSlot = false + // only render slot if rawSlots is defined and slot nodes are not empty + // otherwise, render fallback + if (slotsRef.value) { + vnode = renderSlot( + slotsRef.value, + isFunction(name) ? name() : name, + props, + ) - // 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) + let children = vnode.children as any[] + // handle forwarded vapor slot without its own fallback + // use the fallback provided by the slot outlet + ensureVaporSlotFallback(children, fallback as any) + isValidSlot = children.length > 0 } - // vnode children - else { - isValidSlotContent = children.length > 0 - } - if (isValidSlotContent) { + + if (isValidSlot) { if (fallbackNodes) { remove(fallbackNodes, parentNode) fallbackNodes = undefined } internals.p( oldVNode, - vnode, + vnode!, parentNode, anchor, parentComponent as any, ) - oldVNode = vnode + oldVNode = vnode! } else { + // for forwarded slot without its own fallback, use the fallback + // provided by the slot outlet. + // re-fetch `frag.fallback` as it may have been updated at `createSlot` + fallback = frag.fallback if (fallback && !fallbackNodes) { // mount fallback if (oldVNode) { internals.um(oldVNode, parentComponent as any, null, true) } - insert((fallbackNodes = fallback(props)), parentNode, anchor) + insert( + (fallbackNodes = fallback(internals, parentComponent)), + parentNode, + anchor, + ) } oldVNode = null } @@ -315,3 +339,37 @@ export const vaporInteropPlugin: Plugin = app => { return mount(...args) }) satisfies App['mount'] } + +function ensureVDOMSlotFallback(block: VaporFragment, fallback?: () => any) { + if (block.insert && !block.fallback && fallback) { + block.fallback = createFallback(fallback) + } +} + +const createFallback = + (fallback: () => any) => + ( + internals: RendererInternals, + parentComponent: ComponentInternalInstance | null, + ) => { + const fallbackNodes = fallback() + + // vnode slot, wrap it as a VaporFragment + if (isArray(fallbackNodes) && fallbackNodes.every(isVNode)) { + const frag = new VaporFragment([]) + frag.insert = (parentNode, anchor) => { + fallbackNodes.forEach(vnode => { + internals.p(null, vnode, parentNode, anchor, parentComponent) + }) + } + frag.remove = parentNode => { + fallbackNodes.forEach(vnode => { + internals.um(vnode, parentComponent, null, true) + }) + } + return frag + } + + // vapor slot + return fallbackNodes as Block + } From f4f038873fffdd80f487b73e2a3b38c86d1f6dfd Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 12 Jun 2025 15:54:40 +0800 Subject: [PATCH 09/10] wip: refactor tests --- .../__tests__/componentSlots.spec.ts | 1811 +++-------------- 1 file changed, 299 insertions(+), 1512 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 642be2a139f..4af9ef74eec 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -15,6 +15,7 @@ import { vaporInteropPlugin, } from '../src' import { + type Ref, createApp, createSlots, currentInstance, @@ -653,29 +654,46 @@ describe('component: slots', () => { }) describe('vdom interop', () => { - test('vdom slot > vapor forwarded slot > vapor slot', async () => { - const foo = ref('foo') - const show = ref(true) - - const VaporSlot = defineVaporComponent({ + const createVaporSlot = (fallbackText = 'fallback') => { + return defineVaporComponent({ setup() { const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() + const n2 = template(`
${fallbackText}
`)() return n2 }) return n0 }, }) + } + + const createVdomSlot = (fallbackText = 'fallback') => { + return { + render(this: any) { + return renderSlot(this.$slots, 'foo', {}, () => [ + h('div', fallbackText), + ]) + }, + } + } - const VaporForwardedSlot = defineVaporComponent({ + const createVaporForwardedSlot = ( + targetComponent: any, + fallbackText?: string, + ) => { + return defineVaporComponent({ setup() { const createForwardedSlot = forwardedSlotCreator() const n2 = createComponent( - VaporSlot, + targetComponent, null, { foo: () => { - return createForwardedSlot('foo', null) + return fallbackText + ? createForwardedSlot('foo', null, () => { + const n2 = template(`
${fallbackText}
`)() + return n2 + }) + : createForwardedSlot('foo', null) }, }, true, @@ -683,12 +701,60 @@ describe('component: slots', () => { return n2 }, }) + } + + const createVdomForwardedSlot = ( + targetComponent: any, + fallbackText?: string, + ) => { + return { + render(this: any) { + return h(targetComponent, null, { + foo: () => [ + fallbackText + ? renderSlot(this.$slots, 'foo', {}, () => [ + h('div', fallbackText), + ]) + : renderSlot(this.$slots, 'foo'), + ], + _: 3 /* FORWARDED */, + }) + }, + } + } + + const createMultipleVaporForwardedSlots = ( + targetComponent: any, + count: number, + ) => { + let current = targetComponent + for (let i = 0; i < count; i++) { + current = createVaporForwardedSlot(current) + } + return current + } + + const createMultipleVdomForwardedSlots = ( + targetComponent: any, + count: number, + ) => { + let current = targetComponent + for (let i = 0; i < count; i++) { + current = createVdomForwardedSlot(current) + } + return current + } - const App = { + const createTestApp = ( + rootComponent: any, + foo: Ref, + show: Ref, + ) => { + return { setup() { return () => h( - VaporForwardedSlot as any, + rootComponent, null, createSlots({ _: 2 /* DYNAMIC */ } as any, [ show.value @@ -702,6 +768,23 @@ describe('component: slots', () => { ) }, } + } + + const createEmptyTestApp = (rootComponent: any) => { + return { + setup() { + return () => h(rootComponent) + }, + } + } + + test('vdom slot > vapor forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = createVaporSlot() + const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -720,54 +803,12 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VaporForwardedSlotWithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporSlot, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
forwarded fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlotWithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VaporForwardedSlotWithFallback = createVaporForwardedSlot( + VaporSlot, + 'forwarded fallback', + ) + const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -786,49 +827,9 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VaporForwardedSlot = createVaporForwardedSlot(VdomSlot) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -851,52 +852,12 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VaporForwardedSlotWithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
forwarded fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlotWithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VaporForwardedSlotWithFallback = createVaporForwardedSlot( + VdomSlot, + 'forwarded fallback', + ) + const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -915,60 +876,10 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlot = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot) + const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -991,63 +902,13 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlot = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlotWithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
forwarded fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlotWithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot) + const VaporForwardedSlotWithFallback = createVaporForwardedSlot( + VdomForwardedSlot, + 'forwarded fallback', + ) + const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1070,64 +931,15 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlotWithFallback = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => { - return [h('div', 'vdom fallback')] - }), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlotWithFallback as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlotWithFallback = createVdomForwardedSlot( + VaporSlot, + 'vdom fallback', + ) + const VaporForwardedSlot = createVaporForwardedSlot( + VdomForwardedSlotWithFallback, + ) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1147,51 +959,15 @@ describe('component: slots', () => { }) test('vdom slot(empty) > vapor forwarded slot > vdom forwarded slot(with fallback) > vapor slot', async () => { - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlotWithFallback = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => { - return [h('div', 'vdom fallback')] - }), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlotWithFallback as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => h(VaporForwardedSlot as any) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlotWithFallback = createVdomForwardedSlot( + VaporSlot, + 'vdom fallback', + ) + const VaporForwardedSlot = createVaporForwardedSlot( + VdomForwardedSlotWithFallback, + ) + const App = createEmptyTestApp(VaporForwardedSlot) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1202,58 +978,10 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VdomForwardedSlot = { - render(this: any) { - return h(VdomSlot, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot) + const VaporForwardedSlot = createVaporForwardedSlot(VdomForwardedSlot) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1276,61 +1004,13 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VdomForwardedSlot = { - render(this: any) { - return h(VdomSlot, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlotWithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlotWithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot) + const VaporForwardedSlotWithFallback = createVaporForwardedSlot( + VdomForwardedSlot, + 'vapor fallback', + ) + const App = createTestApp(VaporForwardedSlotWithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1353,62 +1033,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VdomForwardedSlotWithFallback = { - render(this: any) { - return h(VdomSlot, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlotWithFallback as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) + const VdomSlot = createVdomSlot() - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomForwardedSlotWithFallback = createVdomForwardedSlot( + VdomSlot, + 'vdom fallback', + ) + const VaporForwardedSlot = createVaporForwardedSlot( + VdomForwardedSlotWithFallback, + ) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1431,104 +1065,25 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } + const VdomSlot = createVdomSlot() + const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot) + const VaporForwardedSlot = createMultipleVaporForwardedSlots( + VdomForwardedSlot, + 3, + ) + const App = createTestApp(VaporForwardedSlot, foo, show) - const VdomForwardedSlot = { - render(this: any) { - return h(VdomSlot, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } + const root = document.createElement('div') + createApp(App).use(vaporInteropPlugin).mount(root) + expect(root.innerHTML).toBe('foo') - const VaporForwardedSlot2 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) + foo.value = 'bar' + await nextTick() + expect(root.innerHTML).toBe('bar') - const VaporForwardedSlot1 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot1, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } - - const root = document.createElement('div') - createApp(App).use(vaporInteropPlugin).mount(root) - expect(root.innerHTML).toBe('foo') - - foo.value = 'bar' - await nextTick() - expect(root.innerHTML).toBe('bar') - - show.value = false - await nextTick() - expect(root.innerHTML).toBe('
fallback
') + show.value = false + await nextTick() + expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() @@ -1539,96 +1094,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VdomForwardedSlotWithFallback = { - render(this: any) { - return h(VdomSlot, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VaporForwardedSlot2 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomForwardedSlotWithFallback as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot1, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VdomForwardedSlotWithFallback = createVdomForwardedSlot( + VdomSlot, + 'vdom fallback', + ) + const VaporForwardedSlot = createMultipleVaporForwardedSlots( + VdomForwardedSlotWithFallback, + 3, + ) + const App = createTestApp(VaporForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1653,43 +1128,9 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlot = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot) + const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1712,60 +1153,10 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VdomForwardedSlot = { - render(this: any) { - return h(VaporForwardedSlot as any, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot) + const VdomForwardedSlot = createVdomForwardedSlot(VaporForwardedSlot) + const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1788,78 +1179,13 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VdomForwardedSlot2 = { - render(this: any) { - return h(VaporForwardedSlot as any, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot1 = { - render(this: any) { - return h(VdomForwardedSlot2, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot = { - render(this: any) { - return h(VdomForwardedSlot1, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VaporForwardedSlot = createVaporForwardedSlot(VaporSlot) + const VdomForwardedSlot = createMultipleVdomForwardedSlots( + VaporForwardedSlot, + 3, + ) + const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1882,81 +1208,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VaporForwardedSlot = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const VdomForwardedSlot2 = { - render(this: any) { - return h(VaporForwardedSlot as any, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot1 = { - render(this: any) { - return h(VdomForwardedSlot2, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot = { - render(this: any) { - return h(VdomForwardedSlot1, null, { - foo: () => [renderSlot(this.$slots, 'foo')], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VaporForwardedSlot = createVaporForwardedSlot( + VaporSlot, + 'vapor fallback', + ) + const VdomForwardedSlot = createMultipleVdomForwardedSlots( + VaporForwardedSlot, + 3, + ) + const App = createTestApp(VdomForwardedSlot, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -1979,66 +1240,12 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VaporForwardedSlot2 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot1 as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VaporForwardedSlot1 = createMultipleVaporForwardedSlots( + VdomSlot, + 2, + ) + const App = createTestApp(VaporForwardedSlot1, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2061,69 +1268,13 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VaporForwardedSlot2 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1WithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor1 fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot1WithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VaporForwardedSlot2 = createVaporForwardedSlot(VdomSlot) + const VaporForwardedSlot1WithFallback = createVaporForwardedSlot( + VaporForwardedSlot2, + 'vapor1 fallback', + ) + const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2146,69 +1297,15 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VaporForwardedSlot2WithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor2 fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2WithFallback, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot1 as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VaporForwardedSlot2WithFallback = createVaporForwardedSlot( + VdomSlot, + 'vapor2 fallback', + ) + const VaporForwardedSlot1 = createVaporForwardedSlot( + VaporForwardedSlot2WithFallback, + ) + const App = createTestApp(VaporForwardedSlot1, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2222,78 +1319,21 @@ describe('component: slots', () => { await nextTick() expect(root.innerHTML).toBe('
vapor2 fallback
') - show.value = true - await nextTick() - expect(root.innerHTML).toBe('bar') - }) - - test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => { - const foo = ref('foo') - const show = ref(true) - - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VaporForwardedSlot2 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporSlot, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1 = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2, - null, - { - foo: () => { - return createForwardedSlot('foo', null) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot1 as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } - + show.value = true + await nextTick() + expect(root.innerHTML).toBe('bar') + }) + + test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => { + const foo = ref('foo') + const show = ref(true) + + const VaporSlot = createVaporSlot() + const VaporForwardedSlot2 = createVaporForwardedSlot(VaporSlot) + const VaporForwardedSlot1 = + createVaporForwardedSlot(VaporForwardedSlot2) + const App = createTestApp(VaporForwardedSlot1, foo, show) + const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) expect(root.innerHTML).toBe('foo') @@ -2315,72 +1355,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VaporForwardedSlot2WithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VdomSlot as any, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor2 fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1WithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2WithFallback, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor1 fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot1WithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VaporForwardedSlot2WithFallback = createVaporForwardedSlot( + VdomSlot, + 'vapor2 fallback', + ) + const VaporForwardedSlot1WithFallback = createVaporForwardedSlot( + VaporForwardedSlot2WithFallback, + 'vapor1 fallback', + ) + const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2403,74 +1387,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VaporForwardedSlot2WithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporSlot, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor2 fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const VaporForwardedSlot1WithFallback = defineVaporComponent({ - setup() { - const createForwardedSlot = forwardedSlotCreator() - const n2 = createComponent( - VaporForwardedSlot2WithFallback, - null, - { - foo: () => { - return createForwardedSlot('foo', null, () => { - const n2 = template('
vapor1 fallback
')() - return n2 - }) - }, - }, - true, - ) - return n2 - }, - }) - - const App = { - setup() { - return () => - h( - VaporForwardedSlot1WithFallback as any, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VaporForwardedSlot2WithFallback = createVaporForwardedSlot( + VaporSlot, + 'vapor2 fallback', + ) + const VaporForwardedSlot1WithFallback = createVaporForwardedSlot( + VaporForwardedSlot2WithFallback, + 'vapor1 fallback', + ) + const App = createTestApp(VaporForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2495,60 +1421,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlot2WithFallback = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom2 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot1WithFallback = { - render(this: any) { - return h(VdomForwardedSlot2WithFallback, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom1 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot1WithFallback, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlot2WithFallback = createVdomForwardedSlot( + VaporSlot, + 'vdom2 fallback', + ) + const VdomForwardedSlot1WithFallback = createVdomForwardedSlot( + VdomForwardedSlot2WithFallback, + 'vdom1 fallback', + ) + const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2571,58 +1453,16 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VdomSlot = { - render(this: any) { - return renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'fallback'), - ]) - }, - } - - const VdomForwardedSlot2WithFallback = { - render(this: any) { - return h(VdomSlot, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom2 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot1WithFallback = { - render(this: any) { - return h(VdomForwardedSlot2WithFallback, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom1 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot1WithFallback, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VdomSlot = createVdomSlot() + const VdomForwardedSlot2WithFallback = createVdomForwardedSlot( + VdomSlot, + 'vdom2 fallback', + ) + const VdomForwardedSlot1WithFallback = createVdomForwardedSlot( + VdomForwardedSlot2WithFallback, + 'vdom1 fallback', + ) + const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) @@ -2645,73 +1485,20 @@ describe('component: slots', () => { const foo = ref('foo') const show = ref(true) - const VaporSlot = defineVaporComponent({ - setup() { - const n0 = createSlot('foo', null, () => { - const n2 = template('
fallback
')() - return n2 - }) - return n0 - }, - }) - - const VdomForwardedSlot3WithFallback = { - render(this: any) { - return h(VaporSlot as any, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom3 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot2WithFallback = { - render(this: any) { - return h(VdomForwardedSlot3WithFallback, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom2 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const VdomForwardedSlot1WithFallback = { - render(this: any) { - return h(VdomForwardedSlot2WithFallback, null, { - foo: () => [ - renderSlot(this.$slots, 'foo', {}, () => [ - h('div', 'vdom1 fallback'), - ]), - ], - _: 3 /* FORWARDED */, - }) - }, - } - - const App = { - setup() { - return () => - h( - VdomForwardedSlot1WithFallback, - null, - createSlots({ _: 2 /* DYNAMIC */ } as any, [ - show.value - ? { - name: 'foo', - fn: () => [h('span', foo.value)], - key: '0', - } - : undefined, - ]), - ) - }, - } + const VaporSlot = createVaporSlot() + const VdomForwardedSlot3WithFallback = createVdomForwardedSlot( + VaporSlot, + 'vdom3 fallback', + ) + const VdomForwardedSlot2WithFallback = createVdomForwardedSlot( + VdomForwardedSlot3WithFallback, + 'vdom2 fallback', + ) + const VdomForwardedSlot1WithFallback = createVdomForwardedSlot( + VdomForwardedSlot2WithFallback, + 'vdom1 fallback', + ) + const App = createTestApp(VdomForwardedSlot1WithFallback, foo, show) const root = document.createElement('div') createApp(App).use(vaporInteropPlugin).mount(root) From 4435a2eca543f02b56ebfea6c79fca8b4a1a4dba Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 13 Jun 2025 14:55:06 +0800 Subject: [PATCH 10/10] chore: use fragment's anchor as insertion point --- .../__tests__/componentSlots.spec.ts | 48 +++++++++---------- packages/runtime-vapor/src/block.ts | 5 +- packages/runtime-vapor/src/vdomInterop.ts | 14 +++--- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts index 4af9ef74eec..9528e91de18 100644 --- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts +++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts @@ -796,7 +796,7 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
fallback
') + expect(root.innerHTML).toBe('
fallback
') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor slot', async () => { @@ -820,7 +820,7 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
forwarded fallback
') + expect(root.innerHTML).toBe('
forwarded fallback
') }) test('vdom slot > vapor forwarded slot > vdom slot', async () => { @@ -1083,11 +1083,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
fallback
') + expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot (multiple) > vdom forwarded slot(with fallback) > vdom slot', async () => { @@ -1116,12 +1116,12 @@ describe('component: slots', () => { show.value = false await nextTick() expect(root.innerHTML).toBe( - '
vdom fallback
', + '
vdom fallback
', ) show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot > vapor slot', async () => { @@ -1168,11 +1168,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
fallback
') + expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot > vdom slot', async () => { @@ -1197,11 +1197,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
fallback
') + expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot (multiple) > vapor forwarded slot(with fallback) > vdom slot', async () => { @@ -1229,11 +1229,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
vapor fallback
') + expect(root.innerHTML).toBe('
vapor fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vapor forwarded slot > vdom slot', async () => { @@ -1257,11 +1257,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
fallback
') + expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot > vdom slot', async () => { @@ -1286,11 +1286,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
vapor1 fallback
') + expect(root.innerHTML).toBe('
vapor1 fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vapor forwarded slot(with fallback) > vdom slot', async () => { @@ -1317,11 +1317,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
vapor2 fallback
') + expect(root.innerHTML).toBe('
vapor2 fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot > vapor forwarded slot > vapor slot', async () => { @@ -1344,11 +1344,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
fallback
') + expect(root.innerHTML).toBe('
fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vdom slot', async () => { @@ -1376,11 +1376,11 @@ describe('component: slots', () => { show.value = false await nextTick() - expect(root.innerHTML).toBe('
vapor1 fallback
') + expect(root.innerHTML).toBe('
vapor1 fallback
') show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vapor forwarded slot(with fallback) > vapor forwarded slot(with fallback) > vapor slot', async () => { @@ -1409,12 +1409,12 @@ describe('component: slots', () => { show.value = false await nextTick() expect(root.innerHTML).toBe( - '
vapor1 fallback
', + '
vapor1 fallback
', ) show.value = true await nextTick() - expect(root.innerHTML).toBe('bar') + expect(root.innerHTML).toBe('bar') }) test('vdom slot > vdom forwarded slot(with fallback) > vdom forwarded slot(with fallback) > vapor slot', async () => { diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index b2d0ca04b67..93c88f9c011 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -124,6 +124,10 @@ export function insert( insert(b, parent, anchor) } } else { + if (block.anchor) { + insert(block.anchor, parent, anchor) + anchor = block.anchor + } // fragment if (block.insert) { // TODO handle hydration for vdom interop @@ -131,7 +135,6 @@ export function insert( } else { insert(block.nodes, parent, anchor) } - if (block.anchor) insert(block.anchor, parent, anchor) } } diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index a6b115f7474..9b73e15b84c 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -102,16 +102,10 @@ const vaporInteropImpl: Omit< slot(n1: VNode, n2: VNode, container, anchor) { if (!n1) { // mount - const selfAnchor = (n2.el = n2.anchor = createTextNode()) - insert(selfAnchor, container, anchor) + let selfAnchor: Node | undefined const { slot, fallback } = n2.vs! const propsRef = (n2.vs!.ref = shallowRef(n2.props)) const slotBlock = slot(new Proxy(propsRef, vaporSlotPropsProxyHandler)) - // TODO fallback for slot with v-if content - // fallback is a vnode slot function here, and slotBlock, if a DynamicFragment, - // expects a Vapor BlockFn as fallback - // fallback - // forwarded vdom slot without its own fallback, use the fallback provided by // the slot outlet if (slotBlock instanceof DynamicFragment) { @@ -121,11 +115,15 @@ const vaporInteropImpl: Omit< ensureVDOMSlotFallback(nodes, fallback) nodes = nodes.nodes } + // use fragment's anchor when possible + selfAnchor = slotBlock.anchor } else if (isFragment(slotBlock)) { ensureVDOMSlotFallback(slotBlock, fallback) + selfAnchor = slotBlock.anchor! } - // TODO use fragment's anchor as selfAnchor? + if (!selfAnchor) selfAnchor = createTextNode() + insert((n2.el = n2.anchor = selfAnchor), container, anchor) insert((n2.vb = slotBlock), container, selfAnchor) } else { // update