mirror of https://github.com/vuejs/core.git
chore: tweaks
This commit is contained in:
parent
a9496dedb8
commit
d776a26d94
|
@ -410,7 +410,7 @@ function shouldProcessChildAsDynamic(
|
||||||
if (dynamicNodeCount === 2) {
|
if (dynamicNodeCount === 2) {
|
||||||
return prevDynamicCount > 0
|
return prevDynamicCount > 0
|
||||||
}
|
}
|
||||||
// For three or more dynamic nodes, mark the intermediate node as dynamic
|
// For three or more dynamic nodes, mark the middle nodes as dynamic
|
||||||
else if (dynamicNodeCount >= 3) {
|
else if (dynamicNodeCount >= 3) {
|
||||||
return prevDynamicCount > 0 && nextDynamicCount > 0
|
return prevDynamicCount > 0 && nextDynamicCount > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,13 @@ import {
|
||||||
getEscapedCssVarName,
|
getEscapedCssVarName,
|
||||||
includeBooleanAttr,
|
includeBooleanAttr,
|
||||||
isBooleanAttr,
|
isBooleanAttr,
|
||||||
isDynamicAnchor,
|
|
||||||
isKnownHtmlAttr,
|
isKnownHtmlAttr,
|
||||||
isKnownSvgAttr,
|
isKnownSvgAttr,
|
||||||
isOn,
|
isOn,
|
||||||
isRenderableAttrValue,
|
isRenderableAttrValue,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
isString,
|
isString,
|
||||||
isVaporFragmentEndAnchor,
|
isVaporAnchors,
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
stringifyStyle,
|
stringifyStyle,
|
||||||
|
@ -127,10 +126,8 @@ export function createHydrationFunctions(
|
||||||
|
|
||||||
function nextSibling(node: Node) {
|
function nextSibling(node: Node) {
|
||||||
let n = next(node)
|
let n = next(node)
|
||||||
// skip if:
|
// skip vapor mode specific anchors
|
||||||
// - dynamic anchors (`<!--[[-->`, `<!--][-->`)
|
if (n && isVaporAnchors(n)) {
|
||||||
// - vapor fragment end anchors (e.g. `<!--if-->`, `<!--for-->`)
|
|
||||||
if (n && (isDynamicAnchor(n) || isVaporFragmentEndAnchor(n))) {
|
|
||||||
n = next(n)
|
n = next(n)
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
@ -162,7 +159,8 @@ export function createHydrationFunctions(
|
||||||
slotScopeIds: string[] | null,
|
slotScopeIds: string[] | null,
|
||||||
optimized = false,
|
optimized = false,
|
||||||
): Node | null => {
|
): Node | null => {
|
||||||
if (isDynamicAnchor(node) || isVaporFragmentEndAnchor(node)) {
|
// skip vapor mode specific anchors
|
||||||
|
if (isVaporAnchors(node)) {
|
||||||
node = nextSibling(node)!
|
node = nextSibling(node)!
|
||||||
}
|
}
|
||||||
optimized = optimized || !!vnode.dynamicChildren
|
optimized = optimized || !!vnode.dynamicChildren
|
||||||
|
|
|
@ -107,7 +107,7 @@ export interface Renderer<HostElement = RendererElement> {
|
||||||
|
|
||||||
export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
|
export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
|
||||||
hydrate: RootHydrateFunction
|
hydrate: RootHydrateFunction
|
||||||
hydrateNode: ReturnType<typeof createHydrationFunctions>[1] | undefined
|
hydrateNode: ReturnType<typeof createHydrationFunctions>[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ElementNamespace = 'svg' | 'mathml' | undefined
|
export type ElementNamespace = 'svg' | 'mathml' | undefined
|
||||||
|
|
|
@ -30,9 +30,9 @@ import { renderEffect } from './renderEffect'
|
||||||
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
||||||
import {
|
import {
|
||||||
currentHydrationNode,
|
currentHydrationNode,
|
||||||
findVaporFragmentAnchor,
|
|
||||||
isHydrating,
|
isHydrating,
|
||||||
locateHydrationNode,
|
locateHydrationNode,
|
||||||
|
locateVaporFragmentAnchor,
|
||||||
} from './dom/hydration'
|
} from './dom/hydration'
|
||||||
import {
|
import {
|
||||||
insertionAnchor,
|
insertionAnchor,
|
||||||
|
@ -97,13 +97,13 @@ export const createFor = (
|
||||||
let parent: ParentNode | undefined | null
|
let parent: ParentNode | undefined | null
|
||||||
let parentAnchor: Node
|
let parentAnchor: Node
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
parentAnchor = findVaporFragmentAnchor(
|
parentAnchor = locateVaporFragmentAnchor(
|
||||||
currentHydrationNode!,
|
currentHydrationNode!,
|
||||||
FOR_ANCHOR_LABEL,
|
FOR_ANCHOR_LABEL,
|
||||||
)!
|
)!
|
||||||
if (__DEV__ && !parentAnchor) {
|
if (__DEV__ && !parentAnchor) {
|
||||||
// TODO warn, should not happen
|
// this should not happen
|
||||||
warn(`createFor anchor not found...`)
|
throw new Error(`v-for fragment anchor node was not found.`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
parentAnchor = __DEV__ ? createComment('for') : createTextNode()
|
||||||
|
|
|
@ -9,12 +9,11 @@ import { createComment, createTextNode } from './dom/node'
|
||||||
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
|
import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
currentHydrationNode,
|
currentHydrationNode,
|
||||||
findVaporFragmentAnchor,
|
|
||||||
isComment,
|
isComment,
|
||||||
isHydrating,
|
isHydrating,
|
||||||
locateHydrationNode,
|
locateHydrationNode,
|
||||||
|
locateVaporFragmentAnchor,
|
||||||
} from './dom/hydration'
|
} from './dom/hydration'
|
||||||
import { warn } from '@vue/runtime-dom'
|
|
||||||
|
|
||||||
export type Block =
|
export type Block =
|
||||||
| Node
|
| Node
|
||||||
|
@ -89,17 +88,17 @@ export class DynamicFragment extends VaporFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(label: string): void {
|
hydrate(label: string): void {
|
||||||
// for `v-if="false"` the node will be an empty comment node use it as the anchor.
|
// for `v-if="false"` the node will be an empty comment, use it as the anchor.
|
||||||
// otherwise, find next sibling vapor fragment anchor
|
// otherwise, find next sibling vapor fragment anchor
|
||||||
if (isComment(currentHydrationNode!, '')) {
|
if (isComment(currentHydrationNode!, '')) {
|
||||||
this.anchor = currentHydrationNode
|
this.anchor = currentHydrationNode
|
||||||
} else {
|
} else {
|
||||||
const anchor = findVaporFragmentAnchor(currentHydrationNode!, label)!
|
const anchor = locateVaporFragmentAnchor(currentHydrationNode!, label)!
|
||||||
if (anchor) {
|
if (anchor) {
|
||||||
this.anchor = anchor
|
this.anchor = anchor
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
// TODO warning, should not happen
|
// this should not happen
|
||||||
warn(`DynamicFragment anchor not found...`)
|
throw new Error(`${label} fragment anchor node was not found.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,11 @@ import {
|
||||||
setInsertionState,
|
setInsertionState,
|
||||||
} from '../insertionState'
|
} from '../insertionState'
|
||||||
import {
|
import {
|
||||||
_child,
|
|
||||||
disableHydrationNodeLookup,
|
disableHydrationNodeLookup,
|
||||||
enableHydrationNodeLookup,
|
enableHydrationNodeLookup,
|
||||||
next,
|
next,
|
||||||
} from './node'
|
} from './node'
|
||||||
import { isDynamicAnchor, isVaporFragmentEndAnchor } from '@vue/shared'
|
import { isVaporAnchors, isVaporFragmentAnchor } from '@vue/shared'
|
||||||
|
|
||||||
export let isHydrating = false
|
export let isHydrating = false
|
||||||
export let currentHydrationNode: Node | null = null
|
export let currentHydrationNode: Node | null = null
|
||||||
|
@ -33,7 +32,6 @@ function performHydration<T>(
|
||||||
|
|
||||||
// optimize anchor cache lookup
|
// optimize anchor cache lookup
|
||||||
;(Comment.prototype as any).$fs = undefined
|
;(Comment.prototype as any).$fs = undefined
|
||||||
;(Node.prototype as any).$nc = undefined
|
|
||||||
isOptimized = true
|
isOptimized = true
|
||||||
}
|
}
|
||||||
enableHydrationNodeLookup()
|
enableHydrationNodeLookup()
|
||||||
|
@ -101,24 +99,29 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hydrationPositionMap = new WeakMap<ParentNode, Node>()
|
||||||
|
|
||||||
function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
||||||
let node: Node | null
|
let node: Node | null
|
||||||
// prepend / firstChild
|
// prepend / firstChild
|
||||||
if (insertionAnchor === 0) {
|
if (insertionAnchor === 0) {
|
||||||
node = _child(insertionParent!)
|
node = insertionParent!.firstChild
|
||||||
} else if (insertionAnchor) {
|
} else if (insertionAnchor) {
|
||||||
// for dynamic children, use insertionAnchor as the node
|
// `insertionAnchor` is a Node, it is the DOM node to hydrate
|
||||||
|
// Template: `...<span/><!----><span/>...`// `insertionAnchor` is the placeholder
|
||||||
|
// SSR Output: `...<span/>Content<span/>...`// `insertionAnchor` is the actual node
|
||||||
node = insertionAnchor
|
node = insertionAnchor
|
||||||
} else {
|
} else {
|
||||||
node = insertionParent
|
node = insertionParent
|
||||||
? insertionParent.$nc || insertionParent.lastChild
|
? hydrationPositionMap.get(insertionParent) || insertionParent.lastChild
|
||||||
: currentHydrationNode
|
: currentHydrationNode
|
||||||
|
|
||||||
// if the last child is a vapor fragment end anchor, find the previous one
|
// if node is a vapor fragment anchor, find the previous one
|
||||||
if (hasFragmentAnchor && node && isVaporFragmentEndAnchor(node)) {
|
if (hasFragmentAnchor && node && isVaporFragmentAnchor(node)) {
|
||||||
node = node.previousSibling
|
node = node.previousSibling
|
||||||
if (__DEV__ && !node) {
|
if (__DEV__ && !node) {
|
||||||
// TODO warning, should not happen
|
// this should not happen
|
||||||
|
throw new Error(`vapor fragment anchor previous node was not found.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +156,8 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insertionParent && node) {
|
if (insertionParent && node) {
|
||||||
insertionParent.$nc = node!.previousSibling
|
const prev = node.previousSibling
|
||||||
|
if (prev) hydrationPositionMap.set(insertionParent, prev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,10 +170,6 @@ function locateHydrationNodeImpl(hasFragmentAnchor?: boolean) {
|
||||||
currentHydrationNode = node
|
currentHydrationNode = node
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEmptyText(node: Node): node is Text {
|
|
||||||
return node.nodeType === 3 && !(node as Text).data.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function locateEndAnchor(
|
export function locateEndAnchor(
|
||||||
node: Node | null,
|
node: Node | null,
|
||||||
open = '[',
|
open = '[',
|
||||||
|
@ -194,26 +194,26 @@ export function locateEndAnchor(
|
||||||
|
|
||||||
export function isNonHydrationNode(node: Node): boolean {
|
export function isNonHydrationNode(node: Node): boolean {
|
||||||
return (
|
return (
|
||||||
// empty text nodes
|
// empty text node
|
||||||
isEmptyText(node) ||
|
isEmptyTextNode(node) ||
|
||||||
// dynamic node anchors (<!--[[-->, <!--]]-->)
|
// vdom fragment end anchor (`<!--]-->`)
|
||||||
isDynamicAnchor(node) ||
|
|
||||||
// fragment end anchor (`<!--]-->`)
|
|
||||||
isComment(node, ']') ||
|
isComment(node, ']') ||
|
||||||
// vapor fragment end anchors
|
// vapor mode specific anchors
|
||||||
isVaporFragmentEndAnchor(node)
|
isVaporAnchors(node)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findVaporFragmentAnchor(
|
export function locateVaporFragmentAnchor(
|
||||||
node: Node,
|
node: Node,
|
||||||
anchorLabel: string,
|
anchorLabel: string,
|
||||||
): Comment | null {
|
): Comment | undefined {
|
||||||
let n = node.nextSibling
|
let n = node.nextSibling
|
||||||
while (n) {
|
while (n) {
|
||||||
if (isComment(n, anchorLabel)) return n
|
if (isComment(n, anchorLabel)) return n
|
||||||
n = n.nextSibling
|
n = n.nextSibling
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null
|
|
||||||
|
export function isEmptyTextNode(node: Node): node is Text {
|
||||||
|
return node.nodeType === 3 && !(node as Text).data.trim()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration'
|
||||||
import {
|
import {
|
||||||
DYNAMIC_END_ANCHOR_LABEL,
|
DYNAMIC_END_ANCHOR_LABEL,
|
||||||
DYNAMIC_START_ANCHOR_LABEL,
|
DYNAMIC_START_ANCHOR_LABEL,
|
||||||
isVaporFragmentEndAnchor,
|
isVaporAnchors,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
|
@ -25,33 +25,43 @@ export function _child(node: ParentNode): Node {
|
||||||
return node.firstChild!
|
return node.firstChild!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydration-specific version of `child`.
|
||||||
|
*
|
||||||
|
* This function skips leading fragment anchors to find the first node relevant
|
||||||
|
* for hydration matching against the client-side template structure.
|
||||||
|
*
|
||||||
|
* Problem:
|
||||||
|
* Template: `<div><slot />{{ msg }}</div>`
|
||||||
|
*
|
||||||
|
* Client Compiled Code (Simplified):
|
||||||
|
* const n2 = t0() // n2 = `<div> </div>`
|
||||||
|
* const n1 = _child(n2) // n1 = text node
|
||||||
|
* // ... slot creation ...
|
||||||
|
* _renderEffect(() => _setText(n1, _ctx.msg))
|
||||||
|
*
|
||||||
|
* SSR Output: `<div><!--[-->slot content<!--]-->Actual Text Node</div>`
|
||||||
|
*
|
||||||
|
* Hydration Mismatch:
|
||||||
|
* - During hydration, `n2` refers to the SSR `<div>`.
|
||||||
|
* - `_child(n2)` would return `<!--[-->`.
|
||||||
|
* - The client code expects `n1` to be the text node, but gets the comment.
|
||||||
|
* The subsequent `_setText(n1, ...)` would fail or target the wrong node.
|
||||||
|
*
|
||||||
|
* Solution (`__child`):
|
||||||
|
* - `__child(n2)` is used during hydration. It skips the SSR fragment anchors
|
||||||
|
* (`<!--[-->...<!--]-->`) and any other non-content nodes to find the
|
||||||
|
* "Actual Text Node", correctly matching the client's expectation for `n1`.
|
||||||
|
*/
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __child(node: ParentNode): Node {
|
export function __child(node: ParentNode): Node {
|
||||||
/**
|
|
||||||
* During hydration, the first child of a node not be the expected
|
|
||||||
* if the first child is slot
|
|
||||||
*
|
|
||||||
* for template code: `div><slot />{{ data }}</div>`
|
|
||||||
* - slot: 'slot',
|
|
||||||
* - data: 'hi',
|
|
||||||
*
|
|
||||||
* client side:
|
|
||||||
* const n2 = _template("<div> </div>")()
|
|
||||||
* const n1 = _child(n2) -> the text node
|
|
||||||
* _setInsertionState(n2, 0) -> slot fragment
|
|
||||||
*
|
|
||||||
* during hydration:
|
|
||||||
* const n2 = <div><!--[-->slot<!--]--><!--slot-->Hi</div> // server output
|
|
||||||
* const n1 = _child(n2) -> should be `Hi` instead of the slot fragment
|
|
||||||
* _setInsertionState(n2, 0) -> slot fragment
|
|
||||||
*/
|
|
||||||
let n = node.firstChild!
|
let n = node.firstChild!
|
||||||
|
|
||||||
if (isComment(n, '[')) {
|
if (isComment(n, '[')) {
|
||||||
n = locateEndAnchor(n)!.nextSibling!
|
n = locateEndAnchor(n)!.nextSibling!
|
||||||
}
|
}
|
||||||
|
|
||||||
while (n && isVaporFragmentEndAnchor(n)) {
|
while (n && isVaporAnchors(n)) {
|
||||||
n = n.nextSibling!
|
n = n.nextSibling!
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
@ -62,11 +72,14 @@ export function _nthChild(node: Node, i: number): Node {
|
||||||
return node.childNodes[i]
|
return node.childNodes[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydration-specific version of `nthChild`.
|
||||||
|
*/
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __nthChild(node: Node, i: number): Node {
|
export function __nthChild(node: Node, i: number): Node {
|
||||||
let n = node.firstChild!
|
let n = node.firstChild!
|
||||||
for (let start = 0; start < i; start++) {
|
for (let start = 0; start < i; start++) {
|
||||||
n = next(n) as ChildNode
|
n = __next(n) as ChildNode
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
@ -76,6 +89,46 @@ function _next(node: Node): Node {
|
||||||
return node.nextSibling!
|
return node.nextSibling!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hydration-specific version of `next`.
|
||||||
|
*
|
||||||
|
* SSR comment anchors (fragments `<!--[-->...<!--]-->`, dynamic `<!--[[-->...<!--]]-->`)
|
||||||
|
* disrupt standard `node.nextSibling` traversal during hydration. `_next` might
|
||||||
|
* return a comment node or an internal node of a fragment instead of skipping
|
||||||
|
* the entire fragment block.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* Template: `<div>Node1<!>Node2</div>` (where <!> is a dynamic component placeholder)
|
||||||
|
*
|
||||||
|
* Client Compiled Code (Simplified):
|
||||||
|
* const n2 = t0() // n2 = `<div>Node1<!---->Node2</div>`
|
||||||
|
* const n1 = _next(_child(n2)) // n1 = _next(Node1) returns `<!---->`
|
||||||
|
* _setInsertionState(n2, n1) // insertion anchor is `<!---->`
|
||||||
|
* const n0 = _createComponent(_ctx.Comp) // inserted before `<!---->`
|
||||||
|
*
|
||||||
|
* SSR Output: `<div>Node1<!--[-->Node3 Node4<!--]-->Node2</div>`
|
||||||
|
*
|
||||||
|
* Hydration Mismatch:
|
||||||
|
* - During hydration, `n2` refers to the SSR `<div>`.
|
||||||
|
* - `_child(n2)` returns `Node1`.
|
||||||
|
* - `_next(Node1)` would return `<!--[-->`.
|
||||||
|
* - The client logic expects `n1` to be the node *after* `Node1` in its structure
|
||||||
|
* (the placeholder), but gets the fragment start anchor `<!--[-->` from SSR.
|
||||||
|
* - Using `<!--[-->` as the insertion anchor for hydrating the component is incorrect.
|
||||||
|
*
|
||||||
|
* Solution (`__next`):
|
||||||
|
* - During hydration, `next.impl` is `__next`.
|
||||||
|
* - `n1 = __next(Node1)` is called.
|
||||||
|
* - `__next` recognizes that the immediate sibling `<!--[-->` is a fragment start anchor.
|
||||||
|
* - It skips the entire fragment block (`<!--[-->Node3 Node4<!--]-->`).
|
||||||
|
* - It returns the node immediately *after* the fragment's end anchor, which is `Node2`.
|
||||||
|
* - This correctly identifies the logical "next sibling" anchor (`Node2`) in the SSR structure,
|
||||||
|
* allowing the component to be hydrated correctly relative to `Node1` and `Node2`.
|
||||||
|
*
|
||||||
|
* This function ensures traversal correctly skips over non-hydration nodes and
|
||||||
|
* treats entire fragment/dynamic blocks (when starting *from* their beginning anchor)
|
||||||
|
* as single logical units to find the next actual sibling node for hydration matching.
|
||||||
|
*/
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export function __next(node: Node): Node {
|
export function __next(node: Node): Node {
|
||||||
// process dynamic node (<!--[[-->...<!--]]-->) as a single node
|
// process dynamic node (<!--[[-->...<!--]]-->) as a single node
|
||||||
|
@ -99,49 +152,36 @@ export function __next(node: Node): Node {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChildFn = (node: ParentNode) => Node
|
type DelegatedFunction<T extends (...args: any[]) => any> = T & {
|
||||||
type NextFn = (node: Node) => Node
|
impl: T
|
||||||
type NthChildFn = (node: Node, i: number) => Node
|
|
||||||
|
|
||||||
interface DelegatedChildFunction extends ChildFn {
|
|
||||||
impl: ChildFn
|
|
||||||
}
|
|
||||||
interface DelegatedNextFunction extends NextFn {
|
|
||||||
impl: NextFn
|
|
||||||
}
|
|
||||||
interface DelegatedNthChildFunction extends NthChildFn {
|
|
||||||
impl: NthChildFn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export const child: DelegatedChildFunction = node => {
|
export const child: DelegatedFunction<typeof _child> = node => {
|
||||||
return child.impl(node)
|
return child.impl(node)
|
||||||
}
|
}
|
||||||
child.impl = _child
|
child.impl = _child
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export const next: DelegatedNextFunction = node => {
|
export const next: DelegatedFunction<typeof _next> = node => {
|
||||||
return next.impl(node)
|
return next.impl(node)
|
||||||
}
|
}
|
||||||
next.impl = _next
|
next.impl = _next
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*! #__NO_SIDE_EFFECTS__ */
|
||||||
export const nthChild: DelegatedNthChildFunction = (node, i) => {
|
export const nthChild: DelegatedFunction<typeof _nthChild> = (node, i) => {
|
||||||
return nthChild.impl(node, i)
|
return nthChild.impl(node, i)
|
||||||
}
|
}
|
||||||
nthChild.impl = _nthChild
|
nthChild.impl = _nthChild
|
||||||
|
|
||||||
// During hydration, there might be differences between the server-rendered (SSR)
|
/**
|
||||||
// HTML and the client-side template.
|
* Enables hydration-specific node lookup behavior.
|
||||||
// For example, a dynamic node `<!>` in the template might be rendered as a
|
*
|
||||||
// `Fragment` (`<!--[-->...<!--]-->`) in the SSR output.
|
* Temporarily switches the implementations of the exported
|
||||||
// The content of the `Fragment` affects the lookup results of the `next` and
|
* `child`, `next`, and `nthChild` functions to their hydration-specific
|
||||||
// `nthChild` functions.
|
* versions (`__child`, `__next`, `__nthChild`). This allows traversal
|
||||||
// To ensure the hydration process correctly finds nodes, we need to treat the
|
* logic to correctly handle SSR comment anchors during hydration.
|
||||||
// `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 {
|
export function enableHydrationNodeLookup(): void {
|
||||||
child.impl = __child
|
child.impl = __child
|
||||||
next.impl = __next
|
next.impl = __next
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
export let insertionParent:
|
export let insertionParent: ParentNode | undefined
|
||||||
| (ParentNode & {
|
|
||||||
// the next child node to be hydrated
|
|
||||||
$nc?: Node | null
|
|
||||||
})
|
|
||||||
| undefined
|
|
||||||
export let insertionAnchor: Node | 0 | undefined
|
export let insertionAnchor: Node | 0 | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,7 @@ export function isDynamicAnchor(node: Node): node is Comment {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVaporFragmentEndAnchor(node: Node): node is Comment {
|
export function isVaporFragmentAnchor(node: Node): node is Comment {
|
||||||
if (node.nodeType !== 8) return false
|
if (node.nodeType !== 8) return false
|
||||||
|
|
||||||
const data = (node as Comment).data
|
const data = (node as Comment).data
|
||||||
|
@ -26,3 +26,7 @@ export function isVaporFragmentEndAnchor(node: Node): node is Comment {
|
||||||
data === DYNAMIC_COMPONENT_ANCHOR_LABEL
|
data === DYNAMIC_COMPONENT_ANCHOR_LABEL
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVaporAnchors(node: Node): node is Comment {
|
||||||
|
return isDynamicAnchor(node) || isVaporFragmentAnchor(node)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue