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
_container: HostElement | null
_context: AppContext
_instance: GenericComponentInstance | null
+ _ssr?: boolean
/**
* @internal custom element vnode
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
parentComponent: any, // VaporComponentInstance
fallback?: any, // VaporSlot
) => any
+ vdomHydrate: ReturnType<typeof createHydrationFunctions>[1] | undefined
}
/**
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,
)
}
} 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.
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
export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
hydrate: RootHydrateFunction
+ hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
}
export type ElementNamespace = 'svg' | 'mathml' | undefined
return {
render,
hydrate,
+ hydrateNode,
internals,
createApp: createAppAPI(
mountApp,
}
}
-function getVaporInterface(
+/**
+ * @internal
+ */
+export function getVaporInterface(
instance: ComponentInternalInstance | null,
vnode: VNode,
): VaporInteropInterface {
export const createSSRApp = ((...args) => {
const app = ensureHydrationRenderer().createApp(...args)
+ app._ssr = true
if (__DEV__) {
injectNativeTagCheck(app)
/**
* @internal
*/
-export { ensureRenderer, normalizeContainer }
+export { ensureRenderer, ensureHydrationRenderer, normalizeContainer }
/**
* @internal
*/
getSlot,
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
-import { isHydrating, locateHydrationNode } from './dom/hydration'
+import {
+ currentHydrationNode,
+ isHydrating,
+ locateHydrationNode,
+} from './dom/hydration'
import {
insertionAnchor,
insertionParent,
// 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
}
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
}
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
type App,
type ComponentInternalInstance,
type ConcreteComponent,
+ type HydrationRenderer,
MoveType,
type Plugin,
type RendererInternals,
type VaporInteropInterface,
createVNode,
currentInstance,
+ ensureHydrationRenderer,
ensureRenderer,
onScopeDispose,
renderSlot,
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())
insert(vnode.vb || (vnode.component as any), container, anchor)
insert(vnode.anchor as any, container, anchor)
},
+
+ hydrate: vaporHydrateNode,
}
const vaporSlotPropsProxyHandler: ProxyHandler<
component: ConcreteComponent,
rawProps?: LooseRawProps | null,
rawSlots?: LooseRawSlots | null,
-): VaporFragment {
+): [VaporFragment, VNode] {
const frag = new VaporFragment([])
const vnode = createVNode(
component,
frag.remove = unmount
- return frag
+ return [frag, vnode]
}
/**
}
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) => {