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 { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import type { DefineComponent } from './apiDefineComponent'
|
||||
import type { createHydrationFunctions } from './hydration'
|
||||
|
||||
export interface App<HostElement = any> {
|
||||
version: string
|
||||
|
@ -104,6 +105,7 @@ export interface App<HostElement = any> {
|
|||
_container: HostElement | null
|
||||
_context: AppContext
|
||||
_instance: GenericComponentInstance | null
|
||||
_ssr?: boolean
|
||||
|
||||
/**
|
||||
* @internal custom element vnode
|
||||
|
@ -193,6 +195,7 @@ export interface VaporInteropInterface {
|
|||
unmount(vnode: VNode, doRemove?: boolean): void
|
||||
move(vnode: 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
|
||||
vdomUnmount: UnmountComponentFn
|
||||
|
@ -203,6 +206,7 @@ export interface VaporInteropInterface {
|
|||
parentComponent: any, // VaporComponentInstance
|
||||
fallback?: any, // VaporSlot
|
||||
) => any
|
||||
vdomHydrate: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,7 +37,11 @@ import {
|
|||
normalizeStyle,
|
||||
stringifyStyle,
|
||||
} from '@vue/shared'
|
||||
import { type RendererInternals, needTransition } from './renderer'
|
||||
import {
|
||||
type RendererInternals,
|
||||
getVaporInterface,
|
||||
needTransition,
|
||||
} from './renderer'
|
||||
import { setRef } from './rendererTemplateRef'
|
||||
import {
|
||||
type SuspenseBoundary,
|
||||
|
@ -294,10 +298,6 @@ export function createHydrationFunctions(
|
|||
)
|
||||
}
|
||||
} 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
|
||||
// has .el set, the component will perform hydration instead of mount
|
||||
// on its sub-tree.
|
||||
|
@ -318,15 +318,23 @@ export function createHydrationFunctions(
|
|||
nextNode = nextSibling(node)
|
||||
}
|
||||
|
||||
mountComponent(
|
||||
vnode,
|
||||
container,
|
||||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
getContainerType(container),
|
||||
optimized,
|
||||
)
|
||||
// hydrate vapor component
|
||||
if ((vnode.type as ConcreteComponent).__vapor) {
|
||||
const vaporInterface = getVaporInterface(parentComponent, vnode)
|
||||
vaporInterface.hydrate(node, () => {
|
||||
vaporInterface.mount(vnode, container, null, parentComponent)
|
||||
})
|
||||
} else {
|
||||
mountComponent(
|
||||
vnode,
|
||||
container,
|
||||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
getContainerType(container),
|
||||
optimized,
|
||||
)
|
||||
}
|
||||
|
||||
// #3787
|
||||
// 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> {
|
||||
hydrate: RootHydrateFunction
|
||||
hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
||||
}
|
||||
|
||||
export type ElementNamespace = 'svg' | 'mathml' | undefined
|
||||
|
@ -2524,6 +2525,7 @@ function baseCreateRenderer(
|
|||
return {
|
||||
render,
|
||||
hydrate,
|
||||
hydrateNode,
|
||||
internals,
|
||||
createApp: createAppAPI(
|
||||
mountApp,
|
||||
|
@ -2639,7 +2641,10 @@ export function invalidateMount(hooks: LifecycleHook | undefined): void {
|
|||
}
|
||||
}
|
||||
|
||||
function getVaporInterface(
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function getVaporInterface(
|
||||
instance: ComponentInternalInstance | null,
|
||||
vnode: VNode,
|
||||
): VaporInteropInterface {
|
||||
|
|
|
@ -149,6 +149,7 @@ export const createApp = ((...args) => {
|
|||
|
||||
export const createSSRApp = ((...args) => {
|
||||
const app = ensureHydrationRenderer().createApp(...args)
|
||||
app._ssr = true
|
||||
|
||||
if (__DEV__) {
|
||||
injectNativeTagCheck(app)
|
||||
|
@ -319,7 +320,7 @@ export * from './jsx'
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export { ensureRenderer, normalizeContainer }
|
||||
export { ensureRenderer, ensureHydrationRenderer, normalizeContainer }
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
|
@ -58,7 +58,11 @@ import {
|
|||
getSlot,
|
||||
} from './componentSlots'
|
||||
import { hmrReload, hmrRerender } from './hmr'
|
||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||
import {
|
||||
currentHydrationNode,
|
||||
isHydrating,
|
||||
locateHydrationNode,
|
||||
} from './dom/hydration'
|
||||
import {
|
||||
insertionAnchor,
|
||||
insertionParent,
|
||||
|
@ -152,13 +156,22 @@ export function createComponent(
|
|||
|
||||
// vdom interop enabled and component is not an explicit vapor component
|
||||
if (appContext.vapor && !component.__vapor) {
|
||||
const frag = appContext.vapor.vdomMount(
|
||||
const [frag, vnode] = appContext.vapor.vdomMount(
|
||||
component as any,
|
||||
rawProps,
|
||||
rawSlots,
|
||||
)
|
||||
if (!isHydrating && _insertionParent) {
|
||||
insert(frag, _insertionParent, _insertionAnchor)
|
||||
} else if (isHydrating) {
|
||||
appContext.vapor.vdomHydrate!(
|
||||
currentHydrationNode!,
|
||||
vnode,
|
||||
currentInstance as any,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
)
|
||||
}
|
||||
return frag
|
||||
}
|
||||
|
|
|
@ -22,10 +22,15 @@ export function setCurrentHydrationNode(node: Node | null): void {
|
|||
|
||||
let isOptimized = false
|
||||
|
||||
export function withHydration(container: ParentNode, fn: () => void): void {
|
||||
adoptTemplate = adoptTemplateImpl
|
||||
locateHydrationNode = locateHydrationNodeImpl
|
||||
function performHydration<T>(
|
||||
fn: () => T,
|
||||
setup: () => void,
|
||||
cleanup: () => void,
|
||||
): T {
|
||||
if (!isOptimized) {
|
||||
adoptTemplate = adoptTemplateImpl
|
||||
locateHydrationNode = locateHydrationNodeImpl
|
||||
|
||||
// optimize anchor cache lookup
|
||||
;(Comment.prototype as any).$fs = undefined
|
||||
;(Node.prototype as any).$nc = undefined
|
||||
|
@ -33,15 +38,27 @@ export function withHydration(container: ParentNode, fn: () => void): void {
|
|||
}
|
||||
enableHydrationNodeLookup()
|
||||
isHydrating = true
|
||||
setInsertionState(container, 0)
|
||||
setup()
|
||||
const res = fn()
|
||||
resetInsertionState()
|
||||
cleanup()
|
||||
currentHydrationNode = null
|
||||
isHydrating = false
|
||||
disableHydrationNodeLookup()
|
||||
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 locateHydrationNode: (hasFragmentAnchor?: boolean) => void
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
type App,
|
||||
type ComponentInternalInstance,
|
||||
type ConcreteComponent,
|
||||
type HydrationRenderer,
|
||||
MoveType,
|
||||
type Plugin,
|
||||
type RendererInternals,
|
||||
|
@ -11,6 +12,7 @@ import {
|
|||
type VaporInteropInterface,
|
||||
createVNode,
|
||||
currentInstance,
|
||||
ensureHydrationRenderer,
|
||||
ensureRenderer,
|
||||
onScopeDispose,
|
||||
renderSlot,
|
||||
|
@ -33,11 +35,12 @@ import type { RawSlots, VaporSlot } from './componentSlots'
|
|||
import { renderEffect } from './renderEffect'
|
||||
import { createTextNode } from './dom/node'
|
||||
import { optimizePropertyLookup } from './dom/prop'
|
||||
import { hydrateNode as vaporHydrateNode } from './dom/hydration'
|
||||
|
||||
// mounting vapor components and slots in vdom
|
||||
const vaporInteropImpl: Omit<
|
||||
VaporInteropInterface,
|
||||
'vdomMount' | 'vdomUnmount' | 'vdomSlot'
|
||||
'vdomMount' | 'vdomUnmount' | 'vdomSlot' | 'vdomHydrate'
|
||||
> = {
|
||||
mount(vnode, container, anchor, parentComponent) {
|
||||
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.anchor as any, container, anchor)
|
||||
},
|
||||
|
||||
hydrate: vaporHydrateNode,
|
||||
}
|
||||
|
||||
const vaporSlotPropsProxyHandler: ProxyHandler<
|
||||
|
@ -147,7 +152,7 @@ function createVDOMComponent(
|
|||
component: ConcreteComponent,
|
||||
rawProps?: LooseRawProps | null,
|
||||
rawSlots?: LooseRawSlots | null,
|
||||
): VaporFragment {
|
||||
): [VaporFragment, VNode] {
|
||||
const frag = new VaporFragment([])
|
||||
const vnode = createVNode(
|
||||
component,
|
||||
|
@ -202,7 +207,7 @@ function createVDOMComponent(
|
|||
|
||||
frag.remove = unmount
|
||||
|
||||
return frag
|
||||
return [frag, vnode]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -279,11 +284,14 @@ function renderVDOMSlot(
|
|||
}
|
||||
|
||||
export const vaporInteropPlugin: Plugin = app => {
|
||||
const internals = ensureRenderer().internals
|
||||
const { internals, hydrateNode } = (
|
||||
app._ssr ? ensureHydrationRenderer() : ensureRenderer()
|
||||
) as HydrationRenderer
|
||||
app._context.vapor = extend(vaporInteropImpl, {
|
||||
vdomMount: createVDOMComponent.bind(null, internals),
|
||||
vdomUnmount: internals.umt,
|
||||
vdomSlot: renderVDOMSlot.bind(null, internals),
|
||||
vdomHydrate: hydrateNode,
|
||||
})
|
||||
const mount = app.mount
|
||||
app.mount = ((...args) => {
|
||||
|
|
Loading…
Reference in New Issue