Skip to content

Commit e5399c3

Browse files
committed
wip: vdom interop
1 parent 700f49e commit e5399c3

File tree

7 files changed

+83
-27
lines changed

7 files changed

+83
-27
lines changed

packages/runtime-core/src/apiCreateApp.ts

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type { NormalizedPropsOptions } from './componentProps'
3333
import type { ObjectEmitsOptions } from './componentEmits'
3434
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
3535
import type { DefineComponent } from './apiDefineComponent'
36+
import type { createHydrationFunctions } from './hydration'
3637

3738
export interface App<HostElement = any> {
3839
version: string
@@ -104,6 +105,7 @@ export interface App<HostElement = any> {
104105
_container: HostElement | null
105106
_context: AppContext
106107
_instance: GenericComponentInstance | null
108+
_ssr?: boolean
107109

108110
/**
109111
* @internal custom element vnode
@@ -193,6 +195,7 @@ export interface VaporInteropInterface {
193195
unmount(vnode: VNode, doRemove?: boolean): void
194196
move(vnode: VNode, container: any, anchor: any): void
195197
slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void
198+
hydrate(node: Node, fn: () => void): void
196199

197200
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
198201
vdomUnmount: UnmountComponentFn
@@ -203,6 +206,7 @@ export interface VaporInteropInterface {
203206
parentComponent: any, // VaporComponentInstance
204207
fallback?: any, // VaporSlot
205208
) => any
209+
vdomHydrate: ReturnType<typeof createHydrationFunctions>[1] | undefined
206210
}
207211

208212
/**

packages/runtime-core/src/hydration.ts

+22-14
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ import {
3737
normalizeStyle,
3838
stringifyStyle,
3939
} from '@vue/shared'
40-
import { type RendererInternals, needTransition } from './renderer'
40+
import {
41+
type RendererInternals,
42+
getVaporInterface,
43+
needTransition,
44+
} from './renderer'
4145
import { setRef } from './rendererTemplateRef'
4246
import {
4347
type SuspenseBoundary,
@@ -294,10 +298,6 @@ export function createHydrationFunctions(
294298
)
295299
}
296300
} else if (shapeFlag & ShapeFlags.COMPONENT) {
297-
if ((vnode.type as ConcreteComponent).__vapor) {
298-
throw new Error('Vapor component hydration is not supported yet.')
299-
}
300-
301301
// when setting up the render effect, if the initial vnode already
302302
// has .el set, the component will perform hydration instead of mount
303303
// on its sub-tree.
@@ -318,15 +318,23 @@ export function createHydrationFunctions(
318318
nextNode = nextSibling(node)
319319
}
320320

321-
mountComponent(
322-
vnode,
323-
container,
324-
null,
325-
parentComponent,
326-
parentSuspense,
327-
getContainerType(container),
328-
optimized,
329-
)
321+
// hydrate vapor component
322+
if ((vnode.type as ConcreteComponent).__vapor) {
323+
const vaporInterface = getVaporInterface(parentComponent, vnode)
324+
vaporInterface.hydrate(node, () => {
325+
vaporInterface.mount(vnode, container, null, parentComponent)
326+
})
327+
} else {
328+
mountComponent(
329+
vnode,
330+
container,
331+
null,
332+
parentComponent,
333+
parentSuspense,
334+
getContainerType(container),
335+
optimized,
336+
)
337+
}
330338

331339
// #3787
332340
// if component is async, it may get moved / unmounted before its

packages/runtime-core/src/renderer.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export interface Renderer<HostElement = RendererElement> {
107107

108108
export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
109109
hydrate: RootHydrateFunction
110+
hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
110111
}
111112

112113
export type ElementNamespace = 'svg' | 'mathml' | undefined
@@ -2524,6 +2525,7 @@ function baseCreateRenderer(
25242525
return {
25252526
render,
25262527
hydrate,
2528+
hydrateNode,
25272529
internals,
25282530
createApp: createAppAPI(
25292531
mountApp,
@@ -2639,7 +2641,10 @@ export function invalidateMount(hooks: LifecycleHook | undefined): void {
26392641
}
26402642
}
26412643

2642-
function getVaporInterface(
2644+
/**
2645+
* @internal
2646+
*/
2647+
export function getVaporInterface(
26432648
instance: ComponentInternalInstance | null,
26442649
vnode: VNode,
26452650
): VaporInteropInterface {

packages/runtime-dom/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export const createApp = ((...args) => {
149149

150150
export const createSSRApp = ((...args) => {
151151
const app = ensureHydrationRenderer().createApp(...args)
152+
app._ssr = true
152153

153154
if (__DEV__) {
154155
injectNativeTagCheck(app)
@@ -319,7 +320,7 @@ export * from './jsx'
319320
/**
320321
* @internal
321322
*/
322-
export { ensureRenderer, normalizeContainer }
323+
export { ensureRenderer, ensureHydrationRenderer, normalizeContainer }
323324
/**
324325
* @internal
325326
*/

packages/runtime-vapor/src/component.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ import {
5858
getSlot,
5959
} from './componentSlots'
6060
import { hmrReload, hmrRerender } from './hmr'
61-
import { isHydrating, locateHydrationNode } from './dom/hydration'
61+
import {
62+
currentHydrationNode,
63+
isHydrating,
64+
locateHydrationNode,
65+
} from './dom/hydration'
6266
import {
6367
insertionAnchor,
6468
insertionParent,
@@ -152,13 +156,22 @@ export function createComponent(
152156

153157
// vdom interop enabled and component is not an explicit vapor component
154158
if (appContext.vapor && !component.__vapor) {
155-
const frag = appContext.vapor.vdomMount(
159+
const [frag, vnode] = appContext.vapor.vdomMount(
156160
component as any,
157161
rawProps,
158162
rawSlots,
159163
)
160164
if (!isHydrating && _insertionParent) {
161165
insert(frag, _insertionParent, _insertionAnchor)
166+
} else if (isHydrating) {
167+
appContext.vapor.vdomHydrate!(
168+
currentHydrationNode!,
169+
vnode,
170+
currentInstance as any,
171+
null,
172+
null,
173+
false,
174+
)
162175
}
163176
return frag
164177
}

packages/runtime-vapor/src/dom/hydration.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,43 @@ export function setCurrentHydrationNode(node: Node | null): void {
2222

2323
let isOptimized = false
2424

25-
export function withHydration(container: ParentNode, fn: () => void): void {
26-
adoptTemplate = adoptTemplateImpl
27-
locateHydrationNode = locateHydrationNodeImpl
25+
function performHydration<T>(
26+
fn: () => T,
27+
setup: () => void,
28+
cleanup: () => void,
29+
): T {
2830
if (!isOptimized) {
31+
adoptTemplate = adoptTemplateImpl
32+
locateHydrationNode = locateHydrationNodeImpl
33+
2934
// optimize anchor cache lookup
3035
;(Comment.prototype as any).$fs = undefined
3136
;(Node.prototype as any).$nc = undefined
3237
isOptimized = true
3338
}
3439
enableHydrationNodeLookup()
3540
isHydrating = true
36-
setInsertionState(container, 0)
41+
setup()
3742
const res = fn()
38-
resetInsertionState()
43+
cleanup()
3944
currentHydrationNode = null
4045
isHydrating = false
4146
disableHydrationNodeLookup()
4247
return res
4348
}
4449

50+
export function withHydration(container: ParentNode, fn: () => void): void {
51+
const setup = () => setInsertionState(container, 0)
52+
const cleanup = () => resetInsertionState()
53+
return performHydration(fn, setup, cleanup)
54+
}
55+
56+
export function hydrateNode(node: Node, fn: () => void): void {
57+
const setup = () => (currentHydrationNode = node)
58+
const cleanup = () => {}
59+
return performHydration(fn, setup, cleanup)
60+
}
61+
4562
export let adoptTemplate: (node: Node, template: string) => Node | null
4663
export let locateHydrationNode: (hasFragmentAnchor?: boolean) => void
4764

packages/runtime-vapor/src/vdomInterop.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type App,
33
type ComponentInternalInstance,
44
type ConcreteComponent,
5+
type HydrationRenderer,
56
MoveType,
67
type Plugin,
78
type RendererInternals,
@@ -11,6 +12,7 @@ import {
1112
type VaporInteropInterface,
1213
createVNode,
1314
currentInstance,
15+
ensureHydrationRenderer,
1416
ensureRenderer,
1517
onScopeDispose,
1618
renderSlot,
@@ -33,11 +35,12 @@ import type { RawSlots, VaporSlot } from './componentSlots'
3335
import { renderEffect } from './renderEffect'
3436
import { createTextNode } from './dom/node'
3537
import { optimizePropertyLookup } from './dom/prop'
38+
import { hydrateNode as vaporHydrateNode } from './dom/hydration'
3639

3740
// mounting vapor components and slots in vdom
3841
const vaporInteropImpl: Omit<
3942
VaporInteropInterface,
40-
'vdomMount' | 'vdomUnmount' | 'vdomSlot'
43+
'vdomMount' | 'vdomUnmount' | 'vdomSlot' | 'vdomHydrate'
4144
> = {
4245
mount(vnode, container, anchor, parentComponent) {
4346
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
@@ -113,6 +116,8 @@ const vaporInteropImpl: Omit<
113116
insert(vnode.vb || (vnode.component as any), container, anchor)
114117
insert(vnode.anchor as any, container, anchor)
115118
},
119+
120+
hydrate: vaporHydrateNode,
116121
}
117122

118123
const vaporSlotPropsProxyHandler: ProxyHandler<
@@ -147,7 +152,7 @@ function createVDOMComponent(
147152
component: ConcreteComponent,
148153
rawProps?: LooseRawProps | null,
149154
rawSlots?: LooseRawSlots | null,
150-
): VaporFragment {
155+
): [VaporFragment, VNode] {
151156
const frag = new VaporFragment([])
152157
const vnode = createVNode(
153158
component,
@@ -202,7 +207,7 @@ function createVDOMComponent(
202207

203208
frag.remove = unmount
204209

205-
return frag
210+
return [frag, vnode]
206211
}
207212

208213
/**
@@ -279,11 +284,14 @@ function renderVDOMSlot(
279284
}
280285

281286
export const vaporInteropPlugin: Plugin = app => {
282-
const internals = ensureRenderer().internals
287+
const { internals, hydrateNode } = (
288+
app._ssr ? ensureHydrationRenderer() : ensureRenderer()
289+
) as HydrationRenderer
283290
app._context.vapor = extend(vaporInteropImpl, {
284291
vdomMount: createVDOMComponent.bind(null, internals),
285292
vdomUnmount: internals.umt,
286293
vdomSlot: renderVDOMSlot.bind(null, internals),
294+
vdomHydrate: hydrateNode,
287295
})
288296
const mount = app.mount
289297
app.mount = ((...args) => {

0 commit comments

Comments
 (0)