2020-02-14 12:31:03 +08:00
|
|
|
import {
|
|
|
|
VNode,
|
|
|
|
normalizeVNode,
|
|
|
|
Text,
|
|
|
|
Comment,
|
|
|
|
Static,
|
|
|
|
Fragment,
|
|
|
|
Portal
|
|
|
|
} from './vnode'
|
|
|
|
import { queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
|
|
|
import { ComponentInternalInstance } from './component'
|
|
|
|
import { invokeDirectiveHook } from './directives'
|
|
|
|
import { warn } from './warning'
|
2020-02-15 10:04:08 +08:00
|
|
|
import {
|
|
|
|
PatchFlags,
|
|
|
|
ShapeFlags,
|
|
|
|
isReservedProp,
|
|
|
|
isOn,
|
|
|
|
isString
|
|
|
|
} from '@vue/shared'
|
2020-02-15 05:25:41 +08:00
|
|
|
import { RendererOptions, MountComponentFn } from './renderer'
|
2020-02-14 12:31:03 +08:00
|
|
|
|
2020-02-15 01:33:32 +08:00
|
|
|
export type RootHydrateFunction = (
|
|
|
|
vnode: VNode<Node, Element>,
|
|
|
|
container: Element
|
|
|
|
) => void
|
|
|
|
|
2020-02-14 12:31:03 +08:00
|
|
|
// Note: hydration is DOM-specific
|
2020-02-14 14:30:08 +08:00
|
|
|
// But we have to place it in core due to tight coupling with core - splitting
|
2020-02-14 13:13:54 +08:00
|
|
|
// it out creates a ton of unnecessary complexity.
|
2020-02-14 14:30:08 +08:00
|
|
|
// Hydration also depends on some renderer internal logic which needs to be
|
|
|
|
// passed in via arguments.
|
|
|
|
export function createHydrationFunctions(
|
2020-02-15 05:25:41 +08:00
|
|
|
mountComponent: MountComponentFn<Node, Element>,
|
2020-02-14 16:22:52 +08:00
|
|
|
patchProp: RendererOptions['patchProp']
|
2020-02-14 12:31:03 +08:00
|
|
|
) {
|
2020-02-15 01:33:32 +08:00
|
|
|
const hydrate: RootHydrateFunction = (vnode, container) => {
|
2020-02-14 12:31:03 +08:00
|
|
|
if (__DEV__ && !container.hasChildNodes()) {
|
|
|
|
warn(`Attempting to hydrate existing markup but container is empty.`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hydrateNode(container.firstChild!, vnode)
|
|
|
|
flushPostFlushCbs()
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO handle mismatches
|
2020-02-14 16:22:52 +08:00
|
|
|
const hydrateNode = (
|
2020-02-14 12:31:03 +08:00
|
|
|
node: Node,
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null = null
|
2020-02-14 16:22:52 +08:00
|
|
|
): Node | null | undefined => {
|
2020-02-14 12:31:03 +08:00
|
|
|
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:
|
2020-02-15 10:04:08 +08:00
|
|
|
hydratePortal(vnode, parentComponent)
|
|
|
|
return node.nextSibling
|
2020-02-14 12:31:03 +08:00
|
|
|
default:
|
|
|
|
if (shapeFlag & ShapeFlags.ELEMENT) {
|
|
|
|
return hydrateElement(node as Element, vnode, parentComponent)
|
|
|
|
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
2020-02-15 05:25:41 +08:00
|
|
|
// 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.
|
2020-02-14 12:31:03 +08:00
|
|
|
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) {
|
2020-02-15 10:04:08 +08:00
|
|
|
// TODO Suspense
|
2020-02-14 12:31:03 +08:00
|
|
|
} else if (__DEV__) {
|
|
|
|
warn('Invalid HostVNode type:', type, `(${typeof type})`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 16:22:52 +08:00
|
|
|
const hydrateElement = (
|
2020-02-14 12:31:03 +08:00
|
|
|
el: Element,
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null
|
2020-02-14 16:22:52 +08:00
|
|
|
) => {
|
2020-02-14 12:31:03 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-14 16:22:52 +08:00
|
|
|
const hydrateChildren = (
|
2020-02-14 12:31:03 +08:00
|
|
|
node: Node | null | undefined,
|
|
|
|
vnodes: VNode[],
|
|
|
|
parentComponent: ComponentInternalInstance | null
|
2020-02-14 16:22:52 +08:00
|
|
|
): Node | null | undefined => {
|
2020-02-14 12:31:03 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-15 10:04:08 +08:00
|
|
|
const hydratePortal = (
|
|
|
|
vnode: VNode,
|
|
|
|
parentComponent: ComponentInternalInstance | null
|
|
|
|
) => {
|
|
|
|
const targetSelector = vnode.props && vnode.props.target
|
|
|
|
const target = (vnode.target = isString(targetSelector)
|
|
|
|
? document.querySelector(targetSelector)
|
|
|
|
: targetSelector)
|
|
|
|
if (target != null && vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
|
|
|
hydrateChildren(
|
|
|
|
target.firstChild,
|
|
|
|
vnode.children as VNode[],
|
|
|
|
parentComponent
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 12:31:03 +08:00
|
|
|
return [hydrate, hydrateNode] as const
|
|
|
|
}
|