feat(ssr/suspense): suspense hydration
In order to support hydration of async components, server-rendered fragments must be explicitly marked with comment nodes.
This commit is contained in:
@@ -144,7 +144,6 @@ export interface ComponentInternalInstance {
|
||||
|
||||
// suspense related
|
||||
asyncDep: Promise<any> | null
|
||||
asyncResult: unknown
|
||||
asyncResolved: boolean
|
||||
|
||||
// storage for any extra properties
|
||||
@@ -215,7 +214,6 @@ export function createComponentInstance(
|
||||
|
||||
// async dependency management
|
||||
asyncDep: null,
|
||||
asyncResult: null,
|
||||
asyncResolved: false,
|
||||
|
||||
// user namespace for storing whatever the user assigns to `this`
|
||||
@@ -367,7 +365,7 @@ function setupStatefulComponent(
|
||||
if (isPromise(setupResult)) {
|
||||
if (isSSR) {
|
||||
// return the promise so server-renderer can wait on it
|
||||
return setupResult.then(resolvedResult => {
|
||||
return setupResult.then((resolvedResult: unknown) => {
|
||||
handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
|
||||
})
|
||||
} else if (__FEATURE_SUSPENSE__) {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Slots } from '../componentSlots'
|
||||
import { RendererInternals, MoveType, SetupRenderEffectFn } from '../renderer'
|
||||
import { queuePostFlushCb, queueJob } from '../scheduler'
|
||||
import { updateHOCHostEl } from '../componentRenderUtils'
|
||||
import { handleError, ErrorCodes } from '../errorHandling'
|
||||
import { pushWarningContext, popWarningContext } from '../warning'
|
||||
import { handleError, ErrorCodes } from '../errorHandling'
|
||||
|
||||
export interface SuspenseProps {
|
||||
onResolve?: () => void
|
||||
@@ -59,7 +59,8 @@ export const SuspenseImpl = {
|
||||
rendererInternals
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
hydrate: hydrateSuspense
|
||||
}
|
||||
|
||||
// Force-casted public typing for h and TSX props inference
|
||||
@@ -97,14 +98,10 @@ function mountSuspense(
|
||||
rendererInternals
|
||||
))
|
||||
|
||||
const { content, fallback } = normalizeSuspenseChildren(n2)
|
||||
suspense.subTree = content
|
||||
suspense.fallbackTree = fallback
|
||||
|
||||
// start mounting the content subtree in an off-dom container
|
||||
patch(
|
||||
null,
|
||||
content,
|
||||
suspense.subTree,
|
||||
hiddenContainer,
|
||||
null,
|
||||
parentComponent,
|
||||
@@ -117,7 +114,7 @@ function mountSuspense(
|
||||
// mount the fallback tree
|
||||
patch(
|
||||
null,
|
||||
fallback,
|
||||
suspense.fallbackTree,
|
||||
container,
|
||||
anchor,
|
||||
parentComponent,
|
||||
@@ -125,7 +122,7 @@ function mountSuspense(
|
||||
isSVG,
|
||||
optimized
|
||||
)
|
||||
n2.el = fallback.el
|
||||
n2.el = suspense.fallbackTree.el
|
||||
} else {
|
||||
// Suspense has no async deps. Just resolve.
|
||||
suspense.resolve()
|
||||
@@ -209,6 +206,7 @@ export interface SuspenseBoundary<
|
||||
subTree: HostVNode
|
||||
fallbackTree: HostVNode
|
||||
deps: number
|
||||
isHydrating: boolean
|
||||
isResolved: boolean
|
||||
isUnmounted: boolean
|
||||
effects: Function[]
|
||||
@@ -235,7 +233,8 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
anchor: HostNode | null,
|
||||
isSVG: boolean,
|
||||
optimized: boolean,
|
||||
rendererInternals: RendererInternals<HostNode, HostElement>
|
||||
rendererInternals: RendererInternals<HostNode, HostElement>,
|
||||
isHydrating = false
|
||||
): SuspenseBoundary<HostNode, HostElement> {
|
||||
const {
|
||||
p: patch,
|
||||
@@ -245,6 +244,12 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
o: { parentNode }
|
||||
} = rendererInternals
|
||||
|
||||
const getCurrentTree = () =>
|
||||
suspense.isResolved || suspense.isHydrating
|
||||
? suspense.subTree
|
||||
: suspense.fallbackTree
|
||||
|
||||
const { content, fallback } = normalizeSuspenseChildren(vnode)
|
||||
const suspense: SuspenseBoundary<HostNode, HostElement> = {
|
||||
vnode,
|
||||
parent,
|
||||
@@ -255,8 +260,9 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
hiddenContainer,
|
||||
anchor,
|
||||
deps: 0,
|
||||
subTree: (null as unknown) as VNode, // will be set immediately after creation
|
||||
fallbackTree: (null as unknown) as VNode, // will be set immediately after creation
|
||||
subTree: content,
|
||||
fallbackTree: fallback,
|
||||
isHydrating,
|
||||
isResolved: false,
|
||||
isUnmounted: false,
|
||||
effects: [],
|
||||
@@ -283,17 +289,22 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
container
|
||||
} = suspense
|
||||
|
||||
// this is initial anchor on mount
|
||||
let { anchor } = suspense
|
||||
// unmount fallback tree
|
||||
if (fallbackTree.el) {
|
||||
// if the fallback tree was mounted, it may have been moved
|
||||
// as part of a parent suspense. get the latest anchor for insertion
|
||||
anchor = next(fallbackTree)
|
||||
unmount(fallbackTree, parentComponent, suspense, true)
|
||||
if (suspense.isHydrating) {
|
||||
suspense.isHydrating = false
|
||||
} else {
|
||||
// this is initial anchor on mount
|
||||
let { anchor } = suspense
|
||||
// unmount fallback tree
|
||||
if (fallbackTree.el) {
|
||||
// if the fallback tree was mounted, it may have been moved
|
||||
// as part of a parent suspense. get the latest anchor for insertion
|
||||
anchor = next(fallbackTree)
|
||||
unmount(fallbackTree, parentComponent, suspense, true)
|
||||
}
|
||||
// move content from off-dom container to actual container
|
||||
move(subTree, container, anchor, MoveType.ENTER)
|
||||
}
|
||||
// move content from off-dom container to actual container
|
||||
move(subTree, container, anchor, MoveType.ENTER)
|
||||
|
||||
const el = (vnode.el = subTree.el!)
|
||||
// suspense as the root node of a component...
|
||||
if (parentComponent && parentComponent.subTree === vnode) {
|
||||
@@ -367,19 +378,12 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
},
|
||||
|
||||
move(container, anchor, type) {
|
||||
move(
|
||||
suspense.isResolved ? suspense.subTree : suspense.fallbackTree,
|
||||
container,
|
||||
anchor,
|
||||
type
|
||||
)
|
||||
move(getCurrentTree(), container, anchor, type)
|
||||
suspense.container = container
|
||||
},
|
||||
|
||||
next() {
|
||||
return next(
|
||||
suspense.isResolved ? suspense.subTree : suspense.fallbackTree
|
||||
)
|
||||
return next(getCurrentTree())
|
||||
},
|
||||
|
||||
registerDep(instance, setupRenderEffect) {
|
||||
@@ -392,6 +396,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
})
|
||||
}
|
||||
|
||||
const hydratedEl = instance.vnode.el
|
||||
suspense.deps++
|
||||
instance
|
||||
.asyncDep!.catch(err => {
|
||||
@@ -411,14 +416,23 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
pushWarningContext(vnode)
|
||||
}
|
||||
handleSetupResult(instance, asyncSetupResult, suspense, false)
|
||||
// unset placeholder, otherwise this will be treated as a hydration mount
|
||||
vnode.el = null
|
||||
if (hydratedEl) {
|
||||
// vnode may have been replaced if an update happened before the
|
||||
// async dep is reoslved.
|
||||
vnode.el = hydratedEl
|
||||
}
|
||||
setupRenderEffect(
|
||||
instance,
|
||||
vnode,
|
||||
// component may have been moved before resolve
|
||||
parentNode(instance.subTree.el)!,
|
||||
next(instance.subTree),
|
||||
// component may have been moved before resolve.
|
||||
// if this is not a hydration, instance.subTree will be the comment
|
||||
// placeholder.
|
||||
hydratedEl
|
||||
? parentNode(hydratedEl)!
|
||||
: parentNode(instance.subTree.el)!,
|
||||
// anchor will not be used if this is hydration, so only need to
|
||||
// consider the comment placeholder case.
|
||||
hydratedEl ? null : next(instance.subTree),
|
||||
suspense,
|
||||
isSVG
|
||||
)
|
||||
@@ -449,6 +463,53 @@ function createSuspenseBoundary<HostNode, HostElement>(
|
||||
return suspense
|
||||
}
|
||||
|
||||
function hydrateSuspense(
|
||||
node: Node,
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
optimized: boolean,
|
||||
rendererInternals: RendererInternals,
|
||||
hydrateNode: (
|
||||
node: Node,
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
optimized: boolean
|
||||
) => Node | null
|
||||
): Node | null {
|
||||
const suspense = (vnode.suspense = createSuspenseBoundary(
|
||||
vnode,
|
||||
parentSuspense,
|
||||
parentComponent,
|
||||
node.parentNode,
|
||||
document.createElement('div'),
|
||||
null,
|
||||
isSVG,
|
||||
optimized,
|
||||
rendererInternals,
|
||||
true /* hydrating */
|
||||
))
|
||||
// there are two possible scenarios for server-rendered suspense:
|
||||
// - success: ssr content should be fully resolved
|
||||
// - failure: ssr content should be the fallback branch.
|
||||
// however, on the client we don't really know if it has failed or not
|
||||
// attempt to hydrate the DOM assuming it has succeeded, but we still
|
||||
// need to construct a suspense boundary first
|
||||
const result = hydrateNode(
|
||||
node,
|
||||
suspense.subTree,
|
||||
parentComponent,
|
||||
suspense,
|
||||
optimized
|
||||
)
|
||||
if (suspense.deps === 0) {
|
||||
suspense.resolve()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function normalizeSuspenseChildren(
|
||||
vnode: VNode
|
||||
): {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VNode, normalizeVNode, Text, Comment, Static, Fragment } from './vnode'
|
||||
import { queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||
import { flushPostFlushCbs } from './scheduler'
|
||||
import { ComponentInternalInstance } from './component'
|
||||
import { invokeDirectiveHook } from './directives'
|
||||
import { warn } from './warning'
|
||||
@@ -11,6 +11,11 @@ import {
|
||||
isString
|
||||
} from '@vue/shared'
|
||||
import { RendererInternals } from './renderer'
|
||||
import {
|
||||
SuspenseImpl,
|
||||
SuspenseBoundary,
|
||||
queueEffectWithSuspense
|
||||
} from './components/Suspense'
|
||||
|
||||
export type RootHydrateFunction = (
|
||||
vnode: VNode<Node, Element>,
|
||||
@@ -25,16 +30,27 @@ const enum DOMNodeTypes {
|
||||
|
||||
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({
|
||||
mt: mountComponent,
|
||||
p: patch,
|
||||
o: { patchProp, createText }
|
||||
}: RendererInternals<Node, Element>) {
|
||||
export function createHydrationFunctions(
|
||||
rendererInternals: RendererInternals<Node, Element>
|
||||
) {
|
||||
const {
|
||||
mt: mountComponent,
|
||||
p: patch,
|
||||
n: next,
|
||||
o: { patchProp, nextSibling, parentNode }
|
||||
} = rendererInternals
|
||||
|
||||
const hydrate: RootHydrateFunction = (vnode, container) => {
|
||||
if (__DEV__ && !container.hasChildNodes()) {
|
||||
warn(
|
||||
@@ -45,7 +61,7 @@ export function createHydrationFunctions({
|
||||
return
|
||||
}
|
||||
hasMismatch = false
|
||||
hydrateNode(container.firstChild!, vnode)
|
||||
hydrateNode(container.firstChild!, vnode, null, null)
|
||||
flushPostFlushCbs()
|
||||
if (hasMismatch && !__TEST__) {
|
||||
// this error should show up in production
|
||||
@@ -56,7 +72,8 @@ export function createHydrationFunctions({
|
||||
const hydrateNode = (
|
||||
node: Node,
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null = null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
optimized = false
|
||||
): Node | null => {
|
||||
const { type, shapeFlag } = vnode
|
||||
@@ -67,7 +84,7 @@ export function createHydrationFunctions({
|
||||
switch (type) {
|
||||
case Text:
|
||||
if (domType !== DOMNodeTypes.TEXT) {
|
||||
return handleMismtach(node, vnode, parentComponent)
|
||||
return handleMismtach(node, vnode, parentComponent, parentSuspense)
|
||||
}
|
||||
if ((node as Text).data !== vnode.children) {
|
||||
hasMismatch = true
|
||||
@@ -79,48 +96,83 @@ export function createHydrationFunctions({
|
||||
)
|
||||
;(node as Text).data = vnode.children as string
|
||||
}
|
||||
return node.nextSibling
|
||||
return nextSibling(node)
|
||||
case Comment:
|
||||
if (domType !== DOMNodeTypes.COMMENT) {
|
||||
return handleMismtach(node, vnode, parentComponent)
|
||||
return handleMismtach(node, vnode, parentComponent, parentSuspense)
|
||||
}
|
||||
return node.nextSibling
|
||||
return nextSibling(node)
|
||||
case Static:
|
||||
if (domType !== DOMNodeTypes.ELEMENT) {
|
||||
return handleMismtach(node, vnode, parentComponent)
|
||||
return handleMismtach(node, vnode, parentComponent, parentSuspense)
|
||||
}
|
||||
return node.nextSibling
|
||||
return nextSibling(node)
|
||||
case Fragment:
|
||||
return hydrateFragment(node, vnode, parentComponent, optimized)
|
||||
if (domType !== DOMNodeTypes.COMMENT) {
|
||||
return handleMismtach(node, vnode, parentComponent, parentSuspense)
|
||||
}
|
||||
return hydrateFragment(
|
||||
node as Comment,
|
||||
vnode,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
optimized
|
||||
)
|
||||
default:
|
||||
if (shapeFlag & ShapeFlags.ELEMENT) {
|
||||
if (
|
||||
domType !== DOMNodeTypes.ELEMENT ||
|
||||
vnode.type !== (node as Element).tagName.toLowerCase()
|
||||
) {
|
||||
return handleMismtach(node, vnode, parentComponent)
|
||||
return handleMismtach(node, vnode, parentComponent, parentSuspense)
|
||||
}
|
||||
return hydrateElement(
|
||||
node as Element,
|
||||
vnode,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
optimized
|
||||
)
|
||||
} else if (shapeFlag & ShapeFlags.COMPONENT) {
|
||||
// when setting up the render effect, if the initial vnode already
|
||||
// has .el set, the component will perform hydration instead of mount
|
||||
// on its sub-tree.
|
||||
mountComponent(vnode, null, null, parentComponent, null, false)
|
||||
const container = parentNode(node)!
|
||||
mountComponent(
|
||||
vnode,
|
||||
container,
|
||||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(container)
|
||||
)
|
||||
const subTree = vnode.component!.subTree
|
||||
return (subTree.anchor || subTree.el).nextSibling
|
||||
if (subTree) {
|
||||
return next(subTree)
|
||||
} else {
|
||||
// no subTree means this is an async component
|
||||
// try to locate the ending node
|
||||
return isComment(node) && node.data === '1'
|
||||
? locateClosingAsyncAnchor(node)
|
||||
: nextSibling(node)
|
||||
}
|
||||
} else if (shapeFlag & ShapeFlags.PORTAL) {
|
||||
if (domType !== DOMNodeTypes.COMMENT) {
|
||||
return handleMismtach(node, vnode, parentComponent)
|
||||
return handleMismtach(node, vnode, parentComponent, parentSuspense)
|
||||
}
|
||||
hydratePortal(vnode, parentComponent, optimized)
|
||||
return node.nextSibling
|
||||
hydratePortal(vnode, parentComponent, parentSuspense, optimized)
|
||||
return nextSibling(node)
|
||||
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
|
||||
// TODO Suspense
|
||||
return (vnode.type as typeof SuspenseImpl).hydrate(
|
||||
node,
|
||||
vnode,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(parentNode(node)!),
|
||||
optimized,
|
||||
rendererInternals,
|
||||
hydrateNode
|
||||
)
|
||||
} else if (__DEV__) {
|
||||
warn('Invalid HostVNode type:', type, `(${typeof type})`)
|
||||
}
|
||||
@@ -132,6 +184,7 @@ export function createHydrationFunctions({
|
||||
el: Element,
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
optimized = optimized || vnode.dynamicChildren !== null
|
||||
@@ -161,9 +214,9 @@ export function createHydrationFunctions({
|
||||
invokeDirectiveHook(onVnodeBeforeMount, parentComponent, vnode)
|
||||
}
|
||||
if (onVnodeMounted != null) {
|
||||
queuePostFlushCb(() => {
|
||||
queueEffectWithSuspense(() => {
|
||||
invokeDirectiveHook(onVnodeMounted, parentComponent, vnode)
|
||||
})
|
||||
}, parentSuspense)
|
||||
}
|
||||
}
|
||||
// children
|
||||
@@ -177,6 +230,7 @@ export function createHydrationFunctions({
|
||||
vnode,
|
||||
el,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
optimized
|
||||
)
|
||||
let hasWarned = false
|
||||
@@ -215,6 +269,7 @@ export function createHydrationFunctions({
|
||||
vnode: VNode,
|
||||
container: Element,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
optimized: boolean
|
||||
): Node | null => {
|
||||
optimized = optimized || vnode.dynamicChildren !== null
|
||||
@@ -226,7 +281,13 @@ export function createHydrationFunctions({
|
||||
? children[i]
|
||||
: (children[i] = normalizeVNode(children[i]))
|
||||
if (node) {
|
||||
node = hydrateNode(node, vnode, parentComponent, optimized)
|
||||
node = hydrateNode(
|
||||
node,
|
||||
vnode,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
optimized
|
||||
)
|
||||
} else {
|
||||
hasMismatch = true
|
||||
if (__DEV__ && !hasWarned) {
|
||||
@@ -237,34 +298,43 @@ export function createHydrationFunctions({
|
||||
hasWarned = true
|
||||
}
|
||||
// the SSRed DOM didn't contain enough nodes. Mount the missing ones.
|
||||
patch(null, vnode, container)
|
||||
patch(
|
||||
null,
|
||||
vnode,
|
||||
container,
|
||||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(container)
|
||||
)
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
const hydrateFragment = (
|
||||
node: Node,
|
||||
node: Comment,
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
const parent = node.parentNode as Element
|
||||
parent.insertBefore((vnode.el = createText('')), node)
|
||||
const next = hydrateChildren(
|
||||
node,
|
||||
vnode,
|
||||
parent,
|
||||
parentComponent,
|
||||
optimized
|
||||
return nextSibling(
|
||||
(vnode.anchor = hydrateChildren(
|
||||
nextSibling(node)!,
|
||||
vnode,
|
||||
parentNode(node)!,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
optimized
|
||||
)!)
|
||||
)
|
||||
parent.insertBefore((vnode.anchor = createText('')), next)
|
||||
return next
|
||||
}
|
||||
|
||||
const hydratePortal = (
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
const targetSelector = vnode.props && vnode.props.target
|
||||
@@ -277,6 +347,7 @@ export function createHydrationFunctions({
|
||||
vnode,
|
||||
target,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
optimized
|
||||
)
|
||||
} else if (__DEV__) {
|
||||
@@ -290,7 +361,8 @@ export function createHydrationFunctions({
|
||||
const handleMismtach = (
|
||||
node: Node,
|
||||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null
|
||||
) => {
|
||||
hasMismatch = true
|
||||
__DEV__ &&
|
||||
@@ -298,16 +370,43 @@ export function createHydrationFunctions({
|
||||
`Hydration node mismatch:\n- Client vnode:`,
|
||||
vnode.type,
|
||||
`\n- Server rendered DOM:`,
|
||||
node
|
||||
node,
|
||||
node.nodeType === DOMNodeTypes.TEXT ? `(text)` : ``
|
||||
)
|
||||
vnode.el = null
|
||||
const next = node.nextSibling
|
||||
const container = node.parentNode as Element
|
||||
const next = nextSibling(node)
|
||||
const container = parentNode(node)!
|
||||
container.removeChild(node)
|
||||
// TODO Suspense and SVG
|
||||
patch(null, vnode, container, next, parentComponent)
|
||||
// TODO Suspense
|
||||
patch(
|
||||
null,
|
||||
vnode,
|
||||
container,
|
||||
next,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(container)
|
||||
)
|
||||
return next
|
||||
}
|
||||
|
||||
const locateClosingAsyncAnchor = (node: Node | null): Node | null => {
|
||||
let match = 0
|
||||
while (node) {
|
||||
node = nextSibling(node)
|
||||
if (node && isComment(node)) {
|
||||
if (node.data === '1') match++
|
||||
if (node.data === '0') {
|
||||
if (match === 0) {
|
||||
return nextSibling(node)
|
||||
} else {
|
||||
match--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
return [hydrate, hydrateNode] as const
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ type UnmountChildrenFn<HostNode, HostElement> = (
|
||||
|
||||
export type MountComponentFn<HostNode, HostElement> = (
|
||||
initialVNode: VNode<HostNode, HostElement>,
|
||||
container: HostElement | null, // only null during hydration
|
||||
container: HostElement,
|
||||
anchor: HostNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||
@@ -219,7 +219,7 @@ type ProcessTextOrCommentFn<HostNode, HostElement> = (
|
||||
export type SetupRenderEffectFn<HostNode, HostElement> = (
|
||||
instance: ComponentInternalInstance,
|
||||
initialVNode: VNode<HostNode, HostElement>,
|
||||
container: HostElement | null, // only null during hydration
|
||||
container: HostElement,
|
||||
anchor: HostNode | null,
|
||||
parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
|
||||
isSVG: boolean
|
||||
@@ -991,7 +991,7 @@ function baseCreateRenderer<
|
||||
|
||||
const mountComponent: MountComponentFn<HostNode, HostElement> = (
|
||||
initialVNode,
|
||||
container, // only null during hydration
|
||||
container,
|
||||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
@@ -1031,9 +1031,10 @@ function baseCreateRenderer<
|
||||
parentSuspense.registerDep(instance, setupRenderEffect)
|
||||
|
||||
// Give it a placeholder if this is not hydration
|
||||
const placeholder = (instance.subTree = createVNode(Comment))
|
||||
processCommentNode(null, placeholder, container!, anchor)
|
||||
initialVNode.el = placeholder.el
|
||||
if (!initialVNode.el) {
|
||||
const placeholder = (instance.subTree = createVNode(Comment))
|
||||
processCommentNode(null, placeholder, container!, anchor)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1069,12 +1070,17 @@ function baseCreateRenderer<
|
||||
}
|
||||
if (initialVNode.el && hydrateNode) {
|
||||
// vnode has adopted host node - perform hydration instead of mount.
|
||||
hydrateNode(initialVNode.el as Node, subTree, instance)
|
||||
hydrateNode(
|
||||
initialVNode.el as Node,
|
||||
subTree,
|
||||
instance,
|
||||
parentSuspense
|
||||
)
|
||||
} else {
|
||||
patch(
|
||||
null,
|
||||
subTree,
|
||||
container!, // container is only null during hydration
|
||||
container,
|
||||
anchor,
|
||||
instance,
|
||||
parentSuspense,
|
||||
|
||||
@@ -212,7 +212,8 @@ export function createVNode(
|
||||
): VNode {
|
||||
if (!type) {
|
||||
if (__DEV__) {
|
||||
warn(`Invalid vnode type when creating vnode: ${type}.`)
|
||||
debugger
|
||||
warn(`fsef Invalid vnode type when creating vnode: ${type}.`)
|
||||
}
|
||||
type = Comment
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user