chore: Merge branch 'edison/feat/setScopeId' into edison/testVapor
ci / test (push) Has been cancelled Details
ci / continuous-release (push) Has been cancelled Details

This commit is contained in:
daiwei 2025-06-20 15:33:00 +08:00
commit 6795f5a3e3
13 changed files with 556 additions and 195 deletions

View File

@ -49,7 +49,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)
@ -80,6 +80,7 @@ export function genCreateComponent(
rawSlots,
root ? 'true' : false,
once && 'true',
scopeId && JSON.stringify(scopeId),
),
...genDirectivesForElement(operation.id, context),
]

View File

@ -200,6 +200,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
dynamic?: SimpleExpressionNode
parent?: number
anchor?: number
scopeId?: string | null
}
export interface DeclareOldRefIRNode extends BaseIRNode {

View File

@ -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 = []

View File

@ -192,7 +192,12 @@ export interface VaporInteropInterface {
): void
hydrate(node: Node, fn: () => void): void
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
vdomMount: (
component: ConcreteComponent,
props?: any,
slots?: any,
scopeId?: string,
) => any
vdomUnmount: UnmountComponentFn
vdomSlot: (
slots: any,

View File

@ -510,7 +510,11 @@ export { type VaporInteropInterface } from './apiCreateApp'
/**
* @internal
*/
export { type RendererInternals, MoveType } from './renderer'
export {
type RendererInternals,
MoveType,
getInheritedScopeIds,
} from './renderer'
/**
* @internal
*/

View File

@ -766,30 +766,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])
}
}
@ -2719,3 +2698,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
}

View File

@ -4,6 +4,7 @@ import {
createDynamicComponent,
createSlot,
defineVaporComponent,
forwardedSlotCreator,
setInsertionState,
template,
vaporInteropPlugin,
@ -21,6 +22,23 @@ describe('scopeId', () => {
},
})
const { html } = define({
__scopeId: 'parent',
setup() {
return createComponent(Child)
},
}).render()
expect(html()).toBe(`<div child="" parent=""></div>`)
})
test('should attach scopeId to child component with insertion state', () => {
const Child = defineVaporComponent({
__scopeId: 'child',
setup() {
return template('<div child></div>', true)()
},
})
const { html } = define({
__scopeId: 'parent',
setup() {
@ -34,7 +52,57 @@ describe('scopeId', () => {
expect(html()).toBe(`<div parent=""><div child="" parent=""></div></div>`)
})
test.todo('should attach scopeId to nested child component', () => {
test('should attach scopeId to nested child component', () => {
const Child = defineVaporComponent({
__scopeId: 'child',
setup() {
return template('<div child></div>', true)()
},
})
const Parent = defineVaporComponent({
__scopeId: 'parent',
setup() {
return createComponent(Child)
},
})
const { html } = define({
__scopeId: 'app',
setup() {
return createComponent(Parent)
},
}).render()
expect(html()).toBe(`<div child="" parent="" app=""></div>`)
})
test('should not attach scopeId to nested multiple root components', () => {
const Child = defineVaporComponent({
__scopeId: 'child',
setup() {
return template('<div child></div>', true)()
},
})
const Parent = defineVaporComponent({
__scopeId: 'parent',
setup() {
const n0 = template('<div parent></div>')()
const n1 = createComponent(Child)
return [n0, n1]
},
})
const { html } = define({
__scopeId: 'app',
setup() {
return createComponent(Parent)
},
}).render()
expect(html()).toBe(`<div parent=""></div><div child="" parent=""></div>`)
})
test('should attach scopeId to nested child component with insertion state', () => {
const Child = defineVaporComponent({
__scopeId: 'child',
setup() {
@ -64,23 +132,17 @@ describe('scopeId', () => {
)
})
test('should attach scopeId to child dynamic component', () => {
test('should attach scopeId to dynamic component', () => {
const { html } = define({
__scopeId: 'parent',
setup() {
const t0 = template('<div parent></div>', true)
const n1 = t0() as any
setInsertionState(n1)
createDynamicComponent(() => 'button')
return n1
return createDynamicComponent(() => 'button')
},
}).render()
expect(html()).toBe(
`<div parent=""><button parent=""></button><!--dynamic-component--></div>`,
)
expect(html()).toBe(`<button parent=""></button><!--dynamic-component-->`)
})
test('should attach scopeId to dynamic component', () => {
test('should attach scopeId to dynamic component with insertion state', () => {
const { html } = define({
__scopeId: 'parent',
setup() {
@ -97,6 +159,24 @@ describe('scopeId', () => {
})
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(
`<button child="" parent=""></button><!--dynamic-component-->`,
)
})
test('should attach scopeId to nested dynamic component with insertion state', () => {
const Comp = defineVaporComponent({
__scopeId: 'child',
setup() {
@ -121,7 +201,7 @@ describe('scopeId', () => {
test.todo('should attach scopeId to suspense content', async () => {})
// :slotted basic
test.todo('should work on slots', () => {
test('should work on slots', () => {
const Child = defineVaporComponent({
__scopeId: 'child',
setup() {
@ -148,7 +228,14 @@ describe('scopeId', () => {
{
default: () => {
const n0 = template('<div parent></div>')()
const n1 = createComponent(Child2)
const n1 = createComponent(
Child2,
null,
null,
undefined,
undefined,
'parent',
)
return [n0, n1]
},
},
@ -165,13 +252,69 @@ describe('scopeId', () => {
// - scopeId from template context
// - slotted scopeId from slot owner
// - its own scopeId
`<span child2="" child="" parent="" child-s=""></span>` +
`<span child2="" parent="" child-s="" child=""></span>` +
`<!--slot-->` +
`</div>`,
)
})
test.todo(':slotted on forwarded slots', async () => {})
test(':slotted on forwarded slots', async () => {
const Wrapper = defineVaporComponent({
__scopeId: 'wrapper',
setup() {
// <div><slot/></div>
const n1 = template('<div wrapper></div>', true)() as any
setInsertionState(n1)
createSlot('default', null)
return n1
},
})
const Slotted = defineVaporComponent({
__scopeId: 'slotted',
setup() {
// <Wrapper><slot/></Wrapper>
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() {
// <Slotted><div></div></Slotted>
const n2 = createComponent(
Slotted,
null,
{
default: () => {
return template('<div root></div>')()
},
},
true,
)
return n2
},
}).render()
expect(html()).toBe(
`<div wrapper="" slotted="" root="">` +
`<div root="" slotted-s=""></div>` +
`<!--slot--><!--slot-->` +
`</div>`,
)
})
})
describe('vdom interop', () => {
@ -183,6 +326,172 @@ describe('vdom interop', () => {
},
})
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(
`<button vapor-child="" vdom-parent=""></button>`,
)
})
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(
`<button vdom-child="" vapor-child="" vdom-parent=""></button>`,
)
})
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(
`<button vdom-child="" nested-vapor-child="" vapor-child="" vdom-parent=""></button>`,
)
})
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(
`<button vapor-child="" vdom-parent=""></button><!--dynamic-component-->`,
)
})
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(
`<button vdom-child="" vapor-parent=""></button>`,
)
})
test('vapor parent > vdom > vapor child', () => {
const VaporChild = defineVaporComponent({
__scopeId: 'vapor-child',
setup() {
return template('<button vapor-child></button>', true)()
},
})
const VdomChild = {
__scopeId: 'vdom-child',
setup() {
@ -190,107 +499,16 @@ describe('vdom interop', () => {
},
}
const App = {
__scopeId: 'parent',
setup() {
return () => h(VdomChild)
},
}
const root = document.createElement('div')
createApp(App).use(vaporInteropPlugin).mount(root)
expect(root.innerHTML).toBe(
`<button vapor-child="" vdom-child="" parent=""></button>`,
)
})
test('vdom parent > vapor > vdom child', () => {
const InnerVdomChild = {
__scopeId: 'inner-vdom-child',
setup() {
return () => h('button')
},
}
const VaporChild = defineVaporComponent({
__scopeId: 'vapor-child',
setup() {
return createComponent(InnerVdomChild as any, null, null, true)
},
})
const VdomChild = {
__scopeId: 'vdom-child',
setup() {
return () => h(VaporChild as any)
},
}
const App = {
__scopeId: 'parent',
setup() {
return () => h(VdomChild)
},
}
const root = document.createElement('div')
createApp(App).use(vaporInteropPlugin).mount(root)
expect(root.innerHTML).toBe(
`<button inner-vdom-child="" vapor-child="" vdom-child="" parent=""></button>`,
)
})
test('vdom parent > vapor dynamic child', () => {
const VaporChild = defineVaporComponent({
__scopeId: 'vapor-child',
setup() {
return createDynamicComponent(() => 'button', null, null, true)
},
})
const VdomChild = {
__scopeId: 'vdom-child',
setup() {
return () => h(VaporChild as any)
},
}
const App = {
__scopeId: 'parent',
setup() {
return () => h(VdomChild)
},
}
const root = document.createElement('div')
createApp(App).use(vaporInteropPlugin).mount(root)
expect(root.innerHTML).toBe(
`<button vapor-child="" vdom-child="" parent=""></button><!--dynamic-component-->`,
)
})
test('vapor parent > vdom child', () => {
const VdomChild = {
__scopeId: 'vdom-child',
setup() {
return () => h('button')
},
}
const VaporChild = defineVaporComponent({
__scopeId: 'vapor-child',
const VaporParent = defineVaporComponent({
__scopeId: 'vapor-parent',
setup() {
return createComponent(VdomChild as any, null, null, true)
},
})
const App = {
__scopeId: 'parent',
setup() {
return () => h(VaporChild as any)
return () => h(VaporParent as any)
},
}
@ -298,36 +516,42 @@ describe('vdom interop', () => {
createApp(App).use(vaporInteropPlugin).mount(root)
expect(root.innerHTML).toBe(
`<button vdom-child="" vapor-child="" parent=""></button>`,
`<button vapor-child="" vdom-child="" vapor-parent=""></button>`,
)
})
test('vapor parent > vdom > vapor child', () => {
const InnerVaporChild = defineVaporComponent({
__scopeId: 'inner-vapor-child',
test('vapor parent > vdom > vdom > vapor child', () => {
const VaporChild = defineVaporComponent({
__scopeId: 'vapor-child',
setup() {
return template('<button inner-vapor-child></button>', true)()
return template('<button vapor-child></button>', true)()
},
})
const VdomChild = {
__scopeId: 'vdom-child',
setup() {
return () => h(InnerVaporChild as any)
return () => h(VaporChild as any)
},
}
const VaporChild = defineVaporComponent({
__scopeId: 'vapor-child',
const VdomParent = {
__scopeId: 'vdom-parent',
setup() {
return createComponent(VdomChild as any, null, null, true)
return () => h(VdomChild as any)
},
}
const VaporParent = defineVaporComponent({
__scopeId: 'vapor-parent',
setup() {
return createComponent(VdomParent as any, null, null, true)
},
})
const App = {
__scopeId: 'parent',
setup() {
return () => h(VaporChild as any)
return () => h(VaporParent as any)
},
}
@ -335,7 +559,69 @@ describe('vdom interop', () => {
createApp(App).use(vaporInteropPlugin).mount(root)
expect(root.innerHTML).toBe(
`<button inner-vapor-child="" vdom-child="" vapor-child="" parent=""></button>`,
`<button vapor-child="" vdom-child="" vdom-parent="" vapor-parent=""></button>`,
)
})
test.todo('vapor parent > vapor slot > vdom child', () => {
const VaporSlot = defineVaporComponent({
__scopeId: 'vapor-slot',
setup() {
const n1 = template('<div vapor-slot></div>', 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('<div vapor-parent></div>')()
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(
`<div vapor-slot="" vapor-parent="">` +
`<div vapor-parent="" vapor-slot-s=""></div>` +
`<span vdom-child="" vapor-parent="" vapor-slot-s=""></span>` +
`<!--slot-->` +
`</div>`,
)
})
})

View File

@ -41,6 +41,8 @@ const mountApp: AppMountFn<ParentNode> = (app, container) => {
app._props as RawProps,
null,
false,
false,
undefined,
app._context,
)
mountComponent(instance, container)
@ -61,6 +63,8 @@ const hydrateApp: AppMountFn<ParentNode> = (app, container) => {
app._props as RawProps,
null,
false,
false,
undefined,
app._context,
)
mountComponent(instance, container)

View File

@ -17,6 +17,8 @@ export function createDynamicComponent(
rawProps?: RawProps | null,
rawSlots?: RawSlots | null,
isSingleRoot?: boolean,
once?: boolean,
scopeId?: string,
): VaporFragment {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
@ -42,6 +44,8 @@ export function createDynamicComponent(
rawProps,
rawSlots,
isSingleRoot,
once,
scopeId,
appContext,
),
value,

View File

@ -1,7 +1,6 @@
import { isArray } from '@vue/shared'
import {
type VaporComponentInstance,
currentInstance,
isVaporComponent,
mountComponent,
unmountComponent,
@ -11,11 +10,9 @@ import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
import {
currentHydrationNode,
isComment,
isHydrating,
locateHydrationNode,
locateVaporFragmentAnchor,
} from './dom/hydration'
import { queuePostFlushCb } from '@vue/runtime-dom'
import {
type TransitionHooks,
type TransitionProps,
@ -32,6 +29,8 @@ export interface TransitionOptions {
$key?: any
$transition?: VaporTransitionHooks
}
import { isHydrating } from './dom/hydration'
import { getInheritedScopeIds } from '@vue/runtime-dom'
export interface VaporTransitionHooks extends TransitionHooks {
state: TransitionState
@ -73,6 +72,12 @@ export class DynamicFragment extends VaporFragment {
anchor!: Node
scope: EffectScope | undefined
current?: BlockFn
fallback?: BlockFn
/**
* slot only
* indicates forwarded slot
*/
forwarded?: boolean
constructor(anchorLabel?: string) {
super([])
@ -290,56 +295,40 @@ export function normalizeBlock(block: Block): Node[] {
return nodes
}
export function setScopeId(block: Block, scopeId?: string): void {
if (block instanceof Node) {
if (scopeId && block instanceof Element) {
block.setAttribute(scopeId, '')
}
export function setScopeId(block: Block, scopeId: string): void {
if (block instanceof Element) {
block.setAttribute(scopeId, '')
} else if (isVaporComponent(block)) {
setComponentScopeId(block, scopeId, true)
setScopeId(block.block, scopeId)
} else if (isArray(block)) {
for (const b of block) {
setScopeId(b, scopeId)
}
} else {
} else if (isFragment(block)) {
setScopeId(block.nodes, scopeId)
}
}
export function setComponentScopeId(
instance: VaporComponentInstance,
scopeId: string | undefined = currentInstance
? currentInstance.type.__scopeId
: undefined,
immediate: boolean = false,
): void {
function doSet() {
if (scopeId) {
setScopeId(instance.block, scopeId)
}
// inherit scopeId from parent component. this requires initial rendering
// to be finished, due to `parent.block` is null during initial rendering
const parent = instance.parent
if (parent && parent.type.__scopeId) {
// vapor parent
if (
parent.vapor &&
(parent as VaporComponentInstance).block === instance
) {
setScopeId(instance.block, parent.type.__scopeId)
}
// vdom parent
else if (
parent.subTree &&
(parent.subTree.component as any) === instance
) {
setScopeId(instance.block, parent.vnode!.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)
}
if (immediate) {
doSet()
} else {
queuePostFlushCb(doSet)
// 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)
}
}
}

View File

@ -147,6 +147,8 @@ export function createComponent(
rawProps?: LooseRawProps | null,
rawSlots?: LooseRawSlots | null,
isSingleRoot?: boolean,
once?: boolean, // TODO once support
scopeId?: string,
appContext: GenericAppContext = (currentInstance &&
currentInstance.appContext) ||
emptyContext,
@ -165,6 +167,7 @@ export function createComponent(
component as any,
rawProps,
rawSlots,
scopeId,
)
// `frag.insert` handles both hydration and mounting
@ -284,10 +287,11 @@ export function createComponent(
onScopeDispose(() => unmountComponent(instance), true)
if (scopeId) setScopeId(instance.block, scopeId)
if (!isHydrating && _insertionParent) {
mountComponent(instance, _insertionParent, _insertionAnchor)
}
return instance
}
@ -489,10 +493,20 @@ export function createComponentWithFallback(
rawProps?: LooseRawProps | null,
rawSlots?: LooseRawSlots | null,
isSingleRoot?: boolean,
once?: boolean,
scopeId?: string,
appContext?: GenericAppContext,
): HTMLElement | VaporComponentInstance {
if (!isString(comp)) {
return createComponent(comp, rawProps, rawSlots, isSingleRoot, appContext)
return createComponent(
comp,
rawProps,
rawSlots,
isSingleRoot,
once,
scopeId,
appContext,
)
}
const _insertionParent = insertionParent
@ -507,7 +521,9 @@ export function createComponentWithFallback(
// mark single root
;(el as any).$root = isSingleRoot
setScopeId(el, currentInstance ? currentInstance.type.__scopeId : undefined)
scopeId = scopeId || currentInstance!.type.__scopeId
if (scopeId) setScopeId(el, scopeId)
if (rawProps) {
renderEffect(() => {
setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)])

View File

@ -13,6 +13,7 @@ import {
type VaporFragment,
insert,
isFragment,
setScopeId,
} from './block'
import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom'
@ -197,6 +198,12 @@ export function createSlot(
}
}
if (i) fragment.forwarded = true
if (i || !hasForwardedSlot(fragment.nodes)) {
const scopeId = instance!.type.__scopeId
if (scopeId) setScopeId(fragment, `${scopeId}-s`)
}
if (
_insertionParent &&
(!isHydrating ||
@ -209,6 +216,18 @@ 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)
}
}
function ensureVaporSlotFallback(
block: VaporFragment,
fallback?: VaporSlot,

View File

@ -215,6 +215,7 @@ function createVDOMComponent(
component: ConcreteComponent,
rawProps?: LooseRawProps | null,
rawSlots?: LooseRawSlots | null,
scopeId?: string,
): VaporFragment {
const frag = new VaporFragment([])
const vnode = createVNode(