1- import { warn } from '@vue/runtime-dom'
1+ import { MismatchTypes , isMismatchAllowed , warn } from '@vue/runtime-dom'
22import {
33 insertionAnchor ,
44 insertionParent ,
@@ -8,11 +8,15 @@ import {
88import {
99 _child ,
1010 _next ,
11+ child ,
12+ createElement ,
1113 createTextNode ,
1214 disableHydrationNodeLookup ,
1315 enableHydrationNodeLookup ,
16+ parentNode ,
1417} from './node'
1518import { BLOCK_ANCHOR_END_LABEL , BLOCK_ANCHOR_START_LABEL } from '@vue/shared'
19+ import { insert , remove } from '../block'
1620
1721const isHydratingStack = [ ] as boolean [ ]
1822export let isHydrating = false
@@ -83,7 +87,7 @@ export function advanceHydrationNode(
8387) : void {
8488 // if no next sibling, find the next node in the parent chain
8589 const ret =
86- node . nextSibling ||
90+ _next ( node ) ||
8791 // pns is short for "parent next sibling"
8892 node . $pns ||
8993 ( node . $pns = locateNextSiblingOfParent ( node ) )
@@ -97,35 +101,45 @@ export function advanceHydrationNode(
97101function adoptTemplateImpl ( node : Node , template : string ) : Node | null {
98102 if ( ! ( template [ 0 ] === '<' && template [ 1 ] === '!' ) ) {
99103 while ( node . nodeType === 8 ) {
100- node = node . nextSibling !
104+ node = _next ( node )
101105
102106 // empty text node in slot
103107 if (
104108 template . trim ( ) === '' &&
105109 isComment ( node , ']' ) &&
106110 isComment ( node . previousSibling ! , '[' )
107111 ) {
108- node = node . parentNode ! . insertBefore ( createTextNode ( ' ' ) , node )
112+ node = parentNode ( node ) ! . insertBefore ( createTextNode ( ' ' ) , node )
109113 break
110114 }
111115 }
112116 }
113117
114- if ( __DEV__ ) {
115- const type = node . nodeType
116- if (
117- ( type === 8 && ! template . startsWith ( '<!' ) ) ||
118- ( type === 1 &&
119- ! template . startsWith ( `<` + ( node as Element ) . tagName . toLowerCase ( ) ) ) ||
120- ( type === 3 &&
121- template . trim ( ) &&
122- ! template . startsWith ( ( node as Text ) . data ) )
123- ) {
124- // TODO recover and provide more info
125- warn ( `adopted: ` , node )
126- warn ( `template: ${ template } ` )
127- warn ( 'hydration mismatch!' )
128- }
118+ const type = node . nodeType
119+ if (
120+ // comment node
121+ ( type === 8 && ! template . startsWith ( '<!' ) ) ||
122+ // element node
123+ ( type === 1 &&
124+ ! template . startsWith ( `<` + ( node as Element ) . tagName . toLowerCase ( ) ) )
125+ ) {
126+ node = handleMismatch ( node , template )
127+ }
128+ // text node
129+ else if (
130+ type === 3 &&
131+ template . trim ( ) &&
132+ ! template . startsWith ( ( node as Text ) . data )
133+ ) {
134+ ; ( __DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ ) &&
135+ warn (
136+ `Hydration text mismatch in` ,
137+ parentNode ( node ) ,
138+ `\n - rendered on server: ${ JSON . stringify ( ( node as Text ) . data ) } ` +
139+ `\n - expected on client: ${ JSON . stringify ( template ) } ` ,
140+ )
141+ logMismatchError ( )
142+ ; ( node as Text ) . data = template
129143 }
130144
131145 advanceHydrationNode ( node )
@@ -141,14 +155,16 @@ function locateHydrationNodeImpl(): void {
141155 ) !
142156 } else {
143157 node = currentHydrationNode
144- if ( insertionParent && ( ! node || node . parentNode !== insertionParent ) ) {
158+ if ( insertionParent && ( ! node || parentNode ( node ) !== insertionParent ) ) {
145159 node = _child ( insertionParent )
146160 }
147161 }
148162
149163 if ( __DEV__ && ! node ) {
150- // TODO more info
151- warn ( 'Hydration mismatch in ' , insertionParent )
164+ throw new Error (
165+ `No current hydration node was found.\n` +
166+ `this is likely a Vue internal bug.` ,
167+ )
152168 }
153169
154170 resetInsertionState ( )
@@ -166,7 +182,7 @@ export function locateEndAnchor(
166182 }
167183
168184 const stack : Anchor [ ] = [ node ]
169- while ( ( node = node . nextSibling as Anchor ) && stack . length > 0 ) {
185+ while ( ( node = _next ( node ) as Anchor ) && stack . length > 0 ) {
170186 if ( node . nodeType === 8 ) {
171187 if ( node . data === open ) {
172188 stack . push ( node )
@@ -187,20 +203,29 @@ export function locateFragmentAnchor(
187203) : Comment | null {
188204 while ( node && node . nodeType === 8 ) {
189205 if ( ( node as Comment ) . data === label ) return node as Comment
190- node = node . nextSibling !
206+ node = _next ( node )
207+ }
208+
209+ if ( __DEV__ ) {
210+ throw new Error (
211+ `Could not locate fragment anchor node with label: ${ label } \n` +
212+ `this is likely a Vue internal bug.` ,
213+ )
191214 }
215+
192216 return null
193217}
194218
195219function locateNextBlockNode ( node : Node ) : Node | null {
196220 while ( node ) {
197- if ( isComment ( node , BLOCK_ANCHOR_START_LABEL ) ) return node . nextSibling
198- node = node . nextSibling !
221+ if ( isComment ( node , BLOCK_ANCHOR_START_LABEL ) ) return _next ( node )
222+ node = _next ( node )
199223 }
200224
201225 if ( __DEV__ ) {
202226 throw new Error (
203- `Could not locate hydration node with anchor label: ${ BLOCK_ANCHOR_START_LABEL } ` ,
227+ `Could not locate hydration node with anchor label: ${ BLOCK_ANCHOR_START_LABEL } \n` +
228+ `this is likely a Vue internal bug.` ,
204229 )
205230 }
206231 return null
@@ -221,3 +246,63 @@ export function advanceToNonBlockNode(node: Node): Node {
221246 }
222247 return node
223248}
249+
250+ function handleMismatch ( node : Node , template : string ) : Node {
251+ if ( ! isMismatchAllowed ( node . parentElement ! , MismatchTypes . CHILDREN ) ) {
252+ ; ( __DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ ) &&
253+ warn (
254+ `Hydration node mismatch:\n- rendered on server:` ,
255+ node ,
256+ node . nodeType === 3
257+ ? `(text)`
258+ : isComment ( node , '[[' )
259+ ? `(start of block node)`
260+ : `` ,
261+ `\n- expected on client:` ,
262+ template ,
263+ )
264+ logMismatchError ( )
265+ }
266+
267+ // block node start
268+ if ( isComment ( node , BLOCK_ANCHOR_START_LABEL ) ) {
269+ const end = locateEndAnchor (
270+ node as Anchor ,
271+ BLOCK_ANCHOR_START_LABEL ,
272+ BLOCK_ANCHOR_END_LABEL ,
273+ )
274+ while ( true ) {
275+ const next = _next ( node )
276+ if ( next && next !== end ) {
277+ remove ( next , parentNode ( node ) ! )
278+ } else {
279+ break
280+ }
281+ }
282+ }
283+
284+ const next = _next ( node )
285+ const container = parentNode ( node ) !
286+ remove ( node , container )
287+
288+ let newNode : Node | null
289+ if ( template [ 0 ] !== '<' ) {
290+ newNode = createTextNode ( template )
291+ } else {
292+ const t = createElement ( 'template' ) as HTMLTemplateElement
293+ t . innerHTML = template
294+ newNode = child ( t . content ) . cloneNode ( true )
295+ }
296+ insert ( newNode , container , next )
297+ return newNode
298+ }
299+
300+ let hasLoggedMismatchError = false
301+ const logMismatchError = ( ) => {
302+ if ( __TEST__ || hasLoggedMismatchError ) {
303+ return
304+ }
305+ // this error should show up in production
306+ console . error ( 'Hydration completed but contains mismatches.' )
307+ hasLoggedMismatchError = true
308+ }
0 commit comments