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 { isArray } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
type VaporComponentInstance,
|
type VaporComponentInstance,
|
||||||
|
currentInstance,
|
||||||
isVaporComponent,
|
isVaporComponent,
|
||||||
mountComponent,
|
mountComponent,
|
||||||
unmountComponent,
|
unmountComponent,
|
||||||
|
@ -14,6 +15,7 @@ import {
|
||||||
locateHydrationNode,
|
locateHydrationNode,
|
||||||
locateVaporFragmentAnchor,
|
locateVaporFragmentAnchor,
|
||||||
} from './dom/hydration'
|
} from './dom/hydration'
|
||||||
|
import { queuePostFlushCb } from '@vue/runtime-dom'
|
||||||
|
|
||||||
export type Block =
|
export type Block =
|
||||||
| Node
|
| Node
|
||||||
|
@ -213,3 +215,57 @@ export function normalizeBlock(block: Block): Node[] {
|
||||||
}
|
}
|
||||||
return nodes
|
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,
|
unregisterHMR,
|
||||||
warn,
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { type Block, insert, isBlock, remove } from './block'
|
import {
|
||||||
|
type Block,
|
||||||
|
insert,
|
||||||
|
isBlock,
|
||||||
|
remove,
|
||||||
|
setComponentScopeId,
|
||||||
|
setScopeId,
|
||||||
|
} from './block'
|
||||||
import {
|
import {
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
markRaw,
|
markRaw,
|
||||||
|
@ -482,6 +489,7 @@ export function createComponentWithFallback(
|
||||||
// mark single root
|
// mark single root
|
||||||
;(el as any).$root = isSingleRoot
|
;(el as any).$root = isSingleRoot
|
||||||
|
|
||||||
|
setScopeId(el, currentInstance ? currentInstance.type.__scopeId : undefined)
|
||||||
if (rawProps) {
|
if (rawProps) {
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)])
|
setDynamicProps(el, [resolveDynamicProps(rawProps as RawProps)])
|
||||||
|
@ -509,6 +517,7 @@ export function mountComponent(
|
||||||
}
|
}
|
||||||
if (instance.bm) invokeArrayFns(instance.bm)
|
if (instance.bm) invokeArrayFns(instance.bm)
|
||||||
insert(instance.block, parent, anchor)
|
insert(instance.block, parent, anchor)
|
||||||
|
setComponentScopeId(instance)
|
||||||
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
|
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
|
||||||
instance.isMounted = true
|
instance.isMounted = true
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
|
|
@ -77,6 +77,7 @@ const vaporInteropImpl: Omit<
|
||||||
instance.rawPropsRef = propsRef
|
instance.rawPropsRef = propsRef
|
||||||
instance.rawSlotsRef = slotsRef
|
instance.rawSlotsRef = slotsRef
|
||||||
mountComponent(instance, container, selfAnchor)
|
mountComponent(instance, container, selfAnchor)
|
||||||
|
vnode.el = instance.block
|
||||||
simpleSetCurrentInstance(prev)
|
simpleSetCurrentInstance(prev)
|
||||||
return instance
|
return instance
|
||||||
},
|
},
|
||||||
|
@ -203,6 +204,8 @@ function createVDOMComponent(
|
||||||
internals.umt(vnode.component!, null, !!parentNode)
|
internals.umt(vnode.component!, null, !!parentNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vnode.scopeId = parentInstance.type.__scopeId!
|
||||||
|
|
||||||
frag.insert = (parentNode, anchor) => {
|
frag.insert = (parentNode, anchor) => {
|
||||||
if (!isMounted || isHydrating) {
|
if (!isMounted || isHydrating) {
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
|
@ -240,6 +243,9 @@ function createVDOMComponent(
|
||||||
parentInstance as any,
|
parentInstance as any,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update the fragment nodes
|
||||||
|
frag.nodes = vnode.el as Block
|
||||||
}
|
}
|
||||||
|
|
||||||
frag.remove = unmount
|
frag.remove = unmount
|
||||||
|
|
Loading…
Reference in New Issue