wip: vdom interop
This commit is contained in:
parent
700f49ee96
commit
e5399c3418
|
@ -33,6 +33,7 @@ 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
|
||||||
|
@ -104,6 +105,7 @@ 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
|
||||||
|
@ -193,6 +195,7 @@ export interface VaporInteropInterface {
|
||||||
unmount(vnode: VNode, doRemove?: boolean): void
|
unmount(vnode: VNode, doRemove?: boolean): void
|
||||||
move(vnode: VNode, container: any, anchor: any): void
|
move(vnode: VNode, container: any, anchor: any): void
|
||||||
slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void
|
slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void
|
||||||
|
hydrate(node: Node, fn: () => void): void
|
||||||
|
|
||||||
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
|
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
|
||||||
vdomUnmount: UnmountComponentFn
|
vdomUnmount: UnmountComponentFn
|
||||||
|
@ -203,6 +206,7 @@ export interface VaporInteropInterface {
|
||||||
parentComponent: any, // VaporComponentInstance
|
parentComponent: any, // VaporComponentInstance
|
||||||
fallback?: any, // VaporSlot
|
fallback?: any, // VaporSlot
|
||||||
) => any
|
) => any
|
||||||
|
vdomHydrate: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,7 +37,11 @@ import {
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
stringifyStyle,
|
stringifyStyle,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { type RendererInternals, needTransition } from './renderer'
|
import {
|
||||||
|
type RendererInternals,
|
||||||
|
getVaporInterface,
|
||||||
|
needTransition,
|
||||||
|
} from './renderer'
|
||||||
import { setRef } from './rendererTemplateRef'
|
import { setRef } from './rendererTemplateRef'
|
||||||
import {
|
import {
|
||||||
type SuspenseBoundary,
|
type SuspenseBoundary,
|
||||||
|
@ -294,10 +298,6 @@ export function createHydrationFunctions(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
if ((vnode.type as ConcreteComponent).__vapor) {
|
|
||||||
throw new Error('Vapor component hydration is not supported yet.')
|
|
||||||
}
|
|
||||||
|
|
||||||
// when setting up the render effect, if the initial vnode already
|
// when setting up the render effect, if the initial vnode already
|
||||||
// has .el set, the component will perform hydration instead of mount
|
// has .el set, the component will perform hydration instead of mount
|
||||||
// on its sub-tree.
|
// on its sub-tree.
|
||||||
|
@ -318,15 +318,23 @@ export function createHydrationFunctions(
|
||||||
nextNode = nextSibling(node)
|
nextNode = nextSibling(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
mountComponent(
|
// hydrate vapor component
|
||||||
vnode,
|
if ((vnode.type as ConcreteComponent).__vapor) {
|
||||||
container,
|
const vaporInterface = getVaporInterface(parentComponent, vnode)
|
||||||
null,
|
vaporInterface.hydrate(node, () => {
|
||||||
parentComponent,
|
vaporInterface.mount(vnode, container, null, parentComponent)
|
||||||
parentSuspense,
|
})
|
||||||
getContainerType(container),
|
} else {
|
||||||
optimized,
|
mountComponent(
|
||||||
)
|
vnode,
|
||||||
|
container,
|
||||||
|
null,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense,
|
||||||
|
getContainerType(container),
|
||||||
|
optimized,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// #3787
|
// #3787
|
||||||
// if component is async, it may get moved / unmounted before its
|
// if component is async, it may get moved / unmounted before its
|
||||||
|
|
|
@ -107,6 +107,7 @@ export interface Renderer<HostElement = RendererElement> {
|
||||||
|
|
||||||
export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
|
export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
|
||||||
hydrate: RootHydrateFunction
|
hydrate: RootHydrateFunction
|
||||||
|
hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ElementNamespace = 'svg' | 'mathml' | undefined
|
export type ElementNamespace = 'svg' | 'mathml' | undefined
|
||||||
|
@ -2524,6 +2525,7 @@ function baseCreateRenderer(
|
||||||
return {
|
return {
|
||||||
render,
|
render,
|
||||||
hydrate,
|
hydrate,
|
||||||
|
hydrateNode,
|
||||||
internals,
|
internals,
|
||||||
createApp: createAppAPI(
|
createApp: createAppAPI(
|
||||||
mountApp,
|
mountApp,
|
||||||
|
@ -2639,7 +2641,10 @@ export function invalidateMount(hooks: LifecycleHook | undefined): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVaporInterface(
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function getVaporInterface(
|
||||||
instance: ComponentInternalInstance | null,
|
instance: ComponentInternalInstance | null,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
): VaporInteropInterface {
|
): VaporInteropInterface {
|
||||||
|
|
|
@ -149,6 +149,7 @@ 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)
|
||||||
|
@ -319,7 +320,7 @@ export * from './jsx'
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export { ensureRenderer, normalizeContainer }
|
export { ensureRenderer, ensureHydrationRenderer, normalizeContainer }
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -58,7 +58,11 @@ import {
|
||||||
getSlot,
|
getSlot,
|
||||||
} from './componentSlots'
|
} from './componentSlots'
|
||||||
import { hmrReload, hmrRerender } from './hmr'
|
import { hmrReload, hmrRerender } from './hmr'
|
||||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
import {
|
||||||
|
currentHydrationNode,
|
||||||
|
isHydrating,
|
||||||
|
locateHydrationNode,
|
||||||
|
} from './dom/hydration'
|
||||||
import {
|
import {
|
||||||
insertionAnchor,
|
insertionAnchor,
|
||||||
insertionParent,
|
insertionParent,
|
||||||
|
@ -152,13 +156,22 @@ 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 = appContext.vapor.vdomMount(
|
const [frag, vnode] = appContext.vapor.vdomMount(
|
||||||
component as any,
|
component as any,
|
||||||
rawProps,
|
rawProps,
|
||||||
rawSlots,
|
rawSlots,
|
||||||
)
|
)
|
||||||
if (!isHydrating && _insertionParent) {
|
if (!isHydrating && _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
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,15 @@ export function setCurrentHydrationNode(node: Node | null): void {
|
||||||
|
|
||||||
let isOptimized = false
|
let isOptimized = false
|
||||||
|
|
||||||
export function withHydration(container: ParentNode, fn: () => void): void {
|
function performHydration<T>(
|
||||||
adoptTemplate = adoptTemplateImpl
|
fn: () => T,
|
||||||
locateHydrationNode = locateHydrationNodeImpl
|
setup: () => void,
|
||||||
|
cleanup: () => void,
|
||||||
|
): T {
|
||||||
if (!isOptimized) {
|
if (!isOptimized) {
|
||||||
|
adoptTemplate = adoptTemplateImpl
|
||||||
|
locateHydrationNode = locateHydrationNodeImpl
|
||||||
|
|
||||||
// optimize anchor cache lookup
|
// optimize anchor cache lookup
|
||||||
;(Comment.prototype as any).$fs = undefined
|
;(Comment.prototype as any).$fs = undefined
|
||||||
;(Node.prototype as any).$nc = undefined
|
;(Node.prototype as any).$nc = undefined
|
||||||
|
@ -33,15 +38,27 @@ export function withHydration(container: ParentNode, fn: () => void): void {
|
||||||
}
|
}
|
||||||
enableHydrationNodeLookup()
|
enableHydrationNodeLookup()
|
||||||
isHydrating = true
|
isHydrating = true
|
||||||
setInsertionState(container, 0)
|
setup()
|
||||||
const res = fn()
|
const res = fn()
|
||||||
resetInsertionState()
|
cleanup()
|
||||||
currentHydrationNode = null
|
currentHydrationNode = null
|
||||||
isHydrating = false
|
isHydrating = false
|
||||||
disableHydrationNodeLookup()
|
disableHydrationNodeLookup()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function withHydration(container: ParentNode, fn: () => void): void {
|
||||||
|
const setup = () => setInsertionState(container, 0)
|
||||||
|
const cleanup = () => resetInsertionState()
|
||||||
|
return performHydration(fn, setup, cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hydrateNode(node: Node, fn: () => void): void {
|
||||||
|
const setup = () => (currentHydrationNode = node)
|
||||||
|
const cleanup = () => {}
|
||||||
|
return performHydration(fn, setup, cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
export let adoptTemplate: (node: Node, template: string) => Node | null
|
export let adoptTemplate: (node: Node, template: string) => Node | null
|
||||||
export let locateHydrationNode: (hasFragmentAnchor?: boolean) => void
|
export let locateHydrationNode: (hasFragmentAnchor?: boolean) => void
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
type App,
|
type App,
|
||||||
type ComponentInternalInstance,
|
type ComponentInternalInstance,
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
|
type HydrationRenderer,
|
||||||
MoveType,
|
MoveType,
|
||||||
type Plugin,
|
type Plugin,
|
||||||
type RendererInternals,
|
type RendererInternals,
|
||||||
|
@ -11,6 +12,7 @@ import {
|
||||||
type VaporInteropInterface,
|
type VaporInteropInterface,
|
||||||
createVNode,
|
createVNode,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
|
ensureHydrationRenderer,
|
||||||
ensureRenderer,
|
ensureRenderer,
|
||||||
onScopeDispose,
|
onScopeDispose,
|
||||||
renderSlot,
|
renderSlot,
|
||||||
|
@ -33,11 +35,12 @@ 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'
|
||||||
|
|
||||||
// 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'
|
'vdomMount' | 'vdomUnmount' | 'vdomSlot' | 'vdomHydrate'
|
||||||
> = {
|
> = {
|
||||||
mount(vnode, container, anchor, parentComponent) {
|
mount(vnode, container, anchor, parentComponent) {
|
||||||
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
|
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
|
||||||
|
@ -113,6 +116,8 @@ const vaporInteropImpl: Omit<
|
||||||
insert(vnode.vb || (vnode.component as any), container, anchor)
|
insert(vnode.vb || (vnode.component as any), container, anchor)
|
||||||
insert(vnode.anchor as any, container, anchor)
|
insert(vnode.anchor as any, container, anchor)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hydrate: vaporHydrateNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaporSlotPropsProxyHandler: ProxyHandler<
|
const vaporSlotPropsProxyHandler: ProxyHandler<
|
||||||
|
@ -147,7 +152,7 @@ function createVDOMComponent(
|
||||||
component: ConcreteComponent,
|
component: ConcreteComponent,
|
||||||
rawProps?: LooseRawProps | null,
|
rawProps?: LooseRawProps | null,
|
||||||
rawSlots?: LooseRawSlots | null,
|
rawSlots?: LooseRawSlots | null,
|
||||||
): VaporFragment {
|
): [VaporFragment, VNode] {
|
||||||
const frag = new VaporFragment([])
|
const frag = new VaporFragment([])
|
||||||
const vnode = createVNode(
|
const vnode = createVNode(
|
||||||
component,
|
component,
|
||||||
|
@ -202,7 +207,7 @@ function createVDOMComponent(
|
||||||
|
|
||||||
frag.remove = unmount
|
frag.remove = unmount
|
||||||
|
|
||||||
return frag
|
return [frag, vnode]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -279,11 +284,14 @@ function renderVDOMSlot(
|
||||||
}
|
}
|
||||||
|
|
||||||
export const vaporInteropPlugin: Plugin = app => {
|
export const vaporInteropPlugin: Plugin = app => {
|
||||||
const internals = ensureRenderer().internals
|
const { internals, hydrateNode } = (
|
||||||
|
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