Skip to content

Commit

Permalink
Update hydration.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 authored Dec 8, 2023
1 parent a4218e3 commit 4fa7ed3
Showing 1 changed file with 126 additions and 37 deletions.
163 changes: 126 additions & 37 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -111,6 +111,21 @@ export function createHydrationFunctions(
let domType = node.nodeType
vnode.el = node

if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
if (!('__vnode' in node)) {
Object.defineProperty(node, '__vnode', {
value: vnode,
enumerable: false
})
}
if (!('__vueParentComponent' in node)) {
Object.defineProperty(node, '__vueParentComponent', {
value: parentComponent,
enumerable: false
})
}
}

if (patchFlag === PatchFlags.BAIL) {
optimized = false
vnode.dynamicChildren = null
Expand Down Expand Up @@ -145,7 +160,16 @@ export function createHydrationFunctions(
}
break
case Comment:
if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
if (isTemplateNode(node)) {
nextNode = nextSibling(node)
// wrapped <transition appear>
// replace <template> node with inner child
replaceNode(
(vnode.el = node.content.firstChild!),
node,
parentComponent
)
} else if (domType !== DOMNodeTypes.COMMENT || isFragmentStart) {
nextNode = onMismatch()
} else {
nextNode = nextSibling(node)
Expand Down Expand Up @@ -196,9 +220,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)
) {
nextNode = onMismatch()
} else {
Expand All @@ -217,6 +242,21 @@ export function createHydrationFunctions(
// on its sub-tree.
vnode.slotScopeIds = slotScopeIds
const container = parentNode(node)!

// Locate the next node.
if (isFragmentStart) {
// If it's a fragment: since components may be async, we cannot rely
// on component's rendered output to determine the end of the
// fragment. Instead, we do a lookahead to find the end anchor node.
nextNode = locateClosingAnchor(node)
} else if (isComment(node) && node.data === 'teleport start') {
// #4293 #6152
// If a teleport is at component root, look ahead for teleport end.
nextNode = locateClosingAnchor(node, node.data, 'teleport end')
} else {
nextNode = nextSibling(node)
}

mountComponent(
vnode,
container,
Expand All @@ -227,22 +267,6 @@ export function createHydrationFunctions(
optimized
)

// component may be async, so in the case of fragments we cannot rely
// on component's rendered output to determine the end of the fragment
// instead, we do a lookahead to find the end anchor node.
nextNode = isFragmentStart
? locateClosingAsyncAnchor(node)
: nextSibling(node)

// #4293 teleport as component root
if (
nextNode &&
isComment(nextNode) &&
nextNode.data === 'teleport end'
) {
nextNode = nextSibling(nextNode)
}

// #3787
// if component is async, it may get moved / unmounted before its
// inner component is loaded, so we need to give it a placeholder
Expand Down Expand Up @@ -309,27 +333,31 @@ 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'
// #7476 <input indeterminate>
const forcePatch = type === 'input' || type === 'option'
// skip props & children if this is hoisted static nodes
// #5405 in dev, always hydrate children for HMR
if (__DEV__ || forcePatchValue || patchFlag !== PatchFlags.HOISTED) {
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.HOISTED) {
if (dirs) {
invokeDirectiveHook(vnode, null, parentComponent, 'created')
}
// props
if (props) {
if (
forcePatchValue ||
forcePatch ||
!optimized ||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.HYDRATE_EVENTS)
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
) {
for (const key in props) {
if (
(forcePatchValue && key.endsWith('value')) ||
(isOn(key) && !isReservedProp(key))
(forcePatch &&
(key.endsWith('value') || key === 'indeterminate')) ||
(isOn(key) && !isReservedProp(key)) ||
// force hydrate v-bind with .prop modifiers
key[0] === '.'
) {
patchProp(
el,
Expand Down Expand Up @@ -361,12 +389,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)
}
Expand Down Expand Up @@ -520,20 +576,20 @@ export function createHydrationFunctions(
__DEV__ &&
warn(
`Hydration node mismatch:\n- Client vnode:`,
node,
`\n- Server rendered DOM:`,
vnode.type,
`\n- Server rendered DOM:`,
node,
node.nodeType === DOMNodeTypes.TEXT
? `(text)`
: isComment(node) && node.data === '['
? `(start of fragment)`
: ``
? `(start of fragment)`
: ``
)
vnode.el = null

if (isFragment) {
// remove excessive fragment nodes
const end = locateClosingAsyncAnchor(node)
const end = locateClosingAnchor(node)
while (true) {
const next = nextSibling(node)
if (next && next !== end) {
Expand Down Expand Up @@ -561,13 +617,18 @@ export function createHydrationFunctions(
return next
}

const locateClosingAsyncAnchor = (node: Node | null): Node | null => {
// looks ahead for a start and closing comment node
const locateClosingAnchor = (
node: Node | null,
open = '[',
close = ']'
): Node | null => {
let match = 0
while (node) {
node = nextSibling(node)
if (node && isComment(node)) {
if (node.data === '[') match++
if (node.data === ']') {
if (node.data === open) match++
if (node.data === close) {
if (match === 0) {
return nextSibling(node)
} else {
Expand All @@ -579,5 +640,33 @@ 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 = parent.subTree.el = newNode
}
parent = parent.parent
}
}

const isTemplateNode = (node: Node): node is HTMLTemplateElement => {
return (
node.nodeType === DOMNodeTypes.ELEMENT &&
(node as Element).tagName.toLowerCase() === 'template'
)
}

return [hydrate, hydrateNode] as const
}

0 comments on commit 4fa7ed3

Please sign in to comment.