refactor: add enableHydrationNodeLookup and disableHydrationNodeLookup functions for node handling

This commit is contained in:
daiwei 2025-04-23 15:43:58 +08:00
parent 1248172216
commit 25b8fbe2fd
5 changed files with 79 additions and 38 deletions

View File

@ -84,6 +84,10 @@ const getContainerType = (
return undefined
}
export function isDynamicAnchor(node: Node): boolean {
return isComment(node) && (node.data === '[[' || node.data === ']]')
}
export const isComment = (node: Node): node is Comment =>
node.nodeType === DOMNodeTypes.COMMENT
@ -119,10 +123,6 @@ export function createHydrationFunctions(
},
} = rendererInternals
function isDynamicAnchor(node: Node): boolean {
return isComment(node) && (node.data === '[[' || node.data === ']]')
}
function nextSibling(node: Node) {
let n = next(node)
// skip dynamic child anchor

View File

@ -557,3 +557,7 @@ export { startMeasure, endMeasure } from './profiling'
* @internal
*/
export { initFeatureFlags } from './featureFlags'
/**
* @internal
*/
export { isDynamicAnchor } from './hydration'

View File

@ -1,12 +1,16 @@
import { warn } from '@vue/runtime-dom'
import {
type Anchor,
insertionAnchor,
insertionParent,
resetInsertionState,
setInsertionState,
} from '../insertionState'
import { child, next } from './node'
import {
child,
disableHydrationNodeLookup,
enableHydrationNodeLookup,
next,
} from './node'
export let isHydrating = false
export let currentHydrationNode: Node | null = null
@ -25,18 +29,26 @@ export function withHydration(container: ParentNode, fn: () => void): void {
;(Comment.prototype as any).$fs = undefined
isOptimized = true
}
enableHydrationNodeLookup()
isHydrating = true
setInsertionState(container, 0)
const res = fn()
resetInsertionState()
currentHydrationNode = null
isHydrating = false
disableHydrationNodeLookup()
return res
}
export let adoptTemplate: (node: Node, template: string) => Node | null
export let locateHydrationNode: () => void
type Anchor = Comment & {
// cached matching fragment start to avoid repeated traversal
// on nested fragments
$fs?: Anchor
}
export const isComment = (node: Node, data: string): node is Anchor =>
node.nodeType === 8 && (node as Comment).data === data
@ -120,10 +132,6 @@ function locateHydrationNodeImpl() {
currentHydrationNode = node
}
export function isDynamicAnchor(node: Node): node is Comment {
return isComment(node, '[[') || isComment(node, ']]')
}
export function isEmptyText(node: Node): node is Text {
return node.nodeType === 3 && !(node as Text).data.trim()
}

View File

@ -1,10 +1,5 @@
import {
isComment,
isDynamicAnchor,
isEmptyText,
isHydrating,
locateEndAnchor,
} from './hydration'
import { isDynamicAnchor } from '@vue/runtime-dom'
import { isComment, isEmptyText, locateEndAnchor } from './hydration'
/*! #__NO_SIDE_EFFECTS__ */
export function createTextNode(value = ''): Text {
@ -27,9 +22,12 @@ export function child(node: ParentNode): Node {
}
/*! #__NO_SIDE_EFFECTS__ */
export function nthChild(node: Node, i: number): Node {
if (!isHydrating) return node.childNodes[i]
export function _nthChild(node: Node, i: number): Node {
return node.childNodes[i]
}
/*! #__NO_SIDE_EFFECTS__ */
export function __nthChild(node: Node, i: number): Node {
let n = node.firstChild!
for (let start = 0; start < i; start++) {
n = next(n) as ChildNode
@ -38,9 +36,12 @@ export function nthChild(node: Node, i: number): Node {
}
/*! #__NO_SIDE_EFFECTS__ */
export function next(node: Node): Node {
if (!isHydrating) return node.nextSibling!
function _next(node: Node): Node {
return node.nextSibling!
}
/*! #__NO_SIDE_EFFECTS__ */
function __next(node: Node): Node {
// process fragment as a single node
if (node && isComment(node, '[')) {
node = locateEndAnchor(node)!
@ -53,3 +54,46 @@ export function next(node: Node): Node {
}
return n
}
type NextFn = (node: Node) => Node
type NthChildFn = (node: Node, i: number) => Node
interface DelegatedNextFunction extends NextFn {
impl: NextFn
}
interface DelegatedNthChildFunction extends NthChildFn {
impl: NthChildFn
}
/*! #__NO_SIDE_EFFECTS__ */
export const next: DelegatedNextFunction = node => {
return next.impl(node)
}
next.impl = _next
/*! #__NO_SIDE_EFFECTS__ */
export const nthChild: DelegatedNthChildFunction = (node, i) => {
return nthChild.impl(node, i)
}
nthChild.impl = _nthChild
// During hydration, there might be differences between the server-rendered (SSR)
// HTML and the client-side template.
// For example, a dynamic node `<!>` in the template might be rendered as a
// `Fragment` (`<!--[-->...<!--]-->`) in the SSR output.
// The content of the `Fragment` affects the lookup results of the `next` and
// `nthChild` functions.
// To ensure the hydration process correctly finds nodes, we need to treat the
// `Fragment` as a single node.
// Therefore, during hydration, we need to temporarily switch the implementations
// of `next` and `nthChild`. After hydration is complete, their implementations
// are restored to the original versions.
export function enableHydrationNodeLookup(): void {
next.impl = __next
nthChild.impl = __nthChild
}
export function disableHydrationNodeLookup(): void {
next.impl = _next
nthChild.impl = _nthChild
}

View File

@ -1,10 +1,5 @@
export let insertionParent:
| (ParentNode & {
// cached the last dynamic start anchor
$lds?: Anchor
})
| undefined
export let insertionAnchor: Node | 0 | undefined | null
export let insertionParent: ParentNode | undefined
export let insertionAnchor: Node | 0 | undefined
/**
* This function is called before a block type that requires insertion
@ -19,13 +14,3 @@ export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
export function resetInsertionState(): void {
insertionParent = insertionAnchor = undefined
}
export function setInsertionAnchor(anchor: Node | null): void {
insertionAnchor = anchor
}
export type Anchor = Comment & {
// cached matching fragment start to avoid repeated traversal
// on nested fragments
$fs?: Anchor
}