diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts
index 04df6c5a48a..94541a50268 100644
--- a/packages/compiler-vapor/src/generators/component.ts
+++ b/packages/compiler-vapor/src/generators/component.ts
@@ -48,7 +48,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 76ef7c53c49..894cb5d9125 100644
--- a/packages/compiler-vapor/src/ir/index.ts
+++ b/packages/compiler-vapor/src/ir/index.ts
@@ -205,6 +205,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
dynamic?: SimpleExpressionNode
parent?: number
anchor?: number
+ scopeId?: string | null
append?: boolean
last?: boolean
}
diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts
index facffadff10..34f295433d7 100644
--- a/packages/compiler-vapor/src/transforms/transformElement.ts
+++ b/packages/compiler-vapor/src/transforms/transformElement.ts
@@ -159,6 +159,7 @@ function transformComponentElement(
root: singleRoot && !context.inVFor,
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 caa39c4436b..b0712e8962b 100644
--- a/packages/runtime-core/src/apiCreateApp.ts
+++ b/packages/runtime-core/src/apiCreateApp.ts
@@ -207,7 +207,12 @@ export interface VaporInteropInterface {
transition: TransitionHooks,
): 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-core/src/index.ts b/packages/runtime-core/src/index.ts
index b15fe1e6960..97ad1763113 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -516,7 +516,12 @@ export { type VaporInteropInterface } from './apiCreateApp'
/**
* @internal
*/
-export { type RendererInternals, MoveType, invalidateMount } from './renderer'
+export {
+ type RendererInternals,
+ MoveType,
+ getInheritedScopeIds,
+ invalidateMount,
+} from './renderer'
/**
* @internal
*/
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 0cf7c351d0e..9cdc571921c 100644
--- a/packages/runtime-core/src/renderer.ts
+++ b/packages/runtime-core/src/renderer.ts
@@ -777,30 +777,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])
}
}
@@ -2792,3 +2771,54 @@ export 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__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 1beeecf2d08..e3b0c72a63d 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -1899,4 +1899,971 @@ describe('component: slots', () => {
)
})
})
+
+ 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')
+ })
+
+ 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')
+ })
+
+ describe('vdom interop', () => {
+ const createVaporSlot = (fallbackText = 'fallback') => {
+ return defineVaporComponent({
+ setup() {
+ const n0 = createSlot('foo', null, () => {
+ 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 createVaporForwardedSlot = (
+ targetComponent: any,
+ fallbackText?: string,
+ ) => {
+ return defineVaporComponent({
+ setup() {
+ const createForwardedSlot = forwardedSlotCreator()
+ const n2 = createComponent(
+ targetComponent,
+ null,
+ {
+ foo: () => {
+ return fallbackText
+ ? createForwardedSlot('foo', null, () => {
+ const n2 = template(`${fallbackText}
`)()
+ return n2
+ })
+ : createForwardedSlot('foo', null)
+ },
+ },
+ true,
+ )
+ 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 createTestApp = (
+ rootComponent: any,
+ foo: Ref,
+ show: Ref,
+ ) => {
+ return {
+ setup() {
+ return () =>
+ h(
+ rootComponent,
+ null,
+ createSlots({ _: 2 /* DYNAMIC */ } as any, [
+ show.value
+ ? {
+ name: 'foo',
+ fn: () => [h('span', foo.value)],
+ key: '0',
+ }
+ : undefined,
+ ]),
+ )
+ },
+ }
+ }
+
+ 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)
+ 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 = createVaporSlot()
+ const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
+ VaporSlot,
+ 'forwarded fallback',
+ )
+ const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
+
+ 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 = createVdomSlot()
+ const VaporForwardedSlot = createVaporForwardedSlot(VdomSlot)
+ const App = createTestApp(VaporForwardedSlot, foo, show)
+
+ 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 = createVdomSlot()
+ const VaporForwardedSlotWithFallback = createVaporForwardedSlot(
+ VdomSlot,
+ 'forwarded fallback',
+ )
+ const App = createTestApp(VaporForwardedSlotWithFallback, foo, show)
+
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = createVdomSlot()
+
+ 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)
+ 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 = createVdomSlot()
+ const VdomForwardedSlot = createVdomForwardedSlot(VdomSlot)
+ const VaporForwardedSlot = createMultipleVaporForwardedSlots(
+ VdomForwardedSlot,
+ 3,
+ )
+ const App = createTestApp(VaporForwardedSlot, foo, show)
+
+ 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 = 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)
+ 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 = createVaporSlot()
+ const VdomForwardedSlot = createVdomForwardedSlot(VaporSlot)
+ const App = createTestApp(VdomForwardedSlot, foo, show)
+
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = createVdomSlot()
+ const VaporForwardedSlot1 = createMultipleVaporForwardedSlots(
+ VdomSlot,
+ 2,
+ )
+ const App = createTestApp(VaporForwardedSlot1, foo, show)
+
+ 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 = 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)
+ 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 = 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)
+ 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 = 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')
+
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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 = 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)
+ 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/__tests__/scopeId.spec.ts b/packages/runtime-vapor/__tests__/scopeId.spec.ts
new file mode 100644
index 00000000000..a4e878f8329
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/scopeId.spec.ts
@@ -0,0 +1,627 @@
+import { createApp, h } from '@vue/runtime-dom'
+import {
+ createComponent,
+ createDynamicComponent,
+ createSlot,
+ defineVaporComponent,
+ forwardedSlotCreator,
+ 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('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,
+ null,
+ null,
+ undefined,
+ undefined,
+ 'parent',
+ )
+ 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(':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', () => {
+ test('vdom parent > vapor child', () => {
+ const VaporChild = defineVaporComponent({
+ __scopeId: 'vapor-child',
+ setup() {
+ return template('', true)()
+ },
+ })
+
+ const VdomParent = {
+ __scopeId: 'vdom-parent',
+ setup() {
+ return () => h(VaporChild as any)
+ },
+ }
+
+ const App = {
+ setup() {
+ return () => h(VdomParent)
+ },
+ }
+
+ const root = document.createElement('div')
+ createApp(App).use(vaporInteropPlugin).mount(root)
+
+ expect(root.innerHTML).toBe(
+ ``,
+ )
+ })
+
+ test('vdom parent > vapor > 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 VdomParent = {
+ __scopeId: 'vdom-parent',
+ setup() {
+ return () => h(VaporChild as any)
+ },
+ }
+
+ const App = {
+ setup() {
+ return () => h(VdomParent)
+ },
+ }
+
+ const root = document.createElement('div')
+ createApp(App).use(vaporInteropPlugin).mount(root)
+
+ expect(root.innerHTML).toBe(
+ ``,
+ )
+ })
+
+ test('vdom parent > vapor > vapor > vdom child', () => {
+ const VdomChild = {
+ __scopeId: 'vdom-child',
+ setup() {
+ return () => h('button')
+ },
+ }
+
+ const NestedVaporChild = defineVaporComponent({
+ __scopeId: 'nested-vapor-child',
+ setup() {
+ return createComponent(VdomChild as any, null, null, true)
+ },
+ })
+
+ const VaporChild = defineVaporComponent({
+ __scopeId: 'vapor-child',
+ setup() {
+ return createComponent(NestedVaporChild as any, null, null, true)
+ },
+ })
+
+ const VdomParent = {
+ __scopeId: 'vdom-parent',
+ setup() {
+ return () => h(VaporChild as any)
+ },
+ }
+
+ const App = {
+ setup() {
+ return () => h(VdomParent)
+ },
+ }
+
+ 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 VdomParent = {
+ __scopeId: 'vdom-parent',
+ setup() {
+ return () => h(VaporChild as any)
+ },
+ }
+
+ const App = {
+ setup() {
+ return () => h(VdomParent)
+ },
+ }
+
+ 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 VaporParent = defineVaporComponent({
+ __scopeId: 'vapor-parent',
+ setup() {
+ return createComponent(VdomChild as any, null, null, true)
+ },
+ })
+
+ const App = {
+ setup() {
+ return () => h(VaporParent as any)
+ },
+ }
+
+ const root = document.createElement('div')
+ createApp(App).use(vaporInteropPlugin).mount(root)
+
+ expect(root.innerHTML).toBe(
+ ``,
+ )
+ })
+
+ test('vapor parent > vdom > vapor child', () => {
+ const VaporChild = defineVaporComponent({
+ __scopeId: 'vapor-child',
+ setup() {
+ return template('', true)()
+ },
+ })
+
+ const VdomChild = {
+ __scopeId: 'vdom-child',
+ setup() {
+ return () => h(VaporChild as any)
+ },
+ }
+
+ const VaporParent = defineVaporComponent({
+ __scopeId: 'vapor-parent',
+ setup() {
+ return createComponent(VdomChild as any, null, null, true)
+ },
+ })
+
+ const App = {
+ setup() {
+ return () => h(VaporParent as any)
+ },
+ }
+
+ const root = document.createElement('div')
+ createApp(App).use(vaporInteropPlugin).mount(root)
+
+ expect(root.innerHTML).toBe(
+ ``,
+ )
+ })
+
+ test('vapor parent > vdom > vdom > vapor child', () => {
+ const VaporChild = defineVaporComponent({
+ __scopeId: 'vapor-child',
+ setup() {
+ return template('', true)()
+ },
+ })
+
+ const VdomChild = {
+ __scopeId: 'vdom-child',
+ setup() {
+ return () => h(VaporChild as any)
+ },
+ }
+
+ const VdomParent = {
+ __scopeId: 'vdom-parent',
+ setup() {
+ return () => h(VdomChild as any)
+ },
+ }
+
+ const VaporParent = defineVaporComponent({
+ __scopeId: 'vapor-parent',
+ setup() {
+ return createComponent(VdomParent as any, null, null, true)
+ },
+ })
+
+ const App = {
+ setup() {
+ return () => h(VaporParent as any)
+ },
+ }
+
+ const root = document.createElement('div')
+ createApp(App).use(vaporInteropPlugin).mount(root)
+
+ expect(root.innerHTML).toBe(
+ ``,
+ )
+ })
+
+ test.todo('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)
+ },
+ }
+
+ const root = document.createElement('div')
+ 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 89fc6179ee0..b7d9e64079d 100644
--- a/packages/runtime-vapor/src/apiCreateApp.ts
+++ b/packages/runtime-vapor/src/apiCreateApp.ts
@@ -42,6 +42,7 @@ const mountApp: AppMountFn = (app, container) => {
null,
false,
false,
+ undefined,
app._context,
)
mountComponent(instance, container)
@@ -63,6 +64,7 @@ const hydrateApp: AppMountFn = (app, container) => {
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 8f43e865290..8ac11c98171 100644
--- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts
+++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts
@@ -19,6 +19,7 @@ export function createDynamicComponent(
rawSlots?: RawSlots | null,
isSingleRoot?: boolean,
once?: boolean,
+ scopeId?: string,
): VaporFragment {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
@@ -42,6 +43,7 @@ export function createDynamicComponent(
rawSlots,
isSingleRoot,
once,
+ scopeId,
appContext,
),
value,
diff --git a/packages/runtime-vapor/src/apiDefineAsyncComponent.ts b/packages/runtime-vapor/src/apiDefineAsyncComponent.ts
index dd6143950e3..263986e16b4 100644
--- a/packages/runtime-vapor/src/apiDefineAsyncComponent.ts
+++ b/packages/runtime-vapor/src/apiDefineAsyncComponent.ts
@@ -186,6 +186,7 @@ function createInnerComp(
rawSlots,
isSingleRoot,
undefined,
+ undefined,
appContext,
)
diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts
index c4c2f0e188a..aad5840f10e 100644
--- a/packages/runtime-vapor/src/block.ts
+++ b/packages/runtime-vapor/src/block.ts
@@ -11,6 +11,7 @@ import {
type TransitionHooks,
type TransitionProps,
type TransitionState,
+ getInheritedScopeIds,
performTransitionEnter,
performTransitionLeave,
} from '@vue/runtime-dom'
@@ -179,6 +180,43 @@ 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)
+ }
+
+ // 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)
+ }
+ }
+}
export function findBlockNode(block: Block): {
parentNode: Node | null
nextNode: Node | null
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 1952b31048d..bafb042bc12 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -28,7 +28,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,
@@ -165,6 +172,7 @@ export function createComponent(
rawSlots?: LooseRawSlots | null,
isSingleRoot?: boolean,
once?: boolean,
+ scopeId?: string,
appContext: GenericAppContext = (currentInstance &&
currentInstance.appContext) ||
emptyContext,
@@ -611,6 +619,7 @@ export function createComponentWithFallback(
rawSlots?: LooseRawSlots | null,
isSingleRoot?: boolean,
once?: boolean,
+ scopeId?: string,
appContext?: GenericAppContext,
): HTMLElement | VaporComponentInstance {
if (!isString(comp)) {
@@ -620,6 +629,7 @@ export function createComponentWithFallback(
rawSlots,
isSingleRoot,
once,
+ scopeId,
appContext,
)
}
@@ -640,6 +650,11 @@ export function createComponentWithFallback(
// mark single root
;(el as any).$root = isSingleRoot
+ if (!isHydrating) {
+ scopeId = scopeId || currentInstance!.type.__scopeId
+ if (scopeId) setScopeId(el, scopeId)
+ }
+
if (rawProps) {
const setFn = () =>
setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)])
@@ -690,6 +705,7 @@ export function mountComponent(
if (instance.bm) invokeArrayFns(instance.bm)
if (!isHydrating) {
insert(instance.block, parent, anchor)
+ setComponentScopeId(instance)
}
if (instance.m) queuePostFlushCb(instance.m!)
if (
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index aa0651658c0..a0814e5b32d 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -1,5 +1,5 @@
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
-import { type Block, type BlockFn, insert } from './block'
+import { type Block, type BlockFn, insert, setScopeId } from './block'
import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component'
@@ -160,7 +160,13 @@ export function createSlot(
}
}
+ if (i) fragment.forwarded = true
+
if (!isHydrating) {
+ if (i || !hasForwardedSlot(fragment.nodes)) {
+ const scopeId = instance!.type.__scopeId
+ if (scopeId) setScopeId(fragment, `${scopeId}-s`)
+ }
if (_insertionParent) insert(fragment, _insertionParent, _insertionAnchor)
} else {
if (fragment.insert) {
@@ -173,3 +179,15 @@ export function createSlot(
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/fragment.ts b/packages/runtime-vapor/src/fragment.ts
index 07f1243e4e5..dbd986d59eb 100644
--- a/packages/runtime-vapor/src/fragment.ts
+++ b/packages/runtime-vapor/src/fragment.ts
@@ -73,6 +73,7 @@ export class DynamicFragment extends VaporFragment {
current?: BlockFn
fallback?: BlockFn
anchorLabel?: string
+ forwarded?: boolean
constructor(anchorLabel?: string) {
super([])
diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts
index d3cb8d243a8..d6bfb8cf09a 100644
--- a/packages/runtime-vapor/src/vdomInterop.ts
+++ b/packages/runtime-vapor/src/vdomInterop.ts
@@ -274,6 +274,7 @@ function createVDOMComponent(
component: ConcreteComponent,
rawProps?: LooseRawProps | null,
rawSlots?: LooseRawSlots | null,
+ scopeId?: string,
): VaporFragment {
const frag = new VaporFragment([])
const vnode = (frag.vnode = createVNode(
@@ -324,6 +325,8 @@ function createVDOMComponent(
internals.umt(vnode.component!, null, !!parentNode)
}
+ vnode.scopeId = scopeId || parentInstance.type.__scopeId!
+
frag.hydrate = () => {
hydrateVNode(vnode, parentInstance as any)
onScopeDispose(unmount, true)
@@ -375,6 +378,7 @@ function createVDOMComponent(
)
}
+ // update the fragment nodes
frag.nodes = vnode.el as any
simpleSetCurrentInstance(prev)
}