|
|
@@ -14,7 +14,20 @@ import { flushPostFlushCbs } from './scheduler'
|
|
|
import { ComponentInternalInstance } from './component'
|
|
|
import { invokeDirectiveHook } from './directives'
|
|
|
import { warn } from './warning'
|
|
|
-import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
|
|
|
+import {
|
|
|
+ PatchFlags,
|
|
|
+ ShapeFlags,
|
|
|
+ isReservedProp,
|
|
|
+ isOn,
|
|
|
+ normalizeClass,
|
|
|
+ normalizeStyle,
|
|
|
+ stringifyStyle,
|
|
|
+ isBooleanAttr,
|
|
|
+ isString,
|
|
|
+ includeBooleanAttr,
|
|
|
+ isKnownHtmlAttr,
|
|
|
+ isKnownSvgAttr
|
|
|
+} from '@vue/shared'
|
|
|
import { needTransition, RendererInternals } from './renderer'
|
|
|
import { setRef } from './rendererTemplateRef'
|
|
|
import {
|
|
|
@@ -148,11 +161,12 @@ export function createHydrationFunctions(
|
|
|
hasMismatch = true
|
|
|
__DEV__ &&
|
|
|
warn(
|
|
|
- `Hydration text mismatch:` +
|
|
|
- `\n- Server rendered: ${JSON.stringify(
|
|
|
+ `Hydration text mismatch in`,
|
|
|
+ node.parentNode,
|
|
|
+ `\n - rendered on server: ${JSON.stringify(vnode.children)}` +
|
|
|
+ `\n - expected on client: ${JSON.stringify(
|
|
|
(node as Text).data
|
|
|
- )}` +
|
|
|
- `\n- Client rendered: ${JSON.stringify(vnode.children)}`
|
|
|
+ )}`
|
|
|
)
|
|
|
;(node as Text).data = vnode.children as string
|
|
|
}
|
|
|
@@ -344,51 +358,6 @@ export function createHydrationFunctions(
|
|
|
if (dirs) {
|
|
|
invokeDirectiveHook(vnode, null, parentComponent, 'created')
|
|
|
}
|
|
|
- // props
|
|
|
- if (props) {
|
|
|
- if (
|
|
|
- forcePatch ||
|
|
|
- !optimized ||
|
|
|
- patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
|
|
- ) {
|
|
|
- for (const key in props) {
|
|
|
- if (
|
|
|
- (forcePatch &&
|
|
|
- (key.endsWith('value') || key === 'indeterminate')) ||
|
|
|
- (isOn(key) && !isReservedProp(key)) ||
|
|
|
- // force hydrate v-bind with .prop modifiers
|
|
|
- key[0] === '.'
|
|
|
- ) {
|
|
|
- patchProp(
|
|
|
- el,
|
|
|
- key,
|
|
|
- null,
|
|
|
- props[key],
|
|
|
- false,
|
|
|
- undefined,
|
|
|
- parentComponent
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- } else if (props.onClick) {
|
|
|
- // Fast path for click listeners (which is most often) to avoid
|
|
|
- // iterating through props.
|
|
|
- patchProp(
|
|
|
- el,
|
|
|
- 'onClick',
|
|
|
- null,
|
|
|
- props.onClick,
|
|
|
- false,
|
|
|
- undefined,
|
|
|
- parentComponent
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- // vnode / directive hooks
|
|
|
- let vnodeHooks: VNodeHook | null | undefined
|
|
|
- if ((vnodeHooks = props && props.onVnodeBeforeMount)) {
|
|
|
- invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
|
|
- }
|
|
|
|
|
|
// handle appear transition
|
|
|
let needCallTransitionHooks = false
|
|
|
@@ -411,21 +380,6 @@ export function createHydrationFunctions(
|
|
|
vnode.el = el = content
|
|
|
}
|
|
|
|
|
|
- if (dirs) {
|
|
|
- invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
|
|
|
- }
|
|
|
-
|
|
|
- if (
|
|
|
- (vnodeHooks = props && props.onVnodeMounted) ||
|
|
|
- dirs ||
|
|
|
- needCallTransitionHooks
|
|
|
- ) {
|
|
|
- queueEffectWithSuspense(() => {
|
|
|
- vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
|
|
- needCallTransitionHooks && transition!.enter(el)
|
|
|
- dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
|
|
|
- }, parentSuspense)
|
|
|
- }
|
|
|
// children
|
|
|
if (
|
|
|
shapeFlag & ShapeFlags.ARRAY_CHILDREN &&
|
|
|
@@ -446,8 +400,9 @@ export function createHydrationFunctions(
|
|
|
hasMismatch = true
|
|
|
if (__DEV__ && !hasWarned) {
|
|
|
warn(
|
|
|
- `Hydration children mismatch in <${vnode.type as string}>: ` +
|
|
|
- `server rendered element contains more child nodes than client vdom.`
|
|
|
+ `Hydration children mismatch on`,
|
|
|
+ el,
|
|
|
+ `\nServer rendered element contains more child nodes than client vdom.`
|
|
|
)
|
|
|
hasWarned = true
|
|
|
}
|
|
|
@@ -461,16 +416,82 @@ export function createHydrationFunctions(
|
|
|
hasMismatch = true
|
|
|
__DEV__ &&
|
|
|
warn(
|
|
|
- `Hydration text content mismatch in <${
|
|
|
- vnode.type as string
|
|
|
- }>:\n` +
|
|
|
- `- Server rendered: ${el.textContent}\n` +
|
|
|
- `- Client rendered: ${vnode.children as string}`
|
|
|
+ `Hydration text content mismatch on`,
|
|
|
+ el,
|
|
|
+ `\n - rendered on server: ${vnode.children as string}` +
|
|
|
+ `\n - expected on client: ${el.textContent}`
|
|
|
)
|
|
|
el.textContent = vnode.children as string
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // props
|
|
|
+ if (props) {
|
|
|
+ if (
|
|
|
+ __DEV__ ||
|
|
|
+ forcePatch ||
|
|
|
+ !optimized ||
|
|
|
+ patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
|
|
+ ) {
|
|
|
+ for (const key in props) {
|
|
|
+ // check hydration mismatch
|
|
|
+ if (__DEV__ && propHasMismatch(el, key, props[key])) {
|
|
|
+ hasMismatch = true
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ (forcePatch &&
|
|
|
+ (key.endsWith('value') || key === 'indeterminate')) ||
|
|
|
+ (isOn(key) && !isReservedProp(key)) ||
|
|
|
+ // force hydrate v-bind with .prop modifiers
|
|
|
+ key[0] === '.'
|
|
|
+ ) {
|
|
|
+ patchProp(
|
|
|
+ el,
|
|
|
+ key,
|
|
|
+ null,
|
|
|
+ props[key],
|
|
|
+ false,
|
|
|
+ undefined,
|
|
|
+ parentComponent
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (props.onClick) {
|
|
|
+ // Fast path for click listeners (which is most often) to avoid
|
|
|
+ // iterating through props.
|
|
|
+ patchProp(
|
|
|
+ el,
|
|
|
+ 'onClick',
|
|
|
+ null,
|
|
|
+ props.onClick,
|
|
|
+ false,
|
|
|
+ undefined,
|
|
|
+ parentComponent
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // vnode / directive hooks
|
|
|
+ let vnodeHooks: VNodeHook | null | undefined
|
|
|
+ if ((vnodeHooks = props && props.onVnodeBeforeMount)) {
|
|
|
+ invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
|
|
+ }
|
|
|
+ if (dirs) {
|
|
|
+ invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ (vnodeHooks = props && props.onVnodeMounted) ||
|
|
|
+ dirs ||
|
|
|
+ needCallTransitionHooks
|
|
|
+ ) {
|
|
|
+ queueEffectWithSuspense(() => {
|
|
|
+ vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
|
|
+ needCallTransitionHooks && transition!.enter(el)
|
|
|
+ dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
|
|
|
+ }, parentSuspense)
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
return el.nextSibling
|
|
|
}
|
|
|
|
|
|
@@ -506,8 +527,9 @@ export function createHydrationFunctions(
|
|
|
hasMismatch = true
|
|
|
if (__DEV__ && !hasWarned) {
|
|
|
warn(
|
|
|
- `Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
|
|
|
- `server rendered element contains fewer child nodes than client vdom.`
|
|
|
+ `Hydration children mismatch on`,
|
|
|
+ container,
|
|
|
+ `\nServer rendered element contains fewer child nodes than client vdom.`
|
|
|
)
|
|
|
hasWarned = true
|
|
|
}
|
|
|
@@ -670,3 +692,58 @@ export function createHydrationFunctions(
|
|
|
|
|
|
return [hydrate, hydrateNode] as const
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Dev only
|
|
|
+ */
|
|
|
+function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
|
|
|
+ let mismatchType: string | undefined
|
|
|
+ let mismatchKey: string | undefined
|
|
|
+ let actual: any
|
|
|
+ let expected: any
|
|
|
+ if (key === 'class') {
|
|
|
+ actual = el.className
|
|
|
+ expected = normalizeClass(clientValue)
|
|
|
+ if (actual !== expected) {
|
|
|
+ mismatchType = mismatchKey = `class`
|
|
|
+ }
|
|
|
+ } else if (key === 'style') {
|
|
|
+ actual = el.getAttribute('style')
|
|
|
+ expected = isString(clientValue)
|
|
|
+ ? clientValue
|
|
|
+ : stringifyStyle(normalizeStyle(clientValue))
|
|
|
+ if (actual !== expected) {
|
|
|
+ mismatchType = mismatchKey = 'style'
|
|
|
+ }
|
|
|
+ } else if (
|
|
|
+ (el instanceof SVGElement && isKnownSvgAttr(key)) ||
|
|
|
+ (el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key)))
|
|
|
+ ) {
|
|
|
+ actual = el.hasAttribute(key) && el.getAttribute(key)
|
|
|
+ expected = isBooleanAttr(key)
|
|
|
+ ? includeBooleanAttr(clientValue)
|
|
|
+ ? ''
|
|
|
+ : false
|
|
|
+ : String(clientValue)
|
|
|
+ if (actual !== expected) {
|
|
|
+ mismatchType = `attribute`
|
|
|
+ mismatchKey = key
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mismatchType) {
|
|
|
+ const format = (v: any) =>
|
|
|
+ v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
|
|
|
+ warn(
|
|
|
+ `Hydration ${mismatchType} mismatch on`,
|
|
|
+ el,
|
|
|
+ `\n - rendered on server: ${format(actual)}` +
|
|
|
+ `\n - expected on client: ${format(expected)}` +
|
|
|
+ `\n Note: this mismatch is check-only. The DOM will not be rectified ` +
|
|
|
+ `in production due to performance overhead.` +
|
|
|
+ `\n You should fix the source of the mismatch.`
|
|
|
+ )
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|