mirror of https://github.com/vuejs/core.git
chore: Merge branch 'edison/feat/setScopeId' into edison/testVapor
This commit is contained in:
commit
d0d5c3e6d1
|
@ -0,0 +1,341 @@
|
|||
import { createApp, h } from '@vue/runtime-dom'
|
||||
import {
|
||||
createComponent,
|
||||
createDynamicComponent,
|
||||
createSlot,
|
||||
defineVaporComponent,
|
||||
setInsertionState,
|
||||
template,
|
||||
vaporInteropPlugin,
|
||||
} from '../src'
|
||||
import { makeRender } from './_utils'
|
||||
|
||||
const define = makeRender()
|
||||
|
||||
describe('scopeId', () => {
|
||||
test('should attach scopeId to child component', () => {
|
||||
const Child = defineVaporComponent({
|
||||
__scopeId: 'child',
|
||||
setup() {
|
||||
return template('<div child></div>', true)()
|
||||
},
|
||||
})
|
||||
|
||||
const { html } = define({
|
||||
__scopeId: 'parent',
|
||||
setup() {
|
||||
const t0 = template('<div parent></div>', true)
|
||||
const n1 = t0() as any
|
||||
setInsertionState(n1)
|
||||
createComponent(Child)
|
||||
return n1
|
||||
},
|
||||
}).render()
|
||||
expect(html()).toBe(`<div parent=""><div child="" parent=""></div></div>`)
|
||||
})
|
||||
|
||||
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() {
|
||||
const t0 = template('<div app></div>', true)
|
||||
const n1 = t0() as any
|
||||
setInsertionState(n1)
|
||||
createComponent(Parent)
|
||||
return n1
|
||||
},
|
||||
}).render()
|
||||
expect(html()).toBe(
|
||||
`<div app=""><div child="" parent="" app=""></div></div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('should attach scopeId to child 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
|
||||
},
|
||||
}).render()
|
||||
expect(html()).toBe(
|
||||
`<div parent=""><button parent=""></button><!--dynamic-component--></div>`,
|
||||
)
|
||||
})
|
||||
|
||||
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
|
||||
},
|
||||
}).render()
|
||||
expect(html()).toBe(
|
||||
`<div parent=""><button parent=""></button><!--dynamic-component--></div>`,
|
||||
)
|
||||
})
|
||||
|
||||
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() {
|
||||
const t0 = template('<div parent></div>', true)
|
||||
const n1 = t0() as any
|
||||
setInsertionState(n1)
|
||||
createComponent(Comp, null, null, true)
|
||||
return n1
|
||||
},
|
||||
}).render()
|
||||
expect(html()).toBe(
|
||||
`<div parent=""><button child="" parent=""></button><!--dynamic-component--></div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test.todo('should attach scopeId to suspense content', async () => {})
|
||||
|
||||
// :slotted basic
|
||||
test.todo('should work on slots', () => {
|
||||
const Child = defineVaporComponent({
|
||||
__scopeId: 'child',
|
||||
setup() {
|
||||
const n1 = template('<div child></div>', true)() as any
|
||||
setInsertionState(n1)
|
||||
createSlot('default', null)
|
||||
return n1
|
||||
},
|
||||
})
|
||||
|
||||
const Child2 = defineVaporComponent({
|
||||
__scopeId: 'child2',
|
||||
setup() {
|
||||
return template('<span child2></span>', true)()
|
||||
},
|
||||
})
|
||||
|
||||
const { html } = define({
|
||||
__scopeId: 'parent',
|
||||
setup() {
|
||||
const n2 = createComponent(
|
||||
Child,
|
||||
null,
|
||||
{
|
||||
default: () => {
|
||||
const n0 = template('<div parent></div>')()
|
||||
const n1 = createComponent(Child2)
|
||||
return [n0, n1]
|
||||
},
|
||||
},
|
||||
true,
|
||||
)
|
||||
return n2
|
||||
},
|
||||
}).render()
|
||||
|
||||
expect(html()).toBe(
|
||||
`<div child="" parent="">` +
|
||||
`<div parent="" child-s=""></div>` +
|
||||
// component inside slot should have:
|
||||
// - scopeId from template context
|
||||
// - slotted scopeId from slot owner
|
||||
// - its own scopeId
|
||||
`<span child2="" child="" parent="" child-s=""></span>` +
|
||||
`<!--slot-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
test.todo(':slotted on forwarded slots', async () => {})
|
||||
})
|
||||
|
||||
describe('vdom interop', () => {
|
||||
test('vdom parent > vapor child', () => {
|
||||
const VaporChild = defineVaporComponent({
|
||||
__scopeId: 'vapor-child',
|
||||
setup() {
|
||||
return template('<button vapor-child></button>', 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>`,
|
||||
)
|
||||
})
|
||||
|
||||
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',
|
||||
setup() {
|
||||
return createComponent(VdomChild as any, null, null, true)
|
||||
},
|
||||
})
|
||||
|
||||
const App = {
|
||||
__scopeId: 'parent',
|
||||
setup() {
|
||||
return () => h(VaporChild as any)
|
||||
},
|
||||
}
|
||||
|
||||
const root = document.createElement('div')
|
||||
createApp(App).use(vaporInteropPlugin).mount(root)
|
||||
|
||||
expect(root.innerHTML).toBe(
|
||||
`<button vdom-child="" vapor-child="" parent=""></button>`,
|
||||
)
|
||||
})
|
||||
|
||||
test('vapor parent > vdom > vapor child', () => {
|
||||
const InnerVaporChild = defineVaporComponent({
|
||||
__scopeId: 'inner-vapor-child',
|
||||
setup() {
|
||||
return template('<button inner-vapor-child></button>', true)()
|
||||
},
|
||||
})
|
||||
|
||||
const VdomChild = {
|
||||
__scopeId: 'vdom-child',
|
||||
setup() {
|
||||
return () => h(InnerVaporChild as any)
|
||||
},
|
||||
}
|
||||
|
||||
const VaporChild = defineVaporComponent({
|
||||
__scopeId: 'vapor-child',
|
||||
setup() {
|
||||
return createComponent(VdomChild as any, null, null, true)
|
||||
},
|
||||
})
|
||||
|
||||
const App = {
|
||||
__scopeId: 'parent',
|
||||
setup() {
|
||||
return () => h(VaporChild as any)
|
||||
},
|
||||
}
|
||||
|
||||
const root = document.createElement('div')
|
||||
createApp(App).use(vaporInteropPlugin).mount(root)
|
||||
|
||||
expect(root.innerHTML).toBe(
|
||||
`<button inner-vapor-child="" vdom-child="" vapor-child="" parent=""></button>`,
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,6 +1,7 @@
|
|||
import { isArray } from '@vue/shared'
|
||||
import {
|
||||
type VaporComponentInstance,
|
||||
currentInstance,
|
||||
isVaporComponent,
|
||||
mountComponent,
|
||||
unmountComponent,
|
||||
|
@ -14,6 +15,7 @@ import {
|
|||
locateHydrationNode,
|
||||
locateVaporFragmentAnchor,
|
||||
} from './dom/hydration'
|
||||
import { queuePostFlushCb } from '@vue/runtime-dom'
|
||||
|
||||
export type Block =
|
||||
| Node
|
||||
|
@ -213,3 +215,57 @@ 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, '')
|
||||
}
|
||||
} else if (isVaporComponent(block)) {
|
||||
setComponentScopeId(block, scopeId, true)
|
||||
} else if (isArray(block)) {
|
||||
for (const b of block) {
|
||||
setScopeId(b, scopeId)
|
||||
}
|
||||
} else {
|
||||
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!)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (immediate) {
|
||||
doSet()
|
||||
} else {
|
||||
queuePostFlushCb(doSet)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,14 @@ import {
|
|||
unregisterHMR,
|
||||
warn,
|
||||
} from '@vue/runtime-dom'
|
||||
import { type Block, insert, isBlock, remove } from './block'
|
||||
import {
|
||||
type Block,
|
||||
insert,
|
||||
isBlock,
|
||||
remove,
|
||||
setComponentScopeId,
|
||||
setScopeId,
|
||||
} from './block'
|
||||
import {
|
||||
type ShallowRef,
|
||||
markRaw,
|
||||
|
@ -482,6 +489,7 @@ export function createComponentWithFallback(
|
|||
// mark single root
|
||||
;(el as any).$root = isSingleRoot
|
||||
|
||||
setScopeId(el, currentInstance ? currentInstance.type.__scopeId : undefined)
|
||||
if (rawProps) {
|
||||
renderEffect(() => {
|
||||
setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)])
|
||||
|
@ -509,6 +517,7 @@ export function mountComponent(
|
|||
}
|
||||
if (instance.bm) invokeArrayFns(instance.bm)
|
||||
insert(instance.block, parent, anchor)
|
||||
setComponentScopeId(instance)
|
||||
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
|
||||
instance.isMounted = true
|
||||
if (__DEV__) {
|
||||
|
|
|
@ -77,6 +77,7 @@ const vaporInteropImpl: Omit<
|
|||
instance.rawPropsRef = propsRef
|
||||
instance.rawSlotsRef = slotsRef
|
||||
mountComponent(instance, container, selfAnchor)
|
||||
vnode.el = instance.block
|
||||
simpleSetCurrentInstance(prev)
|
||||
return instance
|
||||
},
|
||||
|
@ -203,6 +204,8 @@ function createVDOMComponent(
|
|||
internals.umt(vnode.component!, null, !!parentNode)
|
||||
}
|
||||
|
||||
vnode.scopeId = parentInstance.type.__scopeId!
|
||||
|
||||
frag.insert = (parentNode, anchor) => {
|
||||
if (!isMounted || isHydrating) {
|
||||
if (isHydrating) {
|
||||
|
@ -240,6 +243,9 @@ function createVDOMComponent(
|
|||
parentInstance as any,
|
||||
)
|
||||
}
|
||||
|
||||
// update the fragment nodes
|
||||
frag.nodes = vnode.el as Block
|
||||
}
|
||||
|
||||
frag.remove = unmount
|
||||
|
|
Loading…
Reference in New Issue