mirror of https://github.com/vuejs/core.git
wip: vdom interop
This commit is contained in:
parent
d281d62312
commit
ea34f2f555
|
@ -33,7 +33,6 @@ import type { NormalizedPropsOptions } from './componentProps'
|
||||||
import type { ObjectEmitsOptions } from './componentEmits'
|
import type { ObjectEmitsOptions } from './componentEmits'
|
||||||
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||||
import type { DefineComponent } from './apiDefineComponent'
|
import type { DefineComponent } from './apiDefineComponent'
|
||||||
import type { createHydrationFunctions } from './hydration'
|
|
||||||
|
|
||||||
export interface App<HostElement = any> {
|
export interface App<HostElement = any> {
|
||||||
version: string
|
version: string
|
||||||
|
@ -105,7 +104,6 @@ export interface App<HostElement = any> {
|
||||||
_container: HostElement | null
|
_container: HostElement | null
|
||||||
_context: AppContext
|
_context: AppContext
|
||||||
_instance: GenericComponentInstance | null
|
_instance: GenericComponentInstance | null
|
||||||
_ssr?: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal custom element vnode
|
* @internal custom element vnode
|
||||||
|
@ -206,7 +204,6 @@ export interface VaporInteropInterface {
|
||||||
parentComponent: any, // VaporComponentInstance
|
parentComponent: any, // VaporComponentInstance
|
||||||
fallback?: any, // VaporSlot
|
fallback?: any, // VaporSlot
|
||||||
) => any
|
) => any
|
||||||
vdomHydrate: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -149,7 +149,6 @@ export const createApp = ((...args) => {
|
||||||
|
|
||||||
export const createSSRApp = ((...args) => {
|
export const createSSRApp = ((...args) => {
|
||||||
const app = ensureHydrationRenderer().createApp(...args)
|
const app = ensureHydrationRenderer().createApp(...args)
|
||||||
app._ssr = true
|
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
injectNativeTagCheck(app)
|
injectNativeTagCheck(app)
|
||||||
|
|
|
@ -54,6 +54,14 @@ function compile(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testHydrationInterop(
|
||||||
|
code: string,
|
||||||
|
components?: Record<string, string | { code: string; vapor: boolean }>,
|
||||||
|
data?: any,
|
||||||
|
) {
|
||||||
|
return testHydration(code, components, data, { interop: true, vapor: false })
|
||||||
|
}
|
||||||
|
|
||||||
async function testHydration(
|
async function testHydration(
|
||||||
code: string,
|
code: string,
|
||||||
components: Record<string, string | { code: string; vapor: boolean }> = {},
|
components: Record<string, string | { code: string; vapor: boolean }> = {},
|
||||||
|
@ -65,7 +73,7 @@ async function testHydration(
|
||||||
for (const key in components) {
|
for (const key in components) {
|
||||||
const comp = components[key]
|
const comp = components[key]
|
||||||
const code = isString(comp) ? comp : comp.code
|
const code = isString(comp) ? comp : comp.code
|
||||||
const isVaporComp = !isString(comp) ? comp.vapor : true
|
const isVaporComp = isString(comp) || !!comp.vapor
|
||||||
clientComponents[key] = compile(code, data, clientComponents, {
|
clientComponents[key] = compile(code, data, clientComponents, {
|
||||||
vapor: isVaporComp,
|
vapor: isVaporComp,
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
@ -3838,9 +3846,9 @@ describe('Vapor Mode hydration', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('VDOM hydration interop', () => {
|
describe('VDOM hydration interop', () => {
|
||||||
test('basic component', async () => {
|
test('basic vapor component', async () => {
|
||||||
const data = ref(true)
|
const data = ref(true)
|
||||||
const { container } = await testHydration(
|
const { container } = await testHydrationInterop(
|
||||||
`<script setup>const data = _data; const components = _components;</script>
|
`<script setup>const data = _data; const components = _components;</script>
|
||||||
<template>
|
<template>
|
||||||
<components.VaporChild/>
|
<components.VaporChild/>
|
||||||
|
@ -3852,7 +3860,6 @@ describe('VDOM hydration interop', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
{ interop: true, vapor: false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(`"true"`)
|
expect(container.innerHTML).toMatchInlineSnapshot(`"true"`)
|
||||||
|
@ -3864,7 +3871,7 @@ describe('VDOM hydration interop', () => {
|
||||||
|
|
||||||
test('nested components (VDOM -> Vapor -> VDOM)', async () => {
|
test('nested components (VDOM -> Vapor -> VDOM)', async () => {
|
||||||
const data = ref(true)
|
const data = ref(true)
|
||||||
const { container } = await testHydration(
|
const { container } = await testHydrationInterop(
|
||||||
`<script setup>const data = _data; const components = _components;</script>
|
`<script setup>const data = _data; const components = _components;</script>
|
||||||
<template>
|
<template>
|
||||||
<components.VaporChild/>
|
<components.VaporChild/>
|
||||||
|
@ -3881,7 +3888,6 @@ describe('VDOM hydration interop', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
{ interop: true, vapor: false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(`"true"`)
|
expect(container.innerHTML).toMatchInlineSnapshot(`"true"`)
|
||||||
|
@ -3891,9 +3897,9 @@ describe('VDOM hydration interop', () => {
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
|
expect(container.innerHTML).toMatchInlineSnapshot(`"false"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('slots', async () => {
|
test('vapor slot render vdom component', async () => {
|
||||||
const data = ref(true)
|
const data = ref(true)
|
||||||
const { container } = await testHydration(
|
const { container } = await testHydrationInterop(
|
||||||
`<script setup>const data = _data; const components = _components;</script>
|
`<script setup>const data = _data; const components = _components;</script>
|
||||||
<template>
|
<template>
|
||||||
<components.VaporChild>
|
<components.VaporChild>
|
||||||
|
@ -3912,7 +3918,6 @@ describe('VDOM hydration interop', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data,
|
data,
|
||||||
{ interop: true, vapor: false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||||
|
|
|
@ -154,7 +154,6 @@ export function insert(
|
||||||
} else {
|
} else {
|
||||||
// fragment
|
// fragment
|
||||||
if (block.insert) {
|
if (block.insert) {
|
||||||
// TODO handle hydration for vdom interop
|
|
||||||
block.insert(parent, anchor)
|
block.insert(parent, anchor)
|
||||||
} else {
|
} else {
|
||||||
insert(block.nodes, parent, anchor)
|
insert(block.nodes, parent, anchor)
|
||||||
|
|
|
@ -58,11 +58,7 @@ import {
|
||||||
getSlot,
|
getSlot,
|
||||||
} from './componentSlots'
|
} from './componentSlots'
|
||||||
import { hmrReload, hmrRerender } from './hmr'
|
import { hmrReload, hmrRerender } from './hmr'
|
||||||
import {
|
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||||
currentHydrationNode,
|
|
||||||
isHydrating,
|
|
||||||
locateHydrationNode,
|
|
||||||
} from './dom/hydration'
|
|
||||||
import {
|
import {
|
||||||
insertionAnchor,
|
insertionAnchor,
|
||||||
insertionParent,
|
insertionParent,
|
||||||
|
@ -156,22 +152,15 @@ export function createComponent(
|
||||||
|
|
||||||
// vdom interop enabled and component is not an explicit vapor component
|
// vdom interop enabled and component is not an explicit vapor component
|
||||||
if (appContext.vapor && !component.__vapor) {
|
if (appContext.vapor && !component.__vapor) {
|
||||||
const [frag, vnode] = appContext.vapor.vdomMount(
|
const frag = appContext.vapor.vdomMount(
|
||||||
component as any,
|
component as any,
|
||||||
rawProps,
|
rawProps,
|
||||||
rawSlots,
|
rawSlots,
|
||||||
)
|
)
|
||||||
if (!isHydrating && _insertionParent) {
|
|
||||||
|
// `frag.insert` handles both hydration and mounting
|
||||||
|
if (_insertionParent) {
|
||||||
insert(frag, _insertionParent, _insertionAnchor)
|
insert(frag, _insertionParent, _insertionAnchor)
|
||||||
} else if (isHydrating) {
|
|
||||||
appContext.vapor.vdomHydrate!(
|
|
||||||
currentHydrationNode!,
|
|
||||||
vnode,
|
|
||||||
currentInstance as any,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return frag
|
return frag
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,6 @@ export function createSlot(
|
||||||
: EMPTY_OBJ
|
: EMPTY_OBJ
|
||||||
|
|
||||||
let fragment: DynamicFragment
|
let fragment: DynamicFragment
|
||||||
|
|
||||||
if (isRef(rawSlots._)) {
|
if (isRef(rawSlots._)) {
|
||||||
fragment = instance.appContext.vapor!.vdomSlot(
|
fragment = instance.appContext.vapor!.vdomSlot(
|
||||||
rawSlots._,
|
rawSlots._,
|
||||||
|
@ -157,7 +156,12 @@ export function createSlot(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHydrating && _insertionParent) {
|
if (
|
||||||
|
_insertionParent &&
|
||||||
|
(!isHydrating ||
|
||||||
|
// for vdom interop fragment, `fragment.insert` handles both hydration and mounting
|
||||||
|
fragment.insert)
|
||||||
|
) {
|
||||||
insert(fragment, _insertionParent, _insertionAnchor)
|
insert(fragment, _insertionParent, _insertionAnchor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,12 +35,17 @@ import type { RawSlots, VaporSlot } from './componentSlots'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
import { createTextNode } from './dom/node'
|
import { createTextNode } from './dom/node'
|
||||||
import { optimizePropertyLookup } from './dom/prop'
|
import { optimizePropertyLookup } from './dom/prop'
|
||||||
import { hydrateNode as vaporHydrateNode } from './dom/hydration'
|
import {
|
||||||
|
currentHydrationNode,
|
||||||
|
isHydrating,
|
||||||
|
locateHydrationNode,
|
||||||
|
hydrateNode as vaporHydrateNode,
|
||||||
|
} from './dom/hydration'
|
||||||
|
|
||||||
// mounting vapor components and slots in vdom
|
// mounting vapor components and slots in vdom
|
||||||
const vaporInteropImpl: Omit<
|
const vaporInteropImpl: Omit<
|
||||||
VaporInteropInterface,
|
VaporInteropInterface,
|
||||||
'vdomMount' | 'vdomUnmount' | 'vdomSlot' | 'vdomHydrate'
|
'vdomMount' | 'vdomUnmount' | 'vdomSlot'
|
||||||
> = {
|
> = {
|
||||||
mount(vnode, container, anchor, parentComponent) {
|
mount(vnode, container, anchor, parentComponent) {
|
||||||
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
|
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
|
||||||
|
@ -144,6 +149,8 @@ const vaporSlotsProxyHandler: ProxyHandler<any> = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let vdomHydrateNode: HydrationRenderer['hydrateNode'] | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mount vdom component in vapor
|
* Mount vdom component in vapor
|
||||||
*/
|
*/
|
||||||
|
@ -152,7 +159,7 @@ function createVDOMComponent(
|
||||||
component: ConcreteComponent,
|
component: ConcreteComponent,
|
||||||
rawProps?: LooseRawProps | null,
|
rawProps?: LooseRawProps | null,
|
||||||
rawSlots?: LooseRawSlots | null,
|
rawSlots?: LooseRawSlots | null,
|
||||||
): [VaporFragment, VNode] {
|
): VaporFragment {
|
||||||
const frag = new VaporFragment([])
|
const frag = new VaporFragment([])
|
||||||
const vnode = createVNode(
|
const vnode = createVNode(
|
||||||
component,
|
component,
|
||||||
|
@ -181,16 +188,30 @@ function createVDOMComponent(
|
||||||
}
|
}
|
||||||
|
|
||||||
frag.insert = (parentNode, anchor) => {
|
frag.insert = (parentNode, anchor) => {
|
||||||
if (!isMounted) {
|
if (!isMounted || isHydrating) {
|
||||||
internals.mt(
|
if (isHydrating) {
|
||||||
vnode,
|
;(
|
||||||
parentNode,
|
vdomHydrateNode ||
|
||||||
anchor,
|
(vdomHydrateNode = ensureHydrationRenderer().hydrateNode!)
|
||||||
parentInstance as any,
|
)(
|
||||||
null,
|
currentHydrationNode!,
|
||||||
undefined,
|
vnode,
|
||||||
false,
|
parentInstance as any,
|
||||||
)
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
internals.mt(
|
||||||
|
vnode,
|
||||||
|
parentNode,
|
||||||
|
anchor,
|
||||||
|
parentInstance as any,
|
||||||
|
null,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
onScopeDispose(unmount, true)
|
onScopeDispose(unmount, true)
|
||||||
isMounted = true
|
isMounted = true
|
||||||
} else {
|
} else {
|
||||||
|
@ -207,7 +228,7 @@ function createVDOMComponent(
|
||||||
|
|
||||||
frag.remove = unmount
|
frag.remove = unmount
|
||||||
|
|
||||||
return [frag, vnode]
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -235,28 +256,43 @@ function renderVDOMSlot(
|
||||||
isFunction(name) ? name() : name,
|
isFunction(name) ? name() : name,
|
||||||
props,
|
props,
|
||||||
)
|
)
|
||||||
if ((vnode.children as any[]).length) {
|
if (isHydrating) {
|
||||||
if (fallbackNodes) {
|
locateHydrationNode(true)
|
||||||
remove(fallbackNodes, parentNode)
|
;(
|
||||||
fallbackNodes = undefined
|
vdomHydrateNode ||
|
||||||
}
|
(vdomHydrateNode = ensureHydrationRenderer().hydrateNode!)
|
||||||
internals.p(
|
)(
|
||||||
oldVNode,
|
currentHydrationNode!,
|
||||||
vnode,
|
vnode,
|
||||||
parentNode,
|
|
||||||
anchor,
|
|
||||||
parentComponent as any,
|
parentComponent as any,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
oldVNode = vnode
|
|
||||||
} else {
|
} else {
|
||||||
if (fallback && !fallbackNodes) {
|
if ((vnode.children as any[]).length) {
|
||||||
// mount fallback
|
if (fallbackNodes) {
|
||||||
if (oldVNode) {
|
remove(fallbackNodes, parentNode)
|
||||||
internals.um(oldVNode, parentComponent as any, null, true)
|
fallbackNodes = undefined
|
||||||
}
|
}
|
||||||
insert((fallbackNodes = fallback(props)), parentNode, anchor)
|
internals.p(
|
||||||
|
oldVNode,
|
||||||
|
vnode,
|
||||||
|
parentNode,
|
||||||
|
anchor,
|
||||||
|
parentComponent as any,
|
||||||
|
)
|
||||||
|
oldVNode = vnode
|
||||||
|
} else {
|
||||||
|
if (fallback && !fallbackNodes) {
|
||||||
|
// mount fallback
|
||||||
|
if (oldVNode) {
|
||||||
|
internals.um(oldVNode, parentComponent as any, null, true)
|
||||||
|
}
|
||||||
|
insert((fallbackNodes = fallback(props)), parentNode, anchor)
|
||||||
|
}
|
||||||
|
oldVNode = null
|
||||||
}
|
}
|
||||||
oldVNode = null
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
isMounted = true
|
isMounted = true
|
||||||
|
@ -284,14 +320,11 @@ function renderVDOMSlot(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const vaporInteropPlugin: Plugin = app => {
|
export const vaporInteropPlugin: Plugin = app => {
|
||||||
const { internals, hydrateNode } = (
|
const internals = ensureRenderer().internals
|
||||||
app._ssr ? ensureHydrationRenderer() : ensureRenderer()
|
|
||||||
) as HydrationRenderer
|
|
||||||
app._context.vapor = extend(vaporInteropImpl, {
|
app._context.vapor = extend(vaporInteropImpl, {
|
||||||
vdomMount: createVDOMComponent.bind(null, internals),
|
vdomMount: createVDOMComponent.bind(null, internals),
|
||||||
vdomUnmount: internals.umt,
|
vdomUnmount: internals.umt,
|
||||||
vdomSlot: renderVDOMSlot.bind(null, internals),
|
vdomSlot: renderVDOMSlot.bind(null, internals),
|
||||||
vdomHydrate: hydrateNode,
|
|
||||||
})
|
})
|
||||||
const mount = app.mount
|
const mount = app.mount
|
||||||
app.mount = ((...args) => {
|
app.mount = ((...args) => {
|
||||||
|
|
Loading…
Reference in New Issue