@@ -15,7 +15,7 @@ import { ComponentInternalInstance } from './component'
15
15
import { invokeDirectiveHook } from './directives'
16
16
import { warn } from './warning'
17
17
import { PatchFlags , ShapeFlags , isReservedProp , isOn } from '@vue/shared'
18
- import { RendererInternals } from './renderer'
18
+ import { needTransition , RendererInternals } from './renderer'
19
19
import { setRef } from './rendererTemplateRef'
20
20
import {
21
21
SuspenseImpl ,
@@ -146,7 +146,17 @@ export function createHydrationFunctions(
146
146
break
147
147
case Comment :
148
148
if ( domType !== DOMNodeTypes . COMMENT || isFragmentStart ) {
149
- nextNode = onMismatch ( )
149
+ if ( ( node as Element ) . tagName . toLowerCase ( ) === 'template' ) {
150
+ const content = ( vnode . el ! as HTMLTemplateElement ) . content
151
+ . firstChild !
152
+
153
+ // replace <template> node with inner children
154
+ replaceNode ( content , node , parentComponent )
155
+ vnode . el = node = content
156
+ nextNode = nextSibling ( node )
157
+ } else {
158
+ nextNode = onMismatch ( )
159
+ }
150
160
} else {
151
161
nextNode = nextSibling ( node )
152
162
}
@@ -196,9 +206,10 @@ export function createHydrationFunctions(
196
206
default :
197
207
if ( shapeFlag & ShapeFlags . ELEMENT ) {
198
208
if (
199
- domType !== DOMNodeTypes . ELEMENT ||
200
- ( vnode . type as string ) . toLowerCase ( ) !==
201
- ( node as Element ) . tagName . toLowerCase ( )
209
+ ( domType !== DOMNodeTypes . ELEMENT ||
210
+ ( vnode . type as string ) . toLowerCase ( ) !==
211
+ ( node as Element ) . tagName . toLowerCase ( ) ) &&
212
+ ! isTemplateNode ( node as Element )
202
213
) {
203
214
nextNode = onMismatch ( )
204
215
} else {
@@ -217,15 +228,6 @@ export function createHydrationFunctions(
217
228
// on its sub-tree.
218
229
vnode . slotScopeIds = slotScopeIds
219
230
const container = parentNode ( node ) !
220
- mountComponent (
221
- vnode ,
222
- container ,
223
- null ,
224
- parentComponent ,
225
- parentSuspense ,
226
- isSVGContainer ( container ) ,
227
- optimized
228
- )
229
231
230
232
// Locate the next node.
231
233
if ( isFragmentStart ) {
@@ -241,6 +243,16 @@ export function createHydrationFunctions(
241
243
nextNode = nextSibling ( node )
242
244
}
243
245
246
+ mountComponent (
247
+ vnode ,
248
+ container ,
249
+ null ,
250
+ parentComponent ,
251
+ parentSuspense ,
252
+ isSVGContainer ( container ) ,
253
+ optimized
254
+ )
255
+
244
256
// #3787
245
257
// if component is async, it may get moved / unmounted before its
246
258
// inner component is loaded, so we need to give it a placeholder
@@ -307,7 +319,7 @@ export function createHydrationFunctions(
307
319
optimized : boolean
308
320
) => {
309
321
optimized = optimized || ! ! vnode . dynamicChildren
310
- const { type, props, patchFlag, shapeFlag, dirs } = vnode
322
+ const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode
311
323
// #4006 for form elements with non-string v-model value bindings
312
324
// e.g. <option :value="obj">, <input type="checkbox" :true-value="1">
313
325
const forcePatchValue = ( type === 'input' && dirs ) || type === 'option'
@@ -359,12 +371,40 @@ export function createHydrationFunctions(
359
371
if ( ( vnodeHooks = props && props . onVnodeBeforeMount ) ) {
360
372
invokeVNodeHook ( vnodeHooks , parentComponent , vnode )
361
373
}
374
+
375
+ // handle appear transition
376
+ let needCallTransitionHooks = false
377
+ if ( isTemplateNode ( el ) ) {
378
+ needCallTransitionHooks =
379
+ needTransition ( parentSuspense , transition ) &&
380
+ parentComponent &&
381
+ parentComponent . vnode . props &&
382
+ parentComponent . vnode . props . appear
383
+
384
+ const content = ( el as HTMLTemplateElement ) . content
385
+ . firstChild as Element
386
+
387
+ if ( needCallTransitionHooks ) {
388
+ transition ! . beforeEnter ( content )
389
+ }
390
+
391
+ // replace <template> node with inner children
392
+ replaceNode ( content , el , parentComponent )
393
+ vnode . el = el = content
394
+ }
395
+
362
396
if ( dirs ) {
363
397
invokeDirectiveHook ( vnode , null , parentComponent , 'beforeMount' )
364
398
}
365
- if ( ( vnodeHooks = props && props . onVnodeMounted ) || dirs ) {
399
+
400
+ if (
401
+ ( vnodeHooks = props && props . onVnodeMounted ) ||
402
+ dirs ||
403
+ needCallTransitionHooks
404
+ ) {
366
405
queueEffectWithSuspense ( ( ) => {
367
406
vnodeHooks && invokeVNodeHook ( vnodeHooks , parentComponent , vnode )
407
+ needCallTransitionHooks && transition ! . enter ( el )
368
408
dirs && invokeDirectiveHook ( vnode , null , parentComponent , 'mounted' )
369
409
} , parentSuspense )
370
410
}
@@ -582,5 +622,34 @@ export function createHydrationFunctions(
582
622
return node
583
623
}
584
624
625
+ const replaceNode = (
626
+ newNode : Node ,
627
+ oldNode : Node ,
628
+ parentComponent : ComponentInternalInstance | null
629
+ ) : void => {
630
+ // replace node
631
+ const parentNode = oldNode . parentNode
632
+ if ( parentNode ) {
633
+ parentNode . replaceChild ( newNode , oldNode )
634
+ }
635
+
636
+ // update vnode
637
+ let parent = parentComponent
638
+ while ( parent ) {
639
+ if ( parent . vnode . el === oldNode ) {
640
+ parent . vnode . el = newNode
641
+ parent . subTree . el = newNode
642
+ }
643
+ parent = parent . parent
644
+ }
645
+ }
646
+
647
+ const isTemplateNode = ( node : Element ) : boolean => {
648
+ return (
649
+ node . nodeType === DOMNodeTypes . ELEMENT &&
650
+ node . tagName . toLowerCase ( ) === 'template'
651
+ )
652
+ }
653
+
585
654
return [ hydrate , hydrateNode ] as const
586
655
}
0 commit comments