@@ -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 { RendererInternals } from './renderer'
19
32
import { setRef } from './rendererTemplateRef'
20
33
import {
@@ -116,9 +129,12 @@ export function createHydrationFunctions(
116
129
hasMismatch = true
117
130
__DEV__ &&
118
131
warn (
119
- `Hydration text mismatch:` +
120
- `\n- Client: ${ JSON . stringify ( ( node as Text ) . data ) } ` +
121
- `\n- Server: ${ JSON . stringify ( vnode . children ) } `
132
+ `Hydration text mismatch in` ,
133
+ node . parentNode ,
134
+ `\n - rendered on server: ${ JSON . stringify ( vnode . children ) } ` +
135
+ `\n - expected on client: ${ JSON . stringify (
136
+ ( node as Text ) . data
137
+ ) } `
122
138
)
123
139
; ( node as Text ) . data = vnode . children as string
124
140
}
@@ -292,14 +308,65 @@ export function createHydrationFunctions(
292
308
if ( dirs ) {
293
309
invokeDirectiveHook ( vnode , null , parentComponent , 'created' )
294
310
}
311
+
312
+ // children
313
+ if (
314
+ shapeFlag & ShapeFlags . ARRAY_CHILDREN &&
315
+ // skip if element has innerHTML / textContent
316
+ ! ( props && ( props . innerHTML || props . textContent ) )
317
+ ) {
318
+ let next = hydrateChildren (
319
+ el . firstChild ,
320
+ vnode ,
321
+ el ,
322
+ parentComponent ,
323
+ parentSuspense ,
324
+ slotScopeIds ,
325
+ optimized
326
+ )
327
+ let hasWarned = false
328
+ while ( next ) {
329
+ hasMismatch = true
330
+ if ( __DEV__ && ! hasWarned ) {
331
+ warn (
332
+ `Hydration children mismatch on` ,
333
+ el ,
334
+ `\nServer rendered element contains more child nodes than client vdom.`
335
+ )
336
+ hasWarned = true
337
+ }
338
+ // The SSRed DOM contains more nodes than it should. Remove them.
339
+ const cur = next
340
+ next = next . nextSibling
341
+ remove ( cur )
342
+ }
343
+ } else if ( shapeFlag & ShapeFlags . TEXT_CHILDREN ) {
344
+ if ( el . textContent !== vnode . children ) {
345
+ hasMismatch = true
346
+ __DEV__ &&
347
+ warn (
348
+ `Hydration text content mismatch on` ,
349
+ el ,
350
+ `\n - rendered on server: ${ vnode . children as string } ` +
351
+ `\n - expected on client: ${ el . textContent } `
352
+ )
353
+ el . textContent = vnode . children as string
354
+ }
355
+ }
356
+
295
357
// props
296
358
if ( props ) {
297
359
if (
360
+ __DEV__ ||
298
361
forcePatchValue ||
299
362
! optimized ||
300
363
patchFlag & ( PatchFlags . FULL_PROPS | PatchFlags . HYDRATE_EVENTS )
301
364
) {
302
365
for ( const key in props ) {
366
+ // check hydration mismatch
367
+ if ( __DEV__ && propHasMismatch ( el , key , props [ key ] ) ) {
368
+ hasMismatch = true
369
+ }
303
370
if (
304
371
( forcePatchValue && key . endsWith ( 'value' ) ) ||
305
372
( isOn ( key ) && ! isReservedProp ( key ) )
@@ -343,50 +410,6 @@ export function createHydrationFunctions(
343
410
dirs && invokeDirectiveHook ( vnode , null , parentComponent , 'mounted' )
344
411
} , parentSuspense )
345
412
}
346
- // children
347
- if (
348
- shapeFlag & ShapeFlags . ARRAY_CHILDREN &&
349
- // skip if element has innerHTML / textContent
350
- ! ( props && ( props . innerHTML || props . textContent ) )
351
- ) {
352
- let next = hydrateChildren (
353
- el . firstChild ,
354
- vnode ,
355
- el ,
356
- parentComponent ,
357
- parentSuspense ,
358
- slotScopeIds ,
359
- optimized
360
- )
361
- let hasWarned = false
362
- while ( next ) {
363
- hasMismatch = true
364
- if ( __DEV__ && ! hasWarned ) {
365
- warn (
366
- `Hydration children mismatch in <${ vnode . type as string } >: ` +
367
- `server rendered element contains more child nodes than client vdom.`
368
- )
369
- hasWarned = true
370
- }
371
- // The SSRed DOM contains more nodes than it should. Remove them.
372
- const cur = next
373
- next = next . nextSibling
374
- remove ( cur )
375
- }
376
- } else if ( shapeFlag & ShapeFlags . TEXT_CHILDREN ) {
377
- if ( el . textContent !== vnode . children ) {
378
- hasMismatch = true
379
- __DEV__ &&
380
- warn (
381
- `Hydration text content mismatch in <${
382
- vnode . type as string
383
- } >:\n` +
384
- `- Client: ${ el . textContent } \n` +
385
- `- Server: ${ vnode . children as string } `
386
- )
387
- el . textContent = vnode . children as string
388
- }
389
- }
390
413
}
391
414
return el . nextSibling
392
415
}
@@ -423,8 +446,9 @@ export function createHydrationFunctions(
423
446
hasMismatch = true
424
447
if ( __DEV__ && ! hasWarned ) {
425
448
warn (
426
- `Hydration children mismatch in <${ container . tagName . toLowerCase ( ) } >: ` +
427
- `server rendered element contains fewer child nodes than client vdom.`
449
+ `Hydration children mismatch on` ,
450
+ container ,
451
+ `\nServer rendered element contains fewer child nodes than client vdom.`
428
452
)
429
453
hasWarned = true
430
454
}
@@ -554,3 +578,58 @@ export function createHydrationFunctions(
554
578
555
579
return [ hydrate , hydrateNode ] as const
556
580
}
581
+
582
+ /**
583
+ * Dev only
584
+ */
585
+ function propHasMismatch ( el : Element , key : string , clientValue : any ) : boolean {
586
+ let mismatchType : string | undefined
587
+ let mismatchKey : string | undefined
588
+ let actual : any
589
+ let expected : any
590
+ if ( key === 'class' ) {
591
+ actual = el . className
592
+ expected = normalizeClass ( clientValue )
593
+ if ( actual !== expected ) {
594
+ mismatchType = mismatchKey = `class`
595
+ }
596
+ } else if ( key === 'style' ) {
597
+ actual = el . getAttribute ( 'style' )
598
+ expected = isString ( clientValue )
599
+ ? clientValue
600
+ : stringifyStyle ( normalizeStyle ( clientValue ) )
601
+ if ( actual !== expected ) {
602
+ mismatchType = mismatchKey = 'style'
603
+ }
604
+ } else if (
605
+ ( el instanceof SVGElement && isKnownSvgAttr ( key ) ) ||
606
+ ( el instanceof HTMLElement && ( isBooleanAttr ( key ) || isKnownHtmlAttr ( key ) ) )
607
+ ) {
608
+ actual = el . hasAttribute ( key ) && el . getAttribute ( key )
609
+ expected = isBooleanAttr ( key )
610
+ ? includeBooleanAttr ( clientValue )
611
+ ? ''
612
+ : false
613
+ : String ( clientValue )
614
+ if ( actual !== expected ) {
615
+ mismatchType = `attribute`
616
+ mismatchKey = key
617
+ }
618
+ }
619
+
620
+ if ( mismatchType ) {
621
+ const format = ( v : any ) =>
622
+ v === false ? `(not rendered)` : `${ mismatchKey } ="${ v } "`
623
+ warn (
624
+ `Hydration ${ mismatchType } mismatch on` ,
625
+ el ,
626
+ `\n - rendered on server: ${ format ( actual ) } ` +
627
+ `\n - expected on client: ${ format ( expected ) } ` +
628
+ `\n Note: this mismatch is check-only. The DOM will not be rectified ` +
629
+ `in production due to performance overhead.` +
630
+ `\n You should fix the source of the mismatch.`
631
+ )
632
+ return true
633
+ }
634
+ return false
635
+ }
0 commit comments