mirror of https://github.com/vuejs/core.git
chore: Merge branch 'edison/feat/setScopeId' into edison/testVapor
This commit is contained in:
commit
6795f5a3e3
|
@ -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),
|
||||
]
|
||||
|
|
|
@ -200,6 +200,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
|
|||
dynamic?: SimpleExpressionNode
|
||||
parent?: number
|
||||
anchor?: number
|
||||
scopeId?: string | null
|
||||
}
|
||||
|
||||
export interface DeclareOldRefIRNode extends BaseIRNode {
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -510,7 +510,11 @@ export { type VaporInteropInterface } from './apiCreateApp'
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export { type RendererInternals, MoveType } from './renderer'
|
||||
export {
|
||||
type RendererInternals,
|
||||
MoveType,
|
||||
getInheritedScopeIds,
|
||||
} from './renderer'
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -215,6 +215,7 @@ function createVDOMComponent(
|
|||
component: ConcreteComponent,
|
||||
rawProps?: LooseRawProps | null,
|
||||
rawSlots?: LooseRawSlots | null,
|
||||
scopeId?: string,
|
||||
): VaporFragment {
|
||||
const frag = new VaporFragment([])
|
||||
const vnode = createVNode(
|
||||
|
|
Loading…
Reference in New Issue