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) }