|
|
@@ -15,7 +15,7 @@ import { ComponentInternalInstance } from './component'
|
|
|
import { invokeDirectiveHook } from './directives'
|
|
|
import { warn } from './warning'
|
|
|
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
|
|
|
-import { RendererInternals } from './renderer'
|
|
|
+import { needTransition, RendererInternals } from './renderer'
|
|
|
import { setRef } from './rendererTemplateRef'
|
|
|
import {
|
|
|
SuspenseImpl,
|
|
|
@@ -146,7 +146,17 @@ export function createHydrationFunctions(
|
|
|
break
|
|
|
case Comment:
|
|
|
if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
|
|
|
- nextNode = onMismatch()
|
|
|
+ if ((node as Element).tagName.toLowerCase() === 'template') {
|
|
|
+ const content = (vnode.el! as HTMLTemplateElement).content
|
|
|
+ .firstChild!
|
|
|
+
|
|
|
+ // replace <template> node with inner children
|
|
|
+ replaceNode(content, node, parentComponent)
|
|
|
+ vnode.el = node = content
|
|
|
+ nextNode = nextSibling(node)
|
|
|
+ } else {
|
|
|
+ nextNode = onMismatch()
|
|
|
+ }
|
|
|
} else {
|
|
|
nextNode = nextSibling(node)
|
|
|
}
|
|
|
@@ -196,9 +206,10 @@ export function createHydrationFunctions(
|
|
|
default:
|
|
|
if (shapeFlag & ShapeFlags.ELEMENT) {
|
|
|
if (
|
|
|
- domType !== DOMNodeTypes.ELEMENT ||
|
|
|
- (vnode.type as string).toLowerCase() !==
|
|
|
- (node as Element).tagName.toLowerCase()
|
|
|
+ (domType !== DOMNodeTypes.ELEMENT ||
|
|
|
+ (vnode.type as string).toLowerCase() !==
|
|
|
+ (node as Element).tagName.toLowerCase()) &&
|
|
|
+ !isTemplateNode(node as Element)
|
|
|
) {
|
|
|
nextNode = onMismatch()
|
|
|
} else {
|
|
|
@@ -217,15 +228,6 @@ export function createHydrationFunctions(
|
|
|
// on its sub-tree.
|
|
|
vnode.slotScopeIds = slotScopeIds
|
|
|
const container = parentNode(node)!
|
|
|
- mountComponent(
|
|
|
- vnode,
|
|
|
- container,
|
|
|
- null,
|
|
|
- parentComponent,
|
|
|
- parentSuspense,
|
|
|
- isSVGContainer(container),
|
|
|
- optimized
|
|
|
- )
|
|
|
|
|
|
// Locate the next node.
|
|
|
if (isFragmentStart) {
|
|
|
@@ -241,6 +243,16 @@ export function createHydrationFunctions(
|
|
|
nextNode = nextSibling(node)
|
|
|
}
|
|
|
|
|
|
+ mountComponent(
|
|
|
+ vnode,
|
|
|
+ container,
|
|
|
+ null,
|
|
|
+ parentComponent,
|
|
|
+ parentSuspense,
|
|
|
+ isSVGContainer(container),
|
|
|
+ optimized
|
|
|
+ )
|
|
|
+
|
|
|
// #3787
|
|
|
// if component is async, it may get moved / unmounted before its
|
|
|
// inner component is loaded, so we need to give it a placeholder
|
|
|
@@ -307,7 +319,7 @@ export function createHydrationFunctions(
|
|
|
optimized: boolean
|
|
|
) => {
|
|
|
optimized = optimized || !!vnode.dynamicChildren
|
|
|
- const { type, props, patchFlag, shapeFlag, dirs } = vnode
|
|
|
+ const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
|
|
|
// #4006 for form elements with non-string v-model value bindings
|
|
|
// e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
|
|
|
const forcePatchValue = (type === 'input' && dirs) || type === 'option'
|
|
|
@@ -359,12 +371,40 @@ export function createHydrationFunctions(
|
|
|
if ((vnodeHooks = props && props.onVnodeBeforeMount)) {
|
|
|
invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
|
|
}
|
|
|
+
|
|
|
+ // handle appear transition
|
|
|
+ let needCallTransitionHooks = false
|
|
|
+ if (isTemplateNode(el)) {
|
|
|
+ needCallTransitionHooks =
|
|
|
+ needTransition(parentSuspense, transition) &&
|
|
|
+ parentComponent &&
|
|
|
+ parentComponent.vnode.props &&
|
|
|
+ parentComponent.vnode.props.appear
|
|
|
+
|
|
|
+ const content = (el as HTMLTemplateElement).content
|
|
|
+ .firstChild as Element
|
|
|
+
|
|
|
+ if (needCallTransitionHooks) {
|
|
|
+ transition!.beforeEnter(content)
|
|
|
+ }
|
|
|
+
|
|
|
+ // replace <template> node with inner children
|
|
|
+ replaceNode(content, el, parentComponent)
|
|
|
+ vnode.el = el = content
|
|
|
+ }
|
|
|
+
|
|
|
if (dirs) {
|
|
|
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
|
|
|
}
|
|
|
- if ((vnodeHooks = props && props.onVnodeMounted) || dirs) {
|
|
|
+
|
|
|
+ if (
|
|
|
+ (vnodeHooks = props && props.onVnodeMounted) ||
|
|
|
+ dirs ||
|
|
|
+ needCallTransitionHooks
|
|
|
+ ) {
|
|
|
queueEffectWithSuspense(() => {
|
|
|
vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
|
|
+ needCallTransitionHooks && transition!.enter(el)
|
|
|
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
|
|
|
}, parentSuspense)
|
|
|
}
|
|
|
@@ -582,5 +622,34 @@ export function createHydrationFunctions(
|
|
|
return node
|
|
|
}
|
|
|
|
|
|
+ const replaceNode = (
|
|
|
+ newNode: Node,
|
|
|
+ oldNode: Node,
|
|
|
+ parentComponent: ComponentInternalInstance | null
|
|
|
+ ): void => {
|
|
|
+ // replace node
|
|
|
+ const parentNode = oldNode.parentNode
|
|
|
+ if (parentNode) {
|
|
|
+ parentNode.replaceChild(newNode, oldNode)
|
|
|
+ }
|
|
|
+
|
|
|
+ // update vnode
|
|
|
+ let parent = parentComponent
|
|
|
+ while (parent) {
|
|
|
+ if (parent.vnode.el === oldNode) {
|
|
|
+ parent.vnode.el = newNode
|
|
|
+ parent.subTree.el = newNode
|
|
|
+ }
|
|
|
+ parent = parent.parent
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const isTemplateNode = (node: Element): boolean => {
|
|
|
+ return (
|
|
|
+ node.nodeType === DOMNodeTypes.ELEMENT &&
|
|
|
+ node.tagName.toLowerCase() === 'template'
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
return [hydrate, hydrateNode] as const
|
|
|
}
|