wip(ssr): component hydration
This commit is contained in:
parent
32d6a46474
commit
42d80b5888
@ -6,7 +6,7 @@ import { RootRenderFunction } from './renderer'
|
|||||||
import { InjectionKey } from './apiInject'
|
import { InjectionKey } from './apiInject'
|
||||||
import { isFunction, NO, isObject } from '@vue/shared'
|
import { isFunction, NO, isObject } from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { createVNode, cloneVNode } from './vnode'
|
import { createVNode, cloneVNode, VNode } from './vnode'
|
||||||
|
|
||||||
export interface App<HostElement = any> {
|
export interface App<HostElement = any> {
|
||||||
config: AppConfig
|
config: AppConfig
|
||||||
@ -16,7 +16,10 @@ export interface App<HostElement = any> {
|
|||||||
component(name: string, component: Component): this
|
component(name: string, component: Component): this
|
||||||
directive(name: string): Directive | undefined
|
directive(name: string): Directive | undefined
|
||||||
directive(name: string, directive: Directive): this
|
directive(name: string, directive: Directive): this
|
||||||
mount(rootContainer: HostElement | string): ComponentPublicInstance
|
mount(
|
||||||
|
rootContainer: HostElement | string,
|
||||||
|
isHydrate?: boolean
|
||||||
|
): ComponentPublicInstance
|
||||||
unmount(rootContainer: HostElement | string): void
|
unmount(rootContainer: HostElement | string): void
|
||||||
provide<T>(key: InjectionKey<T> | string, value: T): this
|
provide<T>(key: InjectionKey<T> | string, value: T): this
|
||||||
|
|
||||||
@ -87,7 +90,8 @@ export type CreateAppFunction<HostElement> = (
|
|||||||
) => App<HostElement>
|
) => App<HostElement>
|
||||||
|
|
||||||
export function createAppAPI<HostNode, HostElement>(
|
export function createAppAPI<HostNode, HostElement>(
|
||||||
render: RootRenderFunction<HostNode, HostElement>
|
render: RootRenderFunction<HostNode, HostElement>,
|
||||||
|
hydrate: (vnode: VNode, container: Element) => void
|
||||||
): CreateAppFunction<HostElement> {
|
): CreateAppFunction<HostElement> {
|
||||||
return function createApp(rootComponent: Component, rootProps = null) {
|
return function createApp(rootComponent: Component, rootProps = null) {
|
||||||
if (rootProps != null && !isObject(rootProps)) {
|
if (rootProps != null && !isObject(rootProps)) {
|
||||||
@ -182,7 +186,7 @@ export function createAppAPI<HostNode, HostElement>(
|
|||||||
return app
|
return app
|
||||||
},
|
},
|
||||||
|
|
||||||
mount(rootContainer: HostElement): any {
|
mount(rootContainer: HostElement, isHydrate?: boolean): any {
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
const vnode = createVNode(rootComponent, rootProps)
|
const vnode = createVNode(rootComponent, rootProps)
|
||||||
// store app context on the root VNode.
|
// store app context on the root VNode.
|
||||||
@ -196,7 +200,11 @@ export function createAppAPI<HostNode, HostElement>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isHydrate) {
|
||||||
|
hydrate(vnode, rootContainer as any)
|
||||||
|
} else {
|
||||||
render(vnode, rootContainer)
|
render(vnode, rootContainer)
|
||||||
|
}
|
||||||
isMounted = true
|
isMounted = true
|
||||||
app._container = rootContainer
|
app._container = rootContainer
|
||||||
return vnode.component!.proxy
|
return vnode.component!.proxy
|
||||||
|
@ -219,10 +219,10 @@ export interface SuspenseBoundary<
|
|||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
setupRenderEffect: (
|
setupRenderEffect: (
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
|
||||||
initialVNode: VNode<HostNode, HostElement>,
|
initialVNode: VNode<HostNode, HostElement>,
|
||||||
container: HostElement,
|
container: HostElement | null,
|
||||||
anchor: HostNode | null,
|
anchor: HostNode | null,
|
||||||
|
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||||
isSVG: boolean
|
isSVG: boolean
|
||||||
) => void
|
) => void
|
||||||
): void
|
): void
|
||||||
@ -419,11 +419,11 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
|||||||
handleSetupResult(instance, asyncSetupResult, suspense)
|
handleSetupResult(instance, asyncSetupResult, suspense)
|
||||||
setupRenderEffect(
|
setupRenderEffect(
|
||||||
instance,
|
instance,
|
||||||
suspense,
|
|
||||||
vnode,
|
vnode,
|
||||||
// component may have been moved before resolve
|
// component may have been moved before resolve
|
||||||
parentNode(instance.subTree.el)!,
|
parentNode(instance.subTree.el)!,
|
||||||
next(instance.subTree),
|
next(instance.subTree),
|
||||||
|
suspense,
|
||||||
isSVG
|
isSVG
|
||||||
)
|
)
|
||||||
updateHOCHostEl(instance, vnode.el)
|
updateHOCHostEl(instance, vnode.el)
|
||||||
|
141
packages/runtime-core/src/hydration.ts
Normal file
141
packages/runtime-core/src/hydration.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
VNode,
|
||||||
|
normalizeVNode,
|
||||||
|
Text,
|
||||||
|
Comment,
|
||||||
|
Static,
|
||||||
|
Fragment,
|
||||||
|
Portal
|
||||||
|
} from './vnode'
|
||||||
|
import { queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||||
|
import { ComponentInternalInstance } from './component'
|
||||||
|
import { invokeDirectiveHook } from './directives'
|
||||||
|
import { ShapeFlags } from './shapeFlags'
|
||||||
|
import { warn } from './warning'
|
||||||
|
import { PatchFlags, isReservedProp, isOn } from '@vue/shared'
|
||||||
|
|
||||||
|
// Note: hydration is DOM-specific
|
||||||
|
// but we have to place it in core due to tight coupling with core renderer
|
||||||
|
// logic - splitting it out
|
||||||
|
export function createHydrateFn(
|
||||||
|
mountComponent: any, // TODO
|
||||||
|
patchProp: any // TODO
|
||||||
|
) {
|
||||||
|
function hydrate(vnode: VNode, container: Element) {
|
||||||
|
if (__DEV__ && !container.hasChildNodes()) {
|
||||||
|
warn(`Attempting to hydrate existing markup but container is empty.`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hydrateNode(container.firstChild!, vnode)
|
||||||
|
flushPostFlushCbs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle mismatches
|
||||||
|
// TODO SVG
|
||||||
|
// TODO Suspense
|
||||||
|
function hydrateNode(
|
||||||
|
node: Node,
|
||||||
|
vnode: VNode,
|
||||||
|
parentComponent: ComponentInternalInstance | null = null
|
||||||
|
): Node | null | undefined {
|
||||||
|
const { type, shapeFlag } = vnode
|
||||||
|
vnode.el = node
|
||||||
|
switch (type) {
|
||||||
|
case Text:
|
||||||
|
case Comment:
|
||||||
|
case Static:
|
||||||
|
return node.nextSibling
|
||||||
|
case Fragment:
|
||||||
|
const anchor = (vnode.anchor = hydrateChildren(
|
||||||
|
node.nextSibling,
|
||||||
|
vnode.children as VNode[],
|
||||||
|
parentComponent
|
||||||
|
)!)
|
||||||
|
// TODO handle potential hydration error if fragment didn't get
|
||||||
|
// back anchor as expected.
|
||||||
|
return anchor.nextSibling
|
||||||
|
case Portal:
|
||||||
|
// TODO
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
if (shapeFlag & ShapeFlags.ELEMENT) {
|
||||||
|
return hydrateElement(node as Element, vnode, parentComponent)
|
||||||
|
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
||||||
|
mountComponent(vnode, null, null, parentComponent, null, false)
|
||||||
|
const subTree = vnode.component!.subTree
|
||||||
|
return (subTree.anchor || subTree.el).nextSibling
|
||||||
|
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
||||||
|
// TODO
|
||||||
|
} else if (__DEV__) {
|
||||||
|
warn('Invalid HostVNode type:', type, `(${typeof type})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hydrateElement(
|
||||||
|
el: Element,
|
||||||
|
vnode: VNode,
|
||||||
|
parentComponent: ComponentInternalInstance | null
|
||||||
|
) {
|
||||||
|
const { props, patchFlag } = vnode
|
||||||
|
// skip props & children if this is hoisted static nodes
|
||||||
|
if (patchFlag !== PatchFlags.HOISTED) {
|
||||||
|
// props
|
||||||
|
if (props !== null) {
|
||||||
|
if (
|
||||||
|
patchFlag & PatchFlags.FULL_PROPS ||
|
||||||
|
patchFlag & PatchFlags.HYDRATE_EVENTS
|
||||||
|
) {
|
||||||
|
for (const key in props) {
|
||||||
|
if (!isReservedProp(key) && isOn(key)) {
|
||||||
|
patchProp(el, key, props[key], null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (props.onClick != null) {
|
||||||
|
// Fast path for click listeners (which is most often) to avoid
|
||||||
|
// iterating through props.
|
||||||
|
patchProp(el, 'onClick', props.onClick, null)
|
||||||
|
}
|
||||||
|
// vnode hooks
|
||||||
|
const { onVnodeBeforeMount, onVnodeMounted } = props
|
||||||
|
if (onVnodeBeforeMount != null) {
|
||||||
|
invokeDirectiveHook(onVnodeBeforeMount, parentComponent, vnode)
|
||||||
|
}
|
||||||
|
if (onVnodeMounted != null) {
|
||||||
|
queuePostFlushCb(() => {
|
||||||
|
invokeDirectiveHook(onVnodeMounted, parentComponent, vnode)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// children
|
||||||
|
if (
|
||||||
|
vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN &&
|
||||||
|
// skip if element has innerHTML / textContent
|
||||||
|
!(props !== null && (props.innerHTML || props.textContent))
|
||||||
|
) {
|
||||||
|
hydrateChildren(
|
||||||
|
el.firstChild,
|
||||||
|
vnode.children as VNode[],
|
||||||
|
parentComponent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return el.nextSibling
|
||||||
|
}
|
||||||
|
|
||||||
|
function hydrateChildren(
|
||||||
|
node: Node | null | undefined,
|
||||||
|
vnodes: VNode[],
|
||||||
|
parentComponent: ComponentInternalInstance | null
|
||||||
|
): Node | null | undefined {
|
||||||
|
for (let i = 0; node != null && i < vnodes.length; i++) {
|
||||||
|
// TODO can skip normalizeVNode in optimized mode
|
||||||
|
// (need hint on rendered markup?)
|
||||||
|
const vnode = (vnodes[i] = normalizeVNode(vnodes[i]))
|
||||||
|
node = hydrateNode(node, vnode, parentComponent)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
return [hydrate, hydrateNode] as const
|
||||||
|
}
|
@ -23,9 +23,8 @@ export {
|
|||||||
openBlock,
|
openBlock,
|
||||||
createBlock
|
createBlock
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
// VNode type symbols
|
|
||||||
export { Text, Comment, Fragment, Portal } from './vnode'
|
|
||||||
// Internal Components
|
// Internal Components
|
||||||
|
export { Fragment, Portal } from './vnode'
|
||||||
export { Suspense, SuspenseProps } from './components/Suspense'
|
export { Suspense, SuspenseProps } from './components/Suspense'
|
||||||
export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
|
export { KeepAlive, KeepAliveProps } from './components/KeepAlive'
|
||||||
export {
|
export {
|
||||||
|
@ -30,8 +30,7 @@ import {
|
|||||||
isReservedProp,
|
isReservedProp,
|
||||||
isFunction,
|
isFunction,
|
||||||
PatchFlags,
|
PatchFlags,
|
||||||
NOOP,
|
NOOP
|
||||||
isOn
|
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
queueJob,
|
queueJob,
|
||||||
@ -54,7 +53,7 @@ import { ShapeFlags } from './shapeFlags'
|
|||||||
import { pushWarningContext, popWarningContext, warn } from './warning'
|
import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
import { ComponentPublicInstance } from './componentProxy'
|
import { ComponentPublicInstance } from './componentProxy'
|
||||||
import { createAppAPI, CreateAppFunction } from './apiCreateApp'
|
import { createAppAPI } from './apiCreateApp'
|
||||||
import {
|
import {
|
||||||
SuspenseBoundary,
|
SuspenseBoundary,
|
||||||
queueEffectWithSuspense,
|
queueEffectWithSuspense,
|
||||||
@ -63,6 +62,7 @@ import {
|
|||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
|
||||||
import { registerHMR, unregisterHMR } from './hmr'
|
import { registerHMR, unregisterHMR } from './hmr'
|
||||||
|
import { createHydrateFn } from './hydration'
|
||||||
|
|
||||||
const __HMR__ = __BUNDLER__ && __DEV__
|
const __HMR__ = __BUNDLER__ && __DEV__
|
||||||
|
|
||||||
@ -185,13 +185,7 @@ export const queuePostRenderEffect = __FEATURE_SUSPENSE__
|
|||||||
export function createRenderer<
|
export function createRenderer<
|
||||||
HostNode extends object = any,
|
HostNode extends object = any,
|
||||||
HostElement extends HostNode = any
|
HostElement extends HostNode = any
|
||||||
>(
|
>(options: RendererOptions<HostNode, HostElement>) {
|
||||||
options: RendererOptions<HostNode, HostElement>
|
|
||||||
): {
|
|
||||||
render: RootRenderFunction<HostNode, HostElement>
|
|
||||||
hydrate: RootRenderFunction<HostNode, HostElement>
|
|
||||||
createApp: CreateAppFunction<HostElement>
|
|
||||||
} {
|
|
||||||
type HostVNode = VNode<HostNode, HostElement>
|
type HostVNode = VNode<HostNode, HostElement>
|
||||||
type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
|
type HostVNodeChildren = VNodeArrayChildren<HostNode, HostElement>
|
||||||
type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
|
type HostSuspenseBoundary = SuspenseBoundary<HostNode, HostElement>
|
||||||
@ -984,7 +978,7 @@ export function createRenderer<
|
|||||||
|
|
||||||
function mountComponent(
|
function mountComponent(
|
||||||
initialVNode: HostVNode,
|
initialVNode: HostVNode,
|
||||||
container: HostElement,
|
container: HostElement | null, // only null during hydration
|
||||||
anchor: HostNode | null,
|
anchor: HostNode | null,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
parentSuspense: HostSuspenseBoundary | null,
|
||||||
@ -1023,19 +1017,19 @@ export function createRenderer<
|
|||||||
|
|
||||||
parentSuspense.registerDep(instance, setupRenderEffect)
|
parentSuspense.registerDep(instance, setupRenderEffect)
|
||||||
|
|
||||||
// give it a placeholder
|
// Give it a placeholder if this is not hydration
|
||||||
const placeholder = (instance.subTree = createVNode(Comment))
|
const placeholder = (instance.subTree = createVNode(Comment))
|
||||||
processCommentNode(null, placeholder, container, anchor)
|
processCommentNode(null, placeholder, container!, anchor)
|
||||||
initialVNode.el = placeholder.el
|
initialVNode.el = placeholder.el
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setupRenderEffect(
|
setupRenderEffect(
|
||||||
instance,
|
instance,
|
||||||
parentSuspense,
|
|
||||||
initialVNode,
|
initialVNode,
|
||||||
container,
|
container,
|
||||||
anchor,
|
anchor,
|
||||||
|
parentSuspense,
|
||||||
isSVG
|
isSVG
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1046,10 +1040,10 @@ export function createRenderer<
|
|||||||
|
|
||||||
function setupRenderEffect(
|
function setupRenderEffect(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
parentSuspense: HostSuspenseBoundary | null,
|
|
||||||
initialVNode: HostVNode,
|
initialVNode: HostVNode,
|
||||||
container: HostElement,
|
container: HostElement | null, // only null during hydration
|
||||||
anchor: HostNode | null,
|
anchor: HostNode | null,
|
||||||
|
parentSuspense: HostSuspenseBoundary | null,
|
||||||
isSVG: boolean
|
isSVG: boolean
|
||||||
) {
|
) {
|
||||||
// create reactive effect for rendering
|
// create reactive effect for rendering
|
||||||
@ -1060,8 +1054,21 @@ export function createRenderer<
|
|||||||
if (instance.bm !== null) {
|
if (instance.bm !== null) {
|
||||||
invokeHooks(instance.bm)
|
invokeHooks(instance.bm)
|
||||||
}
|
}
|
||||||
patch(null, subTree, container, anchor, instance, parentSuspense, isSVG)
|
if (initialVNode.el) {
|
||||||
|
// vnode has adopted host node - perform hydration instead of mount.
|
||||||
|
hydrateNode(initialVNode.el as Node, subTree, instance)
|
||||||
|
} else {
|
||||||
|
patch(
|
||||||
|
null,
|
||||||
|
subTree,
|
||||||
|
container!, // container is only null during hydration
|
||||||
|
anchor,
|
||||||
|
instance,
|
||||||
|
parentSuspense,
|
||||||
|
isSVG
|
||||||
|
)
|
||||||
initialVNode.el = subTree.el
|
initialVNode.el = subTree.el
|
||||||
|
}
|
||||||
// mounted hook
|
// mounted hook
|
||||||
if (instance.m !== null) {
|
if (instance.m !== null) {
|
||||||
queuePostRenderEffect(instance.m, parentSuspense)
|
queuePostRenderEffect(instance.m, parentSuspense)
|
||||||
@ -1816,119 +1823,12 @@ export function createRenderer<
|
|||||||
container._vnode = vnode
|
container._vnode = vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydrate(vnode: HostVNode, container: any) {
|
const [hydrate, hydrateNode] = createHydrateFn(mountComponent, hostPatchProp)
|
||||||
hydrateNode(container.firstChild, vnode, container)
|
|
||||||
flushPostFlushCbs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle mismatches
|
|
||||||
function hydrateNode(
|
|
||||||
node: any,
|
|
||||||
vnode: HostVNode,
|
|
||||||
container: any,
|
|
||||||
parentComponent: ComponentInternalInstance | null = null
|
|
||||||
): any {
|
|
||||||
const { type, shapeFlag } = vnode
|
|
||||||
switch (type) {
|
|
||||||
case Text:
|
|
||||||
case Comment:
|
|
||||||
case Static:
|
|
||||||
vnode.el = node
|
|
||||||
return node.nextSibling
|
|
||||||
case Fragment:
|
|
||||||
vnode.el = node
|
|
||||||
const anchor = (vnode.anchor = hydrateChildren(
|
|
||||||
node.nextSibling,
|
|
||||||
vnode.children as HostVNode[],
|
|
||||||
container,
|
|
||||||
parentComponent
|
|
||||||
))
|
|
||||||
return anchor.nextSibling
|
|
||||||
case Portal:
|
|
||||||
// TODO
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
if (shapeFlag & ShapeFlags.ELEMENT) {
|
|
||||||
return hydrateElement(node, vnode, parentComponent)
|
|
||||||
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
|
||||||
// TODO
|
|
||||||
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
|
||||||
// TODO
|
|
||||||
} else if (__DEV__) {
|
|
||||||
warn('Invalid HostVNode type:', type, `(${typeof type})`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hydrateElement(
|
|
||||||
el: any,
|
|
||||||
vnode: HostVNode,
|
|
||||||
parentComponent: ComponentInternalInstance | null
|
|
||||||
) {
|
|
||||||
vnode.el = el
|
|
||||||
const { props, patchFlag } = vnode
|
|
||||||
// skip props & children if this is hoisted static nodes
|
|
||||||
if (patchFlag !== PatchFlags.HOISTED) {
|
|
||||||
// props
|
|
||||||
if (props !== null) {
|
|
||||||
if (
|
|
||||||
patchFlag & PatchFlags.FULL_PROPS ||
|
|
||||||
patchFlag & PatchFlags.HYDRATE_EVENTS
|
|
||||||
) {
|
|
||||||
for (const key in props) {
|
|
||||||
if (!isReservedProp(key) && isOn(key)) {
|
|
||||||
hostPatchProp(el, key, props[key], null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (props.onClick != null) {
|
|
||||||
// Fast path for click listeners (which is most often) to avoid
|
|
||||||
// iterating through props.
|
|
||||||
hostPatchProp(el, 'onClick', props.onClick, null)
|
|
||||||
}
|
|
||||||
// vnode mounted hook
|
|
||||||
const { onVnodeMounted } = props
|
|
||||||
if (onVnodeMounted != null) {
|
|
||||||
queuePostFlushCb(() => {
|
|
||||||
invokeDirectiveHook(onVnodeMounted, parentComponent, vnode, null)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// children
|
|
||||||
if (
|
|
||||||
vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN &&
|
|
||||||
// skip if element has innerHTML / textContent
|
|
||||||
!(props !== null && (props.innerHTML || props.textContent))
|
|
||||||
) {
|
|
||||||
hydrateChildren(
|
|
||||||
el.firstChild,
|
|
||||||
vnode.children as HostVNode[],
|
|
||||||
el,
|
|
||||||
parentComponent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return el.nextSibling
|
|
||||||
}
|
|
||||||
|
|
||||||
function hydrateChildren(
|
|
||||||
node: any,
|
|
||||||
vnodes: HostVNode[],
|
|
||||||
container: any,
|
|
||||||
parentComponent: ComponentInternalInstance | null = null
|
|
||||||
) {
|
|
||||||
for (let i = 0; i < vnodes.length; i++) {
|
|
||||||
// TODO can skip normalizeVNode in optimized mode
|
|
||||||
// (need hint on rendered markup?)
|
|
||||||
const vnode = (vnodes[i] = normalizeVNode(vnodes[i]))
|
|
||||||
node = hydrateNode(node, vnode, container, parentComponent)
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
render,
|
render,
|
||||||
hydrate,
|
hydrate,
|
||||||
createApp: createAppAPI(render)
|
createApp: createAppAPI<HostNode, HostElement>(render, hydrate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export const createApp: CreateAppFunction<Element> = (...args) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { mount } = app
|
const { mount } = app
|
||||||
app.mount = (container): any => {
|
app.mount = (container: Element | string): any => {
|
||||||
if (isString(container)) {
|
if (isString(container)) {
|
||||||
container = document.querySelector(container)!
|
container = document.querySelector(container)!
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@ -53,9 +53,12 @@ export const createApp: CreateAppFunction<Element> = (...args) => {
|
|||||||
) {
|
) {
|
||||||
component.template = container.innerHTML
|
component.template = container.innerHTML
|
||||||
}
|
}
|
||||||
|
const isHydrate = container.hasAttribute('data-server-rendered')
|
||||||
|
if (!isHydrate) {
|
||||||
// clear content before mounting
|
// clear content before mounting
|
||||||
container.innerHTML = ''
|
container.innerHTML = ''
|
||||||
return mount(container)
|
}
|
||||||
|
return mount(container, isHydrate)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
Loading…
Reference in New Issue
Block a user