@@ -14,7 +14,20 @@ import { flushPostFlushCbs } from './scheduler'
14
14
import { ComponentInternalInstance } from './component'
15
15
import { invokeDirectiveHook } from './directives'
16
16
import { warn } from './warning'
17
- import { PatchFlags , ShapeFlags , isReservedProp , isOn } from '@vue/shared'
17
+ import {
18
+ PatchFlags ,
19
+ ShapeFlags ,
20
+ isReservedProp ,
21
+ isOn ,
22
+ normalizeClass ,
23
+ normalizeStyle ,
24
+ stringifyStyle ,
25
+ isBooleanAttr ,
26
+ isString ,
27
+ includeBooleanAttr ,
28
+ isKnownHtmlAttr ,
29
+ isKnownSvgAttr
30
+ } from '@vue/shared'
18
31
import { needTransition , RendererInternals } from './renderer'
19
32
import { setRef } from './rendererTemplateRef'
20
33
import {
@@ -148,11 +161,12 @@ export function createHydrationFunctions(
148
161
hasMismatch = true
149
162
__DEV__ &&
150
163
warn (
151
- `Hydration text mismatch:` +
152
- `\n- Server rendered: ${ JSON . stringify (
164
+ `Hydration text mismatch in` ,
165
+ node . parentNode ,
166
+ `\n - rendered on server: ${ JSON . stringify ( vnode . children ) } ` +
167
+ `\n - expected on client: ${ JSON . stringify (
153
168
( node as Text ) . data
154
- ) } ` +
155
- `\n- Client rendered: ${ JSON . stringify ( vnode . children ) } `
169
+ ) } `
156
170
)
157
171
; ( node as Text ) . data = vnode . children as string
158
172
}
@@ -344,51 +358,6 @@ export function createHydrationFunctions(
344
358
if ( dirs ) {
345
359
invokeDirectiveHook ( vnode , null , parentComponent , 'created' )
346
360
}
347
- // props
348
- if ( props ) {
349
- if (
350
- forcePatch ||
351
- ! optimized ||
352
- patchFlag & ( PatchFlags . FULL_PROPS | PatchFlags . NEED_HYDRATION )
353
- ) {
354
- for ( const key in props ) {
355
- if (
356
- ( forcePatch &&
357
- ( key . endsWith ( 'value' ) || key === 'indeterminate' ) ) ||
358
- ( isOn ( key ) && ! isReservedProp ( key ) ) ||
359
- // force hydrate v-bind with .prop modifiers
360
- key [ 0 ] === '.'
361
- ) {
362
- patchProp (
363
- el ,
364
- key ,
365
- null ,
366
- props [ key ] ,
367
- false ,
368
- undefined ,
369
- parentComponent
370
- )
371
- }
372
- }
373
- } else if ( props . onClick ) {
374
- // Fast path for click listeners (which is most often) to avoid
375
- // iterating through props.
376
- patchProp (
377
- el ,
378
- 'onClick' ,
379
- null ,
380
- props . onClick ,
381
- false ,
382
- undefined ,
383
- parentComponent
384
- )
385
- }
386
- }
387
- // vnode / directive hooks
388
- let vnodeHooks : VNodeHook | null | undefined
389
- if ( ( vnodeHooks = props && props . onVnodeBeforeMount ) ) {
390
- invokeVNodeHook ( vnodeHooks , parentComponent , vnode )
391
- }
392
361
393
362
// handle appear transition
394
363
let needCallTransitionHooks = false
@@ -411,21 +380,6 @@ export function createHydrationFunctions(
411
380
vnode . el = el = content
412
381
}
413
382
414
- if ( dirs ) {
415
- invokeDirectiveHook ( vnode , null , parentComponent , 'beforeMount' )
416
- }
417
-
418
- if (
419
- ( vnodeHooks = props && props . onVnodeMounted ) ||
420
- dirs ||
421
- needCallTransitionHooks
422
- ) {
423
- queueEffectWithSuspense ( ( ) => {
424
- vnodeHooks && invokeVNodeHook ( vnodeHooks , parentComponent , vnode )
425
- needCallTransitionHooks && transition ! . enter ( el )
426
- dirs && invokeDirectiveHook ( vnode , null , parentComponent , 'mounted' )
427
- } , parentSuspense )
428
- }
429
383
// children
430
384
if (
431
385
shapeFlag & ShapeFlags . ARRAY_CHILDREN &&
@@ -446,8 +400,9 @@ export function createHydrationFunctions(
446
400
hasMismatch = true
447
401
if ( __DEV__ && ! hasWarned ) {
448
402
warn (
449
- `Hydration children mismatch in <${ vnode . type as string } >: ` +
450
- `server rendered element contains more child nodes than client vdom.`
403
+ `Hydration children mismatch on` ,
404
+ el ,
405
+ `\nServer rendered element contains more child nodes than client vdom.`
451
406
)
452
407
hasWarned = true
453
408
}
@@ -461,16 +416,82 @@ export function createHydrationFunctions(
461
416
hasMismatch = true
462
417
__DEV__ &&
463
418
warn (
464
- `Hydration text content mismatch in <${
465
- vnode . type as string
466
- } >:\n` +
467
- `- Server rendered: ${ el . textContent } \n` +
468
- `- Client rendered: ${ vnode . children as string } `
419
+ `Hydration text content mismatch on` ,
420
+ el ,
421
+ `\n - rendered on server: ${ vnode . children as string } ` +
422
+ `\n - expected on client: ${ el . textContent } `
469
423
)
470
424
el . textContent = vnode . children as string
471
425
}
472
426
}
427
+
428
+ // props
429
+ if ( props ) {
430
+ if (
431
+ __DEV__ ||
432
+ forcePatch ||
433
+ ! optimized ||
434
+ patchFlag & ( PatchFlags . FULL_PROPS | PatchFlags . NEED_HYDRATION )
435
+ ) {
436
+ for ( const key in props ) {
437
+ // check hydration mismatch
438
+ if ( __DEV__ && propHasMismatch ( el , key , props [ key ] ) ) {
439
+ hasMismatch = true
440
+ }
441
+ if (
442
+ ( forcePatch &&
443
+ ( key . endsWith ( 'value' ) || key === 'indeterminate' ) ) ||
444
+ ( isOn ( key ) && ! isReservedProp ( key ) ) ||
445
+ // force hydrate v-bind with .prop modifiers
446
+ key [ 0 ] === '.'
447
+ ) {
448
+ patchProp (
449
+ el ,
450
+ key ,
451
+ null ,
452
+ props [ key ] ,
453
+ false ,
454
+ undefined ,
455
+ parentComponent
456
+ )
457
+ }
458
+ }
459
+ } else if ( props . onClick ) {
460
+ // Fast path for click listeners (which is most often) to avoid
461
+ // iterating through props.
462
+ patchProp (
463
+ el ,
464
+ 'onClick' ,
465
+ null ,
466
+ props . onClick ,
467
+ false ,
468
+ undefined ,
469
+ parentComponent
470
+ )
471
+ }
472
+ }
473
+
474
+ // vnode / directive hooks
475
+ let vnodeHooks : VNodeHook | null | undefined
476
+ if ( ( vnodeHooks = props && props . onVnodeBeforeMount ) ) {
477
+ invokeVNodeHook ( vnodeHooks , parentComponent , vnode )
478
+ }
479
+ if ( dirs ) {
480
+ invokeDirectiveHook ( vnode , null , parentComponent , 'beforeMount' )
481
+ }
482
+ if (
483
+ ( vnodeHooks = props && props . onVnodeMounted ) ||
484
+ dirs ||
485
+ needCallTransitionHooks
486
+ ) {
487
+ queueEffectWithSuspense ( ( ) => {
488
+ vnodeHooks && invokeVNodeHook ( vnodeHooks , parentComponent , vnode )
489
+ needCallTransitionHooks && transition ! . enter ( el )
490
+ dirs && invokeDirectiveHook ( vnode , null , parentComponent , 'mounted' )
491
+ } , parentSuspense )
492
+ }
473
493
}
494
+
474
495
return el . nextSibling
475
496
}
476
497
@@ -506,8 +527,9 @@ export function createHydrationFunctions(
506
527
hasMismatch = true
507
528
if ( __DEV__ && ! hasWarned ) {
508
529
warn (
509
- `Hydration children mismatch in <${ container . tagName . toLowerCase ( ) } >: ` +
510
- `server rendered element contains fewer child nodes than client vdom.`
530
+ `Hydration children mismatch on` ,
531
+ container ,
532
+ `\nServer rendered element contains fewer child nodes than client vdom.`
511
533
)
512
534
hasWarned = true
513
535
}
@@ -670,3 +692,58 @@ export function createHydrationFunctions(
670
692
671
693
return [ hydrate , hydrateNode ] as const
672
694
}
695
+
696
+ /**
697
+ * Dev only
698
+ */
699
+ function propHasMismatch ( el : Element , key : string , clientValue : any ) : boolean {
700
+ let mismatchType : string | undefined
701
+ let mismatchKey : string | undefined
702
+ let actual : any
703
+ let expected : any
704
+ if ( key === 'class' ) {
705
+ actual = el . className
706
+ expected = normalizeClass ( clientValue )
707
+ if ( actual !== expected ) {
708
+ mismatchType = mismatchKey = `class`
709
+ }
710
+ } else if ( key === 'style' ) {
711
+ actual = el . getAttribute ( 'style' )
712
+ expected = isString ( clientValue )
713
+ ? clientValue
714
+ : stringifyStyle ( normalizeStyle ( clientValue ) )
715
+ if ( actual !== expected ) {
716
+ mismatchType = mismatchKey = 'style'
717
+ }
718
+ } else if (
719
+ ( el instanceof SVGElement && isKnownSvgAttr ( key ) ) ||
720
+ ( el instanceof HTMLElement && ( isBooleanAttr ( key ) || isKnownHtmlAttr ( key ) ) )
721
+ ) {
722
+ actual = el . hasAttribute ( key ) && el . getAttribute ( key )
723
+ expected = isBooleanAttr ( key )
724
+ ? includeBooleanAttr ( clientValue )
725
+ ? ''
726
+ : false
727
+ : String ( clientValue )
728
+ if ( actual !== expected ) {
729
+ mismatchType = `attribute`
730
+ mismatchKey = key
731
+ }
732
+ }
733
+
734
+ if ( mismatchType ) {
735
+ const format = ( v : any ) =>
736
+ v === false ? `(not rendered)` : `${ mismatchKey } ="${ v } "`
737
+ warn (
738
+ `Hydration ${ mismatchType } mismatch on` ,
739
+ el ,
740
+ `\n - rendered on server: ${ format ( actual ) } ` +
741
+ `\n - expected on client: ${ format ( expected ) } ` +
742
+ `\n Note: this mismatch is check-only. The DOM will not be rectified ` +
743
+ `in production due to performance overhead.` +
744
+ `\n You should fix the source of the mismatch.`
745
+ )
746
+ return true
747
+ }
748
+ return false
749
+ }
0 commit comments