import { VNode , normalizeVNode , Text , Comment , Static , Fragment , VNodeHook , createVNode , createTextVNode } from './vnode' ; import { flushPostFlushCbs } from './scheduler' ; import { ComponentInternalInstance } from './component' ; import { invokeDirectiveHook } from './directives' ; import { warn } from './warning' ; import { PatchFlags , ShapeFlags , isReservedProp , isOn } from '@vue/shared' ; import { RendererInternals , invokeVNodeHook , setRef } from './renderer' ; import { SuspenseImpl , SuspenseBoundary , queueEffectWithSuspense } from './components/Suspense' ; import { TeleportImpl , TeleportVNode } from './components/Teleport' ; import { isAsyncWrapper } from './apiAsyncComponent' ; export type RootHydrateFunction = ( vnode : VNode < Node , Element > , container : Element | ShadowRoot ) => void ; const enum DOMNodeTypes { ELEMENT = 1 , TEXT = 3 , COMMENT = 8 } let hasMismatch = false ; const isSVGContainer = ( container ) => /svg/ . test ( container . namespaceURI ! ) && container . tagName !== 'foreignObject' ; const isComment = ( node ) : node is Comment => node . nodeType === DOMNodeTypes . COMMENT ; export function createHydrationFunctions ( rendererInternals ) { const { mt : mountComponent , p : patch , o : { patchProp , nextSibling , parentNode , remove , insert , createComment } } = rendererInternals ; const hydrate = ( vnode , container ) => { if ( ! container . hasChildNodes ( ) ) { __DEV__ && warn ( `Attempting to hydrate existing markup but container is empty. ` + `Performing full mount instead.` ) ; patch ( null , vnode , container ) ; flushPostFlushCbs ( ) ; return ; } hasMismatch = false ; hydrateNode ( container . firstChild ! , vnode , null , null , null ) ; flushPostFlushCbs ( ) ; if ( hasMismatch && ! __TEST__ ) { console . error ( `Hydration completed but contains mismatches.` ) ; } } ; const hydrateNode = ( node , vnode , parentComponent , parentSuspense , slotScopeIds , optimized = false ) : Node | null => { const isFragmentStart = isComment ( node ) && node . data === '[' ; const onMismatch = ( ) => handleMismatch ( node , vnode , parentComponent , parentSuspense , slotScopeIds , isFragmentStart ) ; const { type , ref , shapeFlag } = vnode ; const domType = node . nodeType ; vnode . el = node ; let nextNode = null ; switch ( type ) { case Text : if ( domType !== DOMNodeTypes . TEXT ) { nextNode = onMismatch ( ) ; } else { if ( ( node as Text ) . data !== vnode . children ) { hasMismatch = true ; __DEV__ && warn ( `Hydration text mismatch:` + `\n- Client: ${ JSON . stringify ( ( node as Text ) . data ) }` + `\n- Server: ${ JSON . stringify ( vnode . children ) }` ) ; ( node as Text ) . data = vnode . children as string ; } nextNode = nextSibling ( node ) ; } break ; case Comment : if ( domType !== DOMNodeTypes . COMMENT || isFragmentStart ) { nextNode = onMismatch ( ) ; } else { nextNode = nextSibling ( node ) ; } break ; case Static : if ( domType !== DOMNodeTypes . ELEMENT ) { nextNode = onMismatch ( ) ; } else { nextNode = node ; const needToAdoptContent = ! ( vnode . children as string ) . length ; for ( let i = 0 ; i < vnode . staticCount ! ; i ++ ) { if ( needToAdoptContent ) vnode . children += ( nextNode as Element ) . outerHTML ; if ( i === vnode . staticCount ! - 1 ) { vnode . anchor = nextNode ; } nextNode = nextSibling ( nextNode ) ! ; } return nextNode ; } break ; case Fragment : if ( ! isFragmentStart ) { nextNode = onMismatch ( ) ; } else { nextNode = hydrateFragment ( node as Comment , vnode , parentComponent , parentSuspense , slotScopeIds , optimized ) ; } break ; default : if ( shapeFlag & ShapeFlags . ELEMENT ) { if ( domType !== DOMNodeTypes . ELEMENT || ( vnode . type as string ) . toLowerCase ( ) !== ( node as Element ) . tagName . toLowerCase ( ) ) { nextNode = onMismatch ( ) ; } else { nextNode = hydrateElement ( node as Element , vnode , parentComponent , parentSuspense , slotScopeIds , optimized ) ; } } else if ( shapeFlag & ShapeFlags . COMPONENT ) { vnode . slotScopeIds = slotScopeIds ; const container = parentNode ( node ) ! ; mountComponent ( vnode , container , null , parentComponent , parentSuspense , isSVGContainer ( container ) , optimized ) ; nextNode = isFragmentStart ? locateClosingAsyncAnchor ( node ) : nextSibling ( node ) ; if ( isAsyncWrapper ( vnode ) ) { let subTree ; if ( isFragmentStart ) { subTree = createVNode ( Fragment ) ; subTree . anchor = nextNode ? nextNode . previousSibling : container . lastChild ; } else { subTree = node . nodeType === 3 ? createTextVNode ( '' ) : createVNode ( 'div' ) ; } subTree . el = node ; vnode . component ! . subTree = subTree ; } } else if ( shapeFlag & ShapeFlags . TELEPORT ) { if ( domType !== DOMNodeTypes . COMMENT ) { nextNode = onMismatch ( ) ; } else { nextNode = ( vnode . type as typeof TeleportImpl ) . hydrate ( node , vnode as TeleportVNode , parentComponent , parentSuspense , slotScopeIds , optimized , rendererInternals , hydrateChildren ) ; } } else if ( __FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags . SUSPENSE ) { nextNode = ( vnode . type as typeof SuspenseImpl ) . hydrate ( node , vnode , parentComponent , parentSuspense , isSVGContainer ( parentNode ( node ) ! ) , slotScopeIds , optimized , rendererInternals , hydrateNode ) ; } else if ( __DEV__ ) { warn ( 'Invalid HostVNode type:' , type , `(${ typeof type })` ) ; } } if ( ref != null ) { setRef ( ref , null , parentSuspense , vnode ) ; } return nextNode ; } ; const hydrateElement = ( el , vnode , parentComponent , parentSuspense , slotScopeIds , optimized ) => { optimized = optimized || ! ! vnode . dynamicChildren ; const { type , props , patchFlag , shapeFlag , dirs } = vnode ; const forcePatchValue = ( type === 'input' && dirs ) || type === 'option' ; if ( forcePatchValue || patchFlag !== PatchFlags . HOISTED ) { if ( dirs ) { invokeDirectiveHook ( vnode , null , parentComponent , 'created' ) ; } if ( props ) { if ( forcePatchValue || ! optimized || patchFlag & ( PatchFlags . FULL_PROPS | PatchFlags . HYDRATE_EVENTS ) ) { for ( const key in props ) { if ( ( forcePatchValue && key . endsWith ( 'value' ) ) || ( isOn ( key ) && ! isReservedProp ( key ) ) ) { patchProp ( el , key , null , props [ key ] ) ; } } } else if ( props . onClick ) { patchProp ( el , 'onClick' , null , props . onClick ) ; } } let vnodeHooks ; if ( ( vnodeHooks = props && props . onVnodeBeforeMount ) ) { invokeVNodeHook ( vnodeHooks , parentComponent , vnode ) ; } if ( dirs ) { invokeDirectiveHook ( vnode , null , parentComponent , 'beforeMount' ) ; } if ( ( vnodeHooks = props && props . onVnodeMounted ) || dirs ) { queueEffectWithSuspense ( ( ) => { vnodeHooks && invokeVNodeHook ( vnodeHooks , parentComponent , vnode ) ; dirs && invokeDirectiveHook ( vnode , null , parentComponent , 'mounted' ) ; } , parentSuspense ) ; } if ( shapeFlag & ShapeFlags . ARRAY_CHILDREN && ! ( props && ( props . innerHTML || props . textContent ) ) ) { let next = hydrateChildren ( el . firstChild , vnode , el , parentComponent , parentSuspense , slotScopeIds , optimized ) ; let hasWarned = false ; while ( next ) { hasMismatch = true ; if ( __DEV__ && ! hasWarned ) { warn ( `Hydration children mismatch in <${ vnode . type as string }>: ` + `server rendered element contains more child nodes than client vdom.` ) ; hasWarned = true ; } const cur = next ; next = next . nextSibling ; remove ( cur ) ; } } else if ( shapeFlag & ShapeFlags . TEXT_CHILDREN ) { if ( el . textContent !== vnode . children ) { hasMismatch = true ; __DEV__ && warn ( `Hydration text content mismatch in <${ vnode . type as string }>:\n` + `- Client: ${ el . textContent }\n` + `- Server: ${ vnode . children as string }` ) ; el . textContent = vnode . children as string ; } } } return el . nextSibling ; } ; const hydrateChildren = ( node , parentVNode , container , parentComponent , parentSuspense , slotScopeIds , optimized ) : Node | null => { optimized = optimized || ! ! parentVNode . dynamicChildren ; const children = parentVNode . children as VNode [ ] ; const l = children . length ; let hasWarned = false ; for ( let i = 0 ; i < l ; i ++ ) { const vnode = optimized ? children [ i ] : ( children [ i ] = normalizeVNode ( children [ i ] ) ) ; if ( node ) { node = hydrateNode ( node , vnode , parentComponent , parentSuspense , slotScopeIds , optimized ) ; } else if ( vnode . type === Text && ! vnode . children ) { continue ; } else { hasMismatch = true ; if ( __DEV__ && ! hasWarned ) { warn ( `Hydration children mismatch in <${ container . tagName . toLowerCase ( ) }>: ` + `server rendered element contains fewer child nodes than client vdom.` ) ; hasWarned = true ; } patch ( null , vnode , container , null , parentComponent , parentSuspense , isSVGContainer ( container ) , slotScopeIds ) ; } } return node ; } ; const hydrateFragment = ( node , vnode , parentComponent , parentSuspense , slotScopeIds , optimized ) => { const { slotScopeIds : fragmentSlotScopeIds } = vnode ; if ( fragmentSlotScopeIds ) { slotScopeIds = slotScopeIds ? slotScopeIds . concat ( fragmentSlotScopeIds ) : fragmentSlotScopeIds ; } const container = parentNode ( node ) ! ; const next = hydrateChildren ( nextSibling ( node ) ! , vnode , container , parentComponent , parentSuspense , slotScopeIds , optimized ) ; if ( next && isComment ( next ) && next . data === ']' ) { return nextSibling ( ( vnode . anchor = next ) ) ; } else { hasMismatch = true ; insert ( ( vnode . anchor = createComment ( `]` ) ) , container , next ) ; return next ; } } ; const handleMismatch = ( node , vnode , parentComponent , parentSuspense , slotScopeIds , isFragment ) : Node | null => { hasMismatch = true ; __DEV__ && warn ( `Hydration node mismatch:\n- Client vnode:` , vnode . type , `\n- Server rendered DOM:` , node , node . nodeType === DOMNodeTypes . TEXT ? `(text)` : isComment ( node ) && node . data === '[' ? `(start of fragment)` : `` ) ; vnode . el = null ; if ( isFragment ) { const end = locateClosingAsyncAnchor ( node ) ; while ( true ) { const next = nextSibling ( node ) ; if ( next && next !== end ) { remove ( next ) ; } else { break ; } } } const next = nextSibling ( node ) ; const container = parentNode ( node ) ! ; remove ( node ) ; patch ( null , vnode , container , next , parentComponent , parentSuspense , isSVGContainer ( container ) , slotScopeIds ) ; return next ; } ; const locateClosingAsyncAnchor = ( node ) : Node | null => { let match = 0 ; while ( node ) { node = nextSibling ( node ) ; if ( node && isComment ( node ) ) { if ( node . data === '[' ) match ++ ; if ( node . data === ']' ) { if ( match === 0 ) { return nextSibling ( node ) ; } else { match -- ; } } } } return node ; } ; return [ hydrate , hydrateNode ] as const ; }