refactor(core): consolidate logic to determine whether node can be hydrated (#61409)

Several instructions were repeating the logic that checks if a specific can be hydrated. These changes move it into a common location.

PR Close #61409
This commit is contained in:
Kristiyan Kostadinov
2025-05-19 10:05:42 +02:00
committed by Jessica Janiuk
parent 6783fb7eae
commit 7a308ccc3b
6 changed files with 33 additions and 36 deletions
+20 -1
View File
@@ -13,7 +13,7 @@ import {LContainer} from '../render3/interfaces/container';
import {getDocument} from '../render3/interfaces/document';
import {RElement, RNode} from '../render3/interfaces/renderer_dom';
import {isRootView} from '../render3/interfaces/type_checks';
import {HEADER_OFFSET, LView, TVIEW, TViewType} from '../render3/interfaces/view';
import {HEADER_OFFSET, HYDRATION, LView, TVIEW, TViewType} from '../render3/interfaces/view';
import {makeStateKey, StateKey, TransferState} from '../transfer_state';
import {assertDefined, assertEqual} from '../util/assert';
import type {HydrationContext} from './annotate';
@@ -39,6 +39,9 @@ import {DeferBlockTrigger, HydrateTriggerDetails} from '../defer/interfaces';
import {hoverEventNames, interactionEventNames} from '../defer/dom_triggers';
import {DEHYDRATED_BLOCK_REGISTRY} from '../defer/registry';
import {sharedMapFunction} from '../event_delegation_utils';
import {isDetachedByI18n} from '../i18n/utils';
import {isInSkipHydrationBlock} from '../render3/state';
import {TNode} from '../render3/interfaces/node';
/**
* The name of the key used in the TransferState collection,
@@ -496,6 +499,22 @@ export function isDisconnectedNode(hydrationInfo: DehydratedView, index: number)
return !!initDisconnectedNodes(hydrationInfo)?.has(index);
}
/**
* Checks whether a node can be hydrated.
* @param lView View in which the node instance is placed.
* @param tNode Node to be checked.
*/
export function canHydrateNode(lView: LView, tNode: TNode): boolean {
const hydrationInfo = lView[HYDRATION];
return (
hydrationInfo !== null &&
!isInSkipHydrationBlock() &&
!isDetachedByI18n(tNode) &&
!isDisconnectedNode(hydrationInfo, tNode.index - HEADER_OFFSET)
);
}
/**
* Helper function to prepare text nodes for serialization by ensuring
* that seperate logical text blocks in the DOM remain separate after
@@ -17,13 +17,12 @@ import {
hasSkipHydrationAttrOnTNode,
} from '../../hydration/skip_hydration';
import {
canHydrateNode,
getSerializedContainerViews,
isDisconnectedNode,
markRNodeAsClaimedByHydration,
markRNodeAsSkippedByHydration,
setSegmentHead,
} from '../../hydration/utils';
import {isDetachedByI18n} from '../../i18n/utils';
import {assertDefined, assertEqual} from '../../util/assert';
import {clearElementContents, createElementNode} from '../dom_node_manipulation';
import {hasClassInput, hasStyleInput, TNode, TNodeType} from '../interfaces/node';
@@ -40,7 +39,6 @@ import {
getLView,
getNamespace,
getTView,
isInSkipHydrationBlock,
isSkipHydrationRootTNode,
lastNodeWasCreated,
leaveSkipHydrationBlock,
@@ -192,12 +190,7 @@ function locateOrCreateElementNodeImpl(
name: string,
index: number,
): RElement {
const hydrationInfo = lView[HYDRATION];
const isNodeCreationMode =
!hydrationInfo ||
isInSkipHydrationBlock() ||
isDetachedByI18n(tNode) ||
isDisconnectedNode(hydrationInfo, index);
const isNodeCreationMode = !canHydrateNode(lView, tNode);
lastNodeWasCreated(isNodeCreationMode);
// Regular creation mode.
@@ -206,6 +199,7 @@ function locateOrCreateElementNodeImpl(
}
// Hydration mode, looking up an existing element in DOM.
const hydrationInfo = lView[HYDRATION]!;
const native = locateNextRNode<RElement>(hydrationInfo, tView, lView, tNode)!;
ngDevMode && validateMatchingNode(native, Node.ELEMENT_NODE, name, lView, tNode);
ngDevMode && markRNodeAsClaimedByHydration(native);
@@ -8,12 +8,11 @@
import {validateMatchingNode, validateNodeExists} from '../../hydration/error_handling';
import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils';
import {
canHydrateNode,
getNgContainerSize,
isDisconnectedNode,
markRNodeAsClaimedByHydration,
setSegmentHead,
} from '../../hydration/utils';
import {isDetachedByI18n} from '../../i18n/utils';
import {assertDefined, assertEqual, assertNumber} from '../../util/assert';
import {createCommentNode} from '../dom_node_manipulation';
import {TNode, TNodeType} from '../interfaces/node';
@@ -26,7 +25,6 @@ import {
getCurrentTNode,
getLView,
getTView,
isInSkipHydrationBlock,
lastNodeWasCreated,
} from '../state';
import {elementLikeEndShared, elementLikeStartShared} from './shared';
@@ -134,12 +132,7 @@ function locateOrCreateElementContainerNode(
index: number,
): RComment {
let comment: RComment;
const hydrationInfo = lView[HYDRATION];
const isNodeCreationMode =
!hydrationInfo ||
isInSkipHydrationBlock() ||
isDisconnectedNode(hydrationInfo, index) ||
isDetachedByI18n(tNode);
const isNodeCreationMode = !canHydrateNode(lView, tNode);
lastNodeWasCreated(isNodeCreationMode);
@@ -149,6 +142,7 @@ function locateOrCreateElementContainerNode(
}
// Hydration mode, looking up existing elements in DOM.
const hydrationInfo = lView[HYDRATION]!;
const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode)!;
ngDevMode && validateNodeExists(currentRNode, lView, tNode);
@@ -10,11 +10,10 @@ import {TEMPLATES} from '../../hydration/interfaces';
import {locateNextRNode, siblingAfter} from '../../hydration/node_lookup_utils';
import {
calcSerializedContainerSize,
isDisconnectedNode,
canHydrateNode,
markRNodeAsClaimedByHydration,
setSegmentHead,
} from '../../hydration/utils';
import {isDetachedByI18n} from '../../i18n/utils';
import {populateDehydratedViewsInLContainer} from '../../linker/view_container_ref';
import {assertEqual} from '../../util/assert';
import {assertFirstCreatePass} from '../assert';
@@ -263,12 +262,7 @@ function locateOrCreateContainerAnchorImpl(
tNode: TNode,
index: number,
): RComment {
const hydrationInfo = lView[HYDRATION];
const isNodeCreationMode =
!hydrationInfo ||
isInSkipHydrationBlock() ||
isDetachedByI18n(tNode) ||
isDisconnectedNode(hydrationInfo, index);
const isNodeCreationMode = !canHydrateNode(lView, tNode);
lastNodeWasCreated(isNodeCreationMode);
// Regular creation mode.
@@ -276,6 +270,7 @@ function locateOrCreateContainerAnchorImpl(
return createContainerAnchorImpl(tView, lView, tNode, index);
}
const hydrationInfo = lView[HYDRATION]!;
const ssrId = hydrationInfo.data[TEMPLATES]?.[index] ?? null;
// Apply `ssrId` value to the underlying TView if it was not previously set.
@@ -7,8 +7,7 @@
*/
import {validateMatchingNode} from '../../hydration/error_handling';
import {locateNextRNode} from '../../hydration/node_lookup_utils';
import {isDisconnectedNode, markRNodeAsClaimedByHydration} from '../../hydration/utils';
import {isDetachedByI18n} from '../../i18n/utils';
import {canHydrateNode, markRNodeAsClaimedByHydration} from '../../hydration/utils';
import {assertEqual, assertIndexInRange} from '../../util/assert';
import {TElementNode, TNode, TNodeType} from '../interfaces/node';
import {RText} from '../interfaces/renderer_dom';
@@ -19,7 +18,6 @@ import {
getBindingIndex,
getLView,
getTView,
isInSkipHydrationBlock,
lastNodeWasCreated,
setCurrentTNode,
wasLastNodeCreated,
@@ -84,12 +82,7 @@ function locateOrCreateTextNodeImpl(
value: string,
index: number,
): RText {
const hydrationInfo = lView[HYDRATION];
const isNodeCreationMode =
!hydrationInfo ||
isInSkipHydrationBlock() ||
isDetachedByI18n(tNode) ||
isDisconnectedNode(hydrationInfo, index);
const isNodeCreationMode = !canHydrateNode(lView, tNode);
lastNodeWasCreated(isNodeCreationMode);
// Regular creation mode.
@@ -98,6 +91,7 @@ function locateOrCreateTextNodeImpl(
}
// Hydration mode, looking up an existing element in DOM.
const hydrationInfo = lView[HYDRATION]!;
const textNative = locateNextRNode(hydrationInfo, tView, lView, tNode) as RText;
ngDevMode && validateMatchingNode(textNative, Node.TEXT_NODE, null, lView, tNode);
@@ -191,6 +191,7 @@
"callHook",
"callHookInternal",
"callHooks",
"canHydrateNode",
"checkStable",
"cleanUpView",
"cleanupDehydratedIcuData",