import { VNode, normalizeVNode, Text, Comment, Static, Fragment, VNodeHook, createVNode, createTextVNode, invokeVNodeHook } 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 { needTransition, RendererInternals } from './renderer' import { setRef } from './rendererTemplateRef' import { SuspenseImpl, SuspenseBoundary, queueEffectWithSuspense } from './components/Suspense' import { TeleportImpl, TeleportVNode } from './components/Teleport' import { isAsyncWrapper } from './apiAsyncComponent' export type RootHydrateFunction = ( vnode: VNode, container: (Element | ShadowRoot) & { _vnode?: VNode } ) => void const enum DOMNodeTypes { ELEMENT = 1, TEXT = 3, COMMENT = 8 } let hasMismatch = false const isSVGContainer = (container: Element) => /svg/.test(container.namespaceURI!) && container.tagName !== 'foreignObject' const isComment = (node: Node): node is Comment => node.nodeType === DOMNodeTypes.COMMENT // Note: hydration is DOM-specific // But we have to place it in core due to tight coupling with core - splitting // it out creates a ton of unnecessary complexity. // Hydration also depends on some renderer internal logic which needs to be // passed in via arguments. export function createHydrationFunctions( rendererInternals: RendererInternals ) { const { mt: mountComponent, p: patch, o: { patchProp, createText, nextSibling, parentNode, remove, insert, createComment } } = rendererInternals const hydrate: RootHydrateFunction = (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() container._vnode = vnode return } hasMismatch = false hydrateNode(container.firstChild!, vnode, null, null, null) flushPostFlushCbs() container._vnode = vnode if (hasMismatch && !__TEST__) { // this error should show up in production console.error(`Hydration completed but contains mismatches.`) } } const hydrateNode = ( node: Node, vnode: VNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, slotScopeIds: string[] | null, optimized = false ): Node | null => { const isFragmentStart = isComment(node) && node.data === '[' const onMismatch = () => handleMismatch( node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragmentStart ) const { type, ref, shapeFlag, patchFlag } = vnode let domType = node.nodeType vnode.el = node if (patchFlag === PatchFlags.BAIL) { optimized = false vnode.dynamicChildren = null } let nextNode: Node | null = null switch (type) { case Text: if (domType !== DOMNodeTypes.TEXT) { // #5728 empty text node inside a slot can cause hydration failure // because the server rendered HTML won't contain a text node if (vnode.children === '') { insert((vnode.el = createText('')), parentNode(node)!, node) nextNode = node } else { nextNode = onMismatch() } } else { if ((node as Text).data !== vnode.children) { hasMismatch = true __DEV__ && warn( `Hydration text mismatch:` + `\n- Server rendered: ${JSON.stringify( (node as Text).data )}` + `\n- Client rendered: ${JSON.stringify(vnode.children)}` ) ;(node as Text).data = vnode.children as string } nextNode = nextSibling(node) } break case Comment: if (isTemplateNode(node)) { nextNode = nextSibling(node) // wrapped // replace