@@ -17,42 +17,86 @@ export type RootHydrateFunction = (
17
17
container : Element
18
18
) => void
19
19
20
+ const enum DOMNodeTypes {
21
+ ELEMENT = 1 ,
22
+ TEXT = 3 ,
23
+ COMMENT = 8
24
+ }
25
+
26
+ let hasHydrationMismatch = false
27
+
20
28
// Note: hydration is DOM-specific
21
29
// But we have to place it in core due to tight coupling with core - splitting
22
30
// it out creates a ton of unnecessary complexity.
23
31
// Hydration also depends on some renderer internal logic which needs to be
24
32
// passed in via arguments.
25
33
export function createHydrationFunctions ( {
26
34
mt : mountComponent ,
35
+ p : patch ,
27
36
o : { patchProp, createText }
28
37
} : RendererInternals < Node , Element > ) {
29
38
const hydrate : RootHydrateFunction = ( vnode , container ) => {
30
39
if ( __DEV__ && ! container . hasChildNodes ( ) ) {
31
- warn ( `Attempting to hydrate existing markup but container is empty.` )
40
+ warn (
41
+ `Attempting to hydrate existing markup but container is empty. ` +
42
+ `Performing full mount instead.`
43
+ )
44
+ patch ( null , vnode , container )
32
45
return
33
46
}
47
+ hasHydrationMismatch = false
34
48
hydrateNode ( container . firstChild ! , vnode )
35
49
flushPostFlushCbs ( )
50
+ if ( hasHydrationMismatch ) {
51
+ // this error should show up in production
52
+ console . error ( `Hydration completed but contains mismatches.` )
53
+ }
36
54
}
37
55
38
- // TODO handle mismatches
39
56
const hydrateNode = (
40
57
node : Node ,
41
58
vnode : VNode ,
42
59
parentComponent : ComponentInternalInstance | null = null ,
43
60
optimized = false
44
61
) : Node | null => {
45
62
const { type, shapeFlag } = vnode
63
+ const domType = node . nodeType
64
+
46
65
vnode . el = node
66
+
47
67
switch ( type ) {
48
68
case Text :
69
+ if ( domType !== DOMNodeTypes . TEXT ) {
70
+ return handleMismtach ( node , vnode , parentComponent )
71
+ }
72
+ if ( ( node as Text ) . data !== vnode . children ) {
73
+ hasHydrationMismatch = true
74
+ __DEV__ &&
75
+ warn (
76
+ `Hydration text mismatch:` +
77
+ `\n- Client: ${ JSON . stringify ( vnode . children ) } ` ,
78
+ `\n- Server: ${ JSON . stringify ( ( node as Text ) . data ) } `
79
+ )
80
+ ; ( node as Text ) . data = vnode . children as string
81
+ }
82
+ return node . nextSibling
49
83
case Comment :
84
+ if ( domType !== DOMNodeTypes . COMMENT ) {
85
+ return handleMismtach ( node , vnode , parentComponent )
86
+ }
87
+ return node . nextSibling
50
88
case Static :
89
+ if ( domType !== DOMNodeTypes . ELEMENT ) {
90
+ return handleMismtach ( node , vnode , parentComponent )
91
+ }
51
92
return node . nextSibling
52
93
case Fragment :
53
94
return hydrateFragment ( node , vnode , parentComponent , optimized )
54
95
default :
55
96
if ( shapeFlag & ShapeFlags . ELEMENT ) {
97
+ if ( domType !== DOMNodeTypes . ELEMENT ) {
98
+ return handleMismtach ( node , vnode , parentComponent )
99
+ }
56
100
return hydrateElement (
57
101
node as Element ,
58
102
vnode ,
@@ -67,7 +111,15 @@ export function createHydrationFunctions({
67
111
const subTree = vnode . component ! . subTree
68
112
return ( subTree . anchor || subTree . el ) . nextSibling
69
113
} else if ( shapeFlag & ShapeFlags . PORTAL ) {
70
- hydratePortal ( vnode , parentComponent , optimized )
114
+ if ( domType !== DOMNodeTypes . COMMENT ) {
115
+ return handleMismtach ( node , vnode , parentComponent )
116
+ }
117
+ hydratePortal (
118
+ vnode ,
119
+ node . parentNode as Element ,
120
+ parentComponent ,
121
+ optimized
122
+ )
71
123
return node . nextSibling
72
124
} else if ( __FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags . SUSPENSE ) {
73
125
// TODO Suspense
@@ -84,7 +136,7 @@ export function createHydrationFunctions({
84
136
parentComponent : ComponentInternalInstance | null ,
85
137
optimized : boolean
86
138
) => {
87
- const { props, patchFlag } = vnode
139
+ const { props, patchFlag, shapeFlag } = vnode
88
140
// skip props & children if this is hoisted static nodes
89
141
if ( patchFlag !== PatchFlags . HOISTED ) {
90
142
// props
@@ -116,16 +168,31 @@ export function createHydrationFunctions({
116
168
}
117
169
// children
118
170
if (
119
- vnode . shapeFlag & ShapeFlags . ARRAY_CHILDREN &&
171
+ shapeFlag & ShapeFlags . ARRAY_CHILDREN &&
120
172
// skip if element has innerHTML / textContent
121
173
! ( props !== null && ( props . innerHTML || props . textContent ) )
122
174
) {
123
- hydrateChildren (
175
+ let next = hydrateChildren (
124
176
el . firstChild ,
125
177
vnode ,
178
+ el ,
126
179
parentComponent ,
127
180
optimized || vnode . dynamicChildren !== null
128
181
)
182
+ while ( next ) {
183
+ hasHydrationMismatch = true
184
+ __DEV__ &&
185
+ warn (
186
+ `Hydration children mismatch: ` +
187
+ `server rendered element contains more child nodes than client vdom.`
188
+ )
189
+ // The SSRed DOM contains more nodes than it should. Remove them.
190
+ const cur = next
191
+ next = next . nextSibling
192
+ el . removeChild ( cur )
193
+ }
194
+ } else if ( shapeFlag & ShapeFlags . TEXT_CHILDREN ) {
195
+ el . textContent = vnode . children as string
129
196
}
130
197
}
131
198
return el . nextSibling
@@ -134,16 +201,28 @@ export function createHydrationFunctions({
134
201
const hydrateChildren = (
135
202
node : Node | null ,
136
203
vnode : VNode ,
204
+ container : Element ,
137
205
parentComponent : ComponentInternalInstance | null ,
138
206
optimized : boolean
139
207
) : Node | null => {
140
208
const children = vnode . children as VNode [ ]
141
209
optimized = optimized || vnode . dynamicChildren !== null
142
- for ( let i = 0 ; node != null && i < children . length ; i ++ ) {
210
+ for ( let i = 0 ; i < children . length ; i ++ ) {
143
211
const vnode = optimized
144
212
? children [ i ]
145
213
: ( children [ i ] = normalizeVNode ( children [ i ] ) )
146
- node = hydrateNode ( node , vnode , parentComponent , optimized )
214
+ if ( node ) {
215
+ node = hydrateNode ( node , vnode , parentComponent , optimized )
216
+ } else {
217
+ hasHydrationMismatch = true
218
+ __DEV__ &&
219
+ warn (
220
+ `Hydration children mismatch: ` +
221
+ `server rendered element contains fewer child nodes than client vdom.`
222
+ )
223
+ // the SSRed DOM didn't contain enough nodes. Mount the missing ones.
224
+ patch ( null , vnode , container )
225
+ }
147
226
}
148
227
return node
149
228
}
@@ -154,15 +233,22 @@ export function createHydrationFunctions({
154
233
parentComponent : ComponentInternalInstance | null ,
155
234
optimized : boolean
156
235
) => {
157
- const parent = node . parentNode !
236
+ const parent = node . parentNode as Element
158
237
parent . insertBefore ( ( vnode . el = createText ( '' ) ) , node )
159
- const next = hydrateChildren ( node , vnode , parentComponent , optimized )
238
+ const next = hydrateChildren (
239
+ node ,
240
+ vnode ,
241
+ parent ,
242
+ parentComponent ,
243
+ optimized
244
+ )
160
245
parent . insertBefore ( ( vnode . anchor = createText ( '' ) ) , next )
161
246
return next
162
247
}
163
248
164
249
const hydratePortal = (
165
250
vnode : VNode ,
251
+ container : Element ,
166
252
parentComponent : ComponentInternalInstance | null ,
167
253
optimized : boolean
168
254
) => {
@@ -171,9 +257,37 @@ export function createHydrationFunctions({
171
257
? document . querySelector ( targetSelector )
172
258
: targetSelector )
173
259
if ( target != null && vnode . shapeFlag & ShapeFlags . ARRAY_CHILDREN ) {
174
- hydrateChildren ( target . firstChild , vnode , parentComponent , optimized )
260
+ hydrateChildren (
261
+ target . firstChild ,
262
+ vnode ,
263
+ container ,
264
+ parentComponent ,
265
+ optimized
266
+ )
175
267
}
176
268
}
177
269
270
+ const handleMismtach = (
271
+ node : Node ,
272
+ vnode : VNode ,
273
+ parentComponent : ComponentInternalInstance | null
274
+ ) => {
275
+ hasHydrationMismatch = true
276
+ __DEV__ &&
277
+ warn (
278
+ `Hydration node mismatch:\n- Client vnode:` ,
279
+ vnode . type ,
280
+ `\n- Server rendered DOM:` ,
281
+ node
282
+ )
283
+ vnode . el = null
284
+ const next = node . nextSibling
285
+ const container = node . parentNode as Element
286
+ container . removeChild ( node )
287
+ // TODO Suspense and SVG
288
+ patch ( null , vnode , container , next , parentComponent )
289
+ return next
290
+ }
291
+
178
292
return [ hydrate , hydrateNode ] as const
179
293
}
0 commit comments