mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(core): cleanup incremental hydration code (#58363)
This cleans up some minor issues with the incremental hydration implementation and should make maintaining it a little easier. PR Close #58363
This commit is contained in:
parent
35d7ca55b2
commit
0f2f7ec754
10 changed files with 102 additions and 84 deletions
|
|
@ -13,7 +13,7 @@ import {
|
|||
PREFETCH_TRIGGER_CLEANUP_FNS,
|
||||
TRIGGER_CLEANUP_FNS,
|
||||
TriggerType,
|
||||
UNIQUE_SSR_ID,
|
||||
SSR_UNIQUE_ID,
|
||||
} from './interfaces';
|
||||
import {DeferBlockRegistry} from './registry';
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ export function invokeAllTriggerCleanupFns(
|
|||
// TODO(incremental-hydration): cleanup functions are invoked in multiple places
|
||||
// should we centralize where cleanup functions are invoked to this registry?
|
||||
if (registry !== null) {
|
||||
registry.invokeCleanupFns(lDetails[UNIQUE_SSR_ID]!);
|
||||
registry.invokeCleanupFns(lDetails[SSR_UNIQUE_ID]!);
|
||||
}
|
||||
|
||||
invokeTriggerCleanupFns(TriggerType.Prefetch, lDetails);
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ import {
|
|||
LOADING_AFTER_CLEANUP_FN,
|
||||
NEXT_DEFER_BLOCK_STATE,
|
||||
ON_COMPLETE_FNS,
|
||||
SSR_STATE,
|
||||
SSR_BLOCK_STATE,
|
||||
STATE_IS_FROZEN_UNTIL,
|
||||
TDeferBlockDetails,
|
||||
TriggerType,
|
||||
DeferBlock,
|
||||
UNIQUE_SSR_ID,
|
||||
SSR_UNIQUE_ID,
|
||||
} from './interfaces';
|
||||
import {onTimer, scheduleTimerTrigger} from './timer_scheduler';
|
||||
import {
|
||||
|
|
@ -144,7 +144,8 @@ function shouldTriggerWhenOnClient(
|
|||
if (!isPlatformBrowser(injector)) {
|
||||
return false;
|
||||
}
|
||||
const isServerRendered = lDetails[SSR_STATE] && lDetails[SSR_STATE] === DeferBlockState.Complete;
|
||||
const isServerRendered =
|
||||
lDetails[SSR_BLOCK_STATE] && lDetails[SSR_BLOCK_STATE] === DeferBlockState.Complete;
|
||||
const hasHydrateTriggers = tDetails.hydrateTriggers && tDetails.hydrateTriggers.size > 0;
|
||||
if (hasHydrateTriggers && isServerRendered && isIncrementalHydrationEnabled(injector)) {
|
||||
return false;
|
||||
|
|
@ -280,13 +281,12 @@ export function ɵɵdefer(
|
|||
// In client-only mode, this function is a noop.
|
||||
populateDehydratedViewsInLContainer(lContainer, tNode, lView);
|
||||
|
||||
let ssrState = null;
|
||||
let uniqueId: string | null = null;
|
||||
let ssrBlockState = null;
|
||||
let ssrUniqueId: string | null = null;
|
||||
if (lContainer[DEHYDRATED_VIEWS]?.length > 0) {
|
||||
// TODO(incremental-hydration): this is a hack, we should serialize defer
|
||||
const info = lContainer[DEHYDRATED_VIEWS][0].data;
|
||||
uniqueId = info[DEFER_BLOCK_ID] ?? null;
|
||||
ssrState = info[SERIALIZED_DEFER_BLOCK_STATE];
|
||||
ssrUniqueId = info[DEFER_BLOCK_ID] ?? null;
|
||||
ssrBlockState = info[SERIALIZED_DEFER_BLOCK_STATE];
|
||||
}
|
||||
|
||||
// Init instance-specific defer details and store it.
|
||||
|
|
@ -297,21 +297,21 @@ export function ɵɵdefer(
|
|||
null, // LOADING_AFTER_CLEANUP_FN
|
||||
null, // TRIGGER_CLEANUP_FNS
|
||||
null, // PREFETCH_TRIGGER_CLEANUP_FNS
|
||||
uniqueId, // UNIQUE_ID
|
||||
ssrState, // SSR_STATE
|
||||
ssrUniqueId, // SSR_UNIQUE_ID
|
||||
ssrBlockState, // SSR_BLOCK_STATE
|
||||
null, // ON_COMPLETE_FNS
|
||||
null, // HYDRATE_TRIGGER_CLEANUP_FNS
|
||||
];
|
||||
setLDeferBlockDetails(lView, adjustedIndex, lDetails);
|
||||
|
||||
let registry: DeferBlockRegistry | null = null;
|
||||
if (uniqueId !== null) {
|
||||
if (ssrUniqueId !== null) {
|
||||
// TODO(incremental-hydration): explore how we can make
|
||||
// `DeferBlockRegistry` tree-shakable for client-only cases.
|
||||
registry = injector.get(DeferBlockRegistry);
|
||||
|
||||
// Also store this defer block in the registry.
|
||||
registry.add(uniqueId, {lView, tNode, lContainer});
|
||||
registry.add(ssrUniqueId, {lView, tNode, lContainer});
|
||||
}
|
||||
|
||||
const cleanupTriggersFn = () => invokeAllTriggerCleanupFns(lDetails, registry);
|
||||
|
|
@ -415,7 +415,7 @@ export function ɵɵdeferHydrateWhen(rawValue: unknown) {
|
|||
// state.
|
||||
incrementallyHydrateFromBlockName(
|
||||
injector,
|
||||
getLDeferBlockDetails(lView, tNode)[UNIQUE_SSR_ID]!,
|
||||
getLDeferBlockDetails(lView, tNode)[SSR_UNIQUE_ID]!,
|
||||
(deferBlock: DeferBlock) => triggerAndWaitForCompletion(deferBlock),
|
||||
);
|
||||
}
|
||||
|
|
@ -534,7 +534,7 @@ export function ɵɵdeferHydrateOnImmediate() {
|
|||
} else {
|
||||
incrementallyHydrateFromBlockName(
|
||||
injector,
|
||||
lDetails[UNIQUE_SSR_ID]!,
|
||||
lDetails[SSR_UNIQUE_ID]!,
|
||||
(deferBlock: DeferBlock) => triggerAndWaitForCompletion(deferBlock),
|
||||
);
|
||||
}
|
||||
|
|
@ -643,6 +643,8 @@ export function ɵɵdeferHydrateOnHover() {
|
|||
// We are on the server and SSR for defer blocks is enabled.
|
||||
triggerDeferBlock(lView, tNode);
|
||||
}
|
||||
// The actual triggering of hydration on hover is handled by JSAction in
|
||||
// event_replay.ts.
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -711,6 +713,8 @@ export function ɵɵdeferHydrateOnInteraction() {
|
|||
// We are on the server and SSR for defer blocks is enabled.
|
||||
triggerDeferBlock(lView, tNode);
|
||||
}
|
||||
// The actual triggering of hydration on interaction is handled by JSAction in
|
||||
// event_replay.ts.
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -780,6 +784,8 @@ export function ɵɵdeferHydrateOnViewport() {
|
|||
// We are on the server and SSR for defer blocks is enabled.
|
||||
triggerDeferBlock(lView, tNode);
|
||||
}
|
||||
// The actual triggering of hydration on viewport happens in incremental.ts,
|
||||
// since these instructions won't exist for dehydrated content.
|
||||
}
|
||||
|
||||
/********** Helper functions **********/
|
||||
|
|
@ -853,7 +859,7 @@ export function scheduleDelayedHydrating(
|
|||
() =>
|
||||
incrementallyHydrateFromBlockName(
|
||||
injector,
|
||||
lDetails[UNIQUE_SSR_ID]!,
|
||||
lDetails[SSR_UNIQUE_ID]!,
|
||||
(deferBlock: DeferBlock) => triggerAndWaitForCompletion(deferBlock),
|
||||
),
|
||||
injector,
|
||||
|
|
@ -896,7 +902,7 @@ export function renderDeferBlockState(
|
|||
|
||||
const currentState = lDetails[DEFER_BLOCK_STATE];
|
||||
|
||||
const ssrState = lDetails[SSR_STATE];
|
||||
const ssrState = lDetails[SSR_BLOCK_STATE];
|
||||
if (ssrState !== null && newState < ssrState) {
|
||||
return; // trying to render a previous state, exit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,8 +210,8 @@ export const STATE_IS_FROZEN_UNTIL = 2;
|
|||
export const LOADING_AFTER_CLEANUP_FN = 3;
|
||||
export const TRIGGER_CLEANUP_FNS = 4;
|
||||
export const PREFETCH_TRIGGER_CLEANUP_FNS = 5;
|
||||
export const UNIQUE_SSR_ID = 6;
|
||||
export const SSR_STATE = 7;
|
||||
export const SSR_UNIQUE_ID = 6;
|
||||
export const SSR_BLOCK_STATE = 7;
|
||||
export const ON_COMPLETE_FNS = 8;
|
||||
export const HYDRATE_TRIGGER_CLEANUP_FNS = 9;
|
||||
|
||||
|
|
@ -259,12 +259,12 @@ export interface LDeferBlockDetails extends Array<unknown> {
|
|||
/**
|
||||
* Unique id of this defer block assigned during SSR.
|
||||
*/
|
||||
[UNIQUE_SSR_ID]: string | null;
|
||||
[SSR_UNIQUE_ID]: string | null;
|
||||
|
||||
/**
|
||||
* Defer block state after SSR.
|
||||
*/
|
||||
[SSR_STATE]: number | null;
|
||||
[SSR_BLOCK_STATE]: number | null;
|
||||
|
||||
/**
|
||||
* A set of callbacks to be invoked once the main content is rendered.
|
||||
|
|
|
|||
|
|
@ -9,13 +9,19 @@ import {ɵɵdefineInjectable} from '../di';
|
|||
import {DeferBlock} from './interfaces';
|
||||
|
||||
// TODO(incremental-hydration): refactor this so that it's not used in CSR cases
|
||||
/**
|
||||
* The DeferBlockRegistry is used for incremental hydration purposes. It keeps
|
||||
* track of the Defer Blocks that need hydration so we can effectively
|
||||
* navigate up to the top dehydrated defer block and fire appropriate cleanup
|
||||
* functions post hydration.
|
||||
*/
|
||||
export class DeferBlockRegistry {
|
||||
private registry = new Map<string, DeferBlock>();
|
||||
private cleanupFns = new Map<string, Function[]>();
|
||||
add(blockId: string, info: any) {
|
||||
add(blockId: string, info: DeferBlock) {
|
||||
this.registry.set(blockId, info);
|
||||
}
|
||||
get(blockId: string) {
|
||||
get(blockId: string): DeferBlock | null {
|
||||
return this.registry.get(blockId) ?? null;
|
||||
}
|
||||
// TODO(incremental-hydration): we need to determine when this should be invoked
|
||||
|
|
@ -38,7 +44,8 @@ export class DeferBlockRegistry {
|
|||
|
||||
invokeCleanupFns(blockId: string) {
|
||||
// TODO(incremental-hydration): determine if we can safely remove entries from
|
||||
// the cleanupFns after they've been invoked
|
||||
// the cleanupFns after they've been invoked. Can we reset
|
||||
// `this.cleanupFns.get(blockId)`?
|
||||
const fns = this.cleanupFns.get(blockId) ?? [];
|
||||
for (let fn of fns) {
|
||||
fn();
|
||||
|
|
@ -46,6 +53,8 @@ export class DeferBlockRegistry {
|
|||
}
|
||||
|
||||
// Blocks that are being hydrated.
|
||||
// TODO(incremental-hydration): cleanup task - we currently retain ids post hydration
|
||||
// and need to determine when we can remove them.
|
||||
hydrating = new Set();
|
||||
|
||||
/** @nocollapse */
|
||||
|
|
|
|||
|
|
@ -11,14 +11,9 @@ import {EventContract} from '@angular/core/primitives/event-dispatch';
|
|||
import {Attribute} from '@angular/core/primitives/event-dispatch';
|
||||
import {InjectionToken, Injector} from './di';
|
||||
import {RElement} from './render3/interfaces/renderer_dom';
|
||||
import {
|
||||
BLOCK_ELEMENT_MAP,
|
||||
EVENT_REPLAY_ENABLED_DEFAULT,
|
||||
IS_EVENT_REPLAY_ENABLED,
|
||||
} from './hydration/tokens';
|
||||
import {OnDestroy} from './interface/lifecycle_hooks';
|
||||
import {BLOCK_ELEMENT_MAP} from './hydration/tokens';
|
||||
|
||||
export const BLOCKNAME_ATTRIBUTE = 'ngb';
|
||||
export const DEFER_BLOCK_SSR_ID_ATTRIBUTE = 'ngb';
|
||||
|
||||
declare global {
|
||||
interface Element {
|
||||
|
|
@ -41,7 +36,9 @@ export function setJSActionAttributes(
|
|||
eventTypes: string[],
|
||||
parentDeferBlockId: string | null = null,
|
||||
) {
|
||||
if (!eventTypes.length || nativeElement.nodeType !== Node.ELEMENT_NODE) {
|
||||
// jsaction attributes specifically should be applied to elements and not comment nodes.
|
||||
// Comment nodes also have no setAttribute function. So this avoids errors.
|
||||
if (eventTypes.length === 0 || nativeElement.nodeType !== Node.ELEMENT_NODE) {
|
||||
return;
|
||||
}
|
||||
const existingAttr = nativeElement.getAttribute(Attribute.JSACTION);
|
||||
|
|
@ -57,7 +54,7 @@ export function setJSActionAttributes(
|
|||
|
||||
const blockName = parentDeferBlockId ?? '';
|
||||
if (blockName !== '' && parts.length > 0) {
|
||||
nativeElement.setAttribute(BLOCKNAME_ATTRIBUTE, blockName);
|
||||
nativeElement.setAttribute(DEFER_BLOCK_SSR_ID_ATTRIBUTE, blockName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +68,7 @@ export const sharedStashFunction = (rEl: RElement, eventType: string, listenerFn
|
|||
};
|
||||
|
||||
export const sharedMapFunction = (rEl: RElement, jsActionMap: Map<string, Set<Element>>) => {
|
||||
let blockName = rEl.getAttribute(BLOCKNAME_ATTRIBUTE) ?? '';
|
||||
let blockName = rEl.getAttribute(DEFER_BLOCK_SSR_ID_ATTRIBUTE) ?? '';
|
||||
const el = rEl as unknown as Element;
|
||||
const blockSet = jsActionMap.get(blockName) ?? new Set<Element>();
|
||||
if (!blockSet.has(el)) {
|
||||
|
|
@ -94,7 +91,7 @@ export function removeListenersFromBlocks(blockNames: string[], injector: Inject
|
|||
|
||||
export const removeListeners = (el: Element) => {
|
||||
el.removeAttribute(Attribute.JSACTION);
|
||||
el.removeAttribute(BLOCKNAME_ATTRIBUTE);
|
||||
el.removeAttribute(DEFER_BLOCK_SSR_ID_ATTRIBUTE);
|
||||
el.__jsaction_fns = undefined;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
DEFER_BLOCK_STATE as CURRENT_DEFER_BLOCK_STATE,
|
||||
DeferBlockTrigger,
|
||||
HydrateTriggerDetails,
|
||||
TDeferBlockDetails,
|
||||
} from '../defer/interfaces';
|
||||
import {getLDeferBlockDetails, getTDeferBlockDetails} from '../defer/utils';
|
||||
import {isDetachedByI18n} from '../i18n/utils';
|
||||
|
|
@ -34,6 +35,7 @@ import {
|
|||
CONTEXT,
|
||||
HEADER_OFFSET,
|
||||
HOST,
|
||||
INJECTOR,
|
||||
LView,
|
||||
PARENT,
|
||||
RENDERER,
|
||||
|
|
@ -44,7 +46,11 @@ import {
|
|||
import {unwrapLView, unwrapRNode} from '../render3/util/view_utils';
|
||||
import {TransferState} from '../transfer_state';
|
||||
|
||||
import {unsupportedProjectionOfDomNodes} from './error_handling';
|
||||
import {
|
||||
unsupportedProjectionOfDomNodes,
|
||||
validateMatchingNode,
|
||||
validateNodeExists,
|
||||
} from './error_handling';
|
||||
import {collectDomEventsInfo, convertHydrateTriggersToJsAction} from './event_replay';
|
||||
import {setJSActionAttributes} from '../event_delegation_utils';
|
||||
import {
|
||||
|
|
@ -183,13 +189,7 @@ function annotateComponentLViewForHydration(
|
|||
// Root elements might also be annotated with the `ngSkipHydration` attribute,
|
||||
// check if it's present before starting the serialization process.
|
||||
if (hostElement && !(hostElement as HTMLElement).hasAttribute(SKIP_HYDRATION_ATTR_NAME)) {
|
||||
return annotateHostElementForHydration(
|
||||
hostElement as HTMLElement,
|
||||
lView,
|
||||
null,
|
||||
context,
|
||||
injector,
|
||||
);
|
||||
return annotateHostElementForHydration(hostElement as HTMLElement, lView, null, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -226,13 +226,7 @@ function annotateLContainerForHydration(
|
|||
|
||||
// Serialize all views within this view container.
|
||||
const rootLView = lContainer[PARENT];
|
||||
const rootLViewNghIndex = annotateHostElementForHydration(
|
||||
hostElement,
|
||||
rootLView,
|
||||
null,
|
||||
context,
|
||||
injector,
|
||||
);
|
||||
const rootLViewNghIndex = annotateHostElementForHydration(hostElement, rootLView, null, context);
|
||||
|
||||
const renderer = componentLView[RENDERER] as Renderer2;
|
||||
|
||||
|
|
@ -336,7 +330,6 @@ function serializeLContainer(
|
|||
lView: LView,
|
||||
parentDeferBlockId: string | null,
|
||||
context: HydrationContext,
|
||||
injector: Injector,
|
||||
): SerializedContainerView[] {
|
||||
const views: SerializedContainerView[] = [];
|
||||
let lastViewAsString = '';
|
||||
|
|
@ -364,7 +357,7 @@ function serializeLContainer(
|
|||
// The `+1` is to capture the `<app-root />` element.
|
||||
numRootNodes = calcNumRootNodesInLContainer(childLView) + 1;
|
||||
|
||||
annotateLContainerForHydration(childLView, context, injector);
|
||||
annotateLContainerForHydration(childLView, context, lView[INJECTOR]!);
|
||||
|
||||
const componentLView = unwrapLView(childLView[HOST]) as LView<unknown>;
|
||||
|
||||
|
|
@ -426,15 +419,17 @@ function serializeLContainer(
|
|||
if ((node as Node).nodeType === Node.COMMENT_NODE) {
|
||||
annotateDeferBlockAnchorForHydration(node as RComment, deferBlockId);
|
||||
}
|
||||
}
|
||||
// Add JSAction attributes for root nodes that use some hydration triggers
|
||||
const actionList = convertHydrateTriggersToJsAction(tDetails.hydrateTriggers);
|
||||
for (let et of actionList) {
|
||||
context.eventTypesToReplay.regular.add(et);
|
||||
} else {
|
||||
ngDevMode && validateNodeExists(node, childLView, tNode);
|
||||
ngDevMode &&
|
||||
validateMatchingNode(node, Node.COMMENT_NODE, null, childLView, tNode, true);
|
||||
|
||||
annotateDeferBlockAnchorForHydration(node as RComment, deferBlockId);
|
||||
}
|
||||
|
||||
if (!isHydrateNeverBlock) {
|
||||
annotateDeferBlockRootNodesWithJsAction(actionList, rootNodes, deferBlockId);
|
||||
// Add JSAction attributes for root nodes that use some hydration triggers
|
||||
annotateDeferBlockRootNodesWithJsAction(tDetails, rootNodes, deferBlockId, context);
|
||||
}
|
||||
|
||||
// Use current block id as parent for nested routes.
|
||||
|
|
@ -445,6 +440,9 @@ function serializeLContainer(
|
|||
// (not at the view level).
|
||||
serializedView[DEFER_BLOCK_ID] = deferBlockId;
|
||||
}
|
||||
// DEFER_BLOCK_STATE is used for reconciliation in hydration, both regular and incremental.
|
||||
// We need to know which template is rendered when hydrating. So we serialize this state
|
||||
// regardless of hydration type.
|
||||
serializedView[DEFER_BLOCK_STATE] = lDetails[CURRENT_DEFER_BLOCK_STATE];
|
||||
}
|
||||
|
||||
|
|
@ -452,7 +450,7 @@ function serializeLContainer(
|
|||
// TODO(incremental-hydration): avoid copying of an object here
|
||||
serializedView = {
|
||||
...serializedView,
|
||||
...serializeLView(lContainer[i] as LView, parentDeferBlockId, context, injector),
|
||||
...serializeLView(lContainer[i] as LView, parentDeferBlockId, context),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -545,7 +543,6 @@ function serializeLView(
|
|||
lView: LView,
|
||||
parentDeferBlockId: string | null = null,
|
||||
context: HydrationContext,
|
||||
injector: Injector,
|
||||
): SerializedView {
|
||||
const ngh: SerializedView = {};
|
||||
const tView = lView[TVIEW];
|
||||
|
|
@ -670,7 +667,6 @@ function serializeLView(
|
|||
hostNode as LView,
|
||||
parentDeferBlockId,
|
||||
context,
|
||||
injector,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -682,7 +678,6 @@ function serializeLView(
|
|||
lView,
|
||||
parentDeferBlockId,
|
||||
context,
|
||||
injector,
|
||||
);
|
||||
} else if (Array.isArray(lView[i]) && !isLetDeclaration(tNode)) {
|
||||
// This is a component, annotate the host node with an `ngh` attribute.
|
||||
|
|
@ -695,7 +690,6 @@ function serializeLView(
|
|||
lView[i],
|
||||
parentDeferBlockId,
|
||||
context,
|
||||
injector,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -820,7 +814,6 @@ function annotateHostElementForHydration(
|
|||
lView: LView,
|
||||
parentDeferBlockId: string | null,
|
||||
context: HydrationContext,
|
||||
injector: Injector,
|
||||
): number | null {
|
||||
const renderer = lView[RENDERER];
|
||||
if (
|
||||
|
|
@ -835,7 +828,7 @@ function annotateHostElementForHydration(
|
|||
renderer.setAttribute(element, SKIP_HYDRATION_ATTR_NAME, '');
|
||||
return null;
|
||||
} else {
|
||||
const ngh = serializeLView(lView, parentDeferBlockId, context, injector);
|
||||
const ngh = serializeLView(lView, parentDeferBlockId, context);
|
||||
const index = context.serializedViewCollection.add(ngh);
|
||||
renderer.setAttribute(element, NGH_ATTR_NAME, index.toString());
|
||||
return index;
|
||||
|
|
@ -848,10 +841,7 @@ function annotateHostElementForHydration(
|
|||
* @param comment The Host element to be annotated
|
||||
* @param deferBlockId the id of the target defer block
|
||||
*/
|
||||
function annotateDeferBlockAnchorForHydration(
|
||||
comment: RComment,
|
||||
deferBlockId: string | null,
|
||||
): void {
|
||||
function annotateDeferBlockAnchorForHydration(comment: RComment, deferBlockId: string): void {
|
||||
comment.textContent = `ngh=${deferBlockId}`;
|
||||
}
|
||||
|
||||
|
|
@ -890,11 +880,24 @@ function isContentProjectedNode(tNode: TNode): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incremental hydration requires that any defer block root node
|
||||
* with interaction or hover triggers have all of their root nodes
|
||||
* trigger hydration with those events. So we need to make sure all
|
||||
* the root nodes of that block have the proper jsaction attribute
|
||||
* to ensure hydration is triggered, since the content is dehydrated
|
||||
*/
|
||||
function annotateDeferBlockRootNodesWithJsAction(
|
||||
actionList: string[],
|
||||
tDetails: TDeferBlockDetails,
|
||||
rootNodes: any[],
|
||||
parentDeferBlockId: string,
|
||||
context: HydrationContext,
|
||||
) {
|
||||
const actionList = convertHydrateTriggersToJsAction(tDetails.hydrateTriggers);
|
||||
for (let et of actionList) {
|
||||
context.eventTypesToReplay.regular.add(et);
|
||||
}
|
||||
|
||||
if (actionList.length > 0) {
|
||||
const elementNodes = (rootNodes as HTMLElement[]).filter(
|
||||
(rn) => rn.nodeType === Node.ELEMENT_NODE,
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ export function withI18nSupport(): Provider[] {
|
|||
/**
|
||||
* Returns a set of providers required to setup support for incremental hydration.
|
||||
* Requires hydration to be enabled separately.
|
||||
* Enabling incremental hydration also enables event replay for the entire app.
|
||||
*
|
||||
* @developerPreview
|
||||
*/
|
||||
|
|
@ -330,9 +331,10 @@ export function withIncrementalHydration(): Provider[] {
|
|||
useFactory: () => {
|
||||
if (isPlatformBrowser()) {
|
||||
const injector = inject(Injector);
|
||||
const doc = getDocument();
|
||||
return () => {
|
||||
bootstrapIncrementalHydration(getDocument(), injector);
|
||||
appendDeferBlocksToJSActionMap(getDocument(), injector);
|
||||
bootstrapIncrementalHydration(doc, injector);
|
||||
appendDeferBlocksToJSActionMap(doc, injector);
|
||||
};
|
||||
}
|
||||
return () => {}; // noop for the server code
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {whenStable, ApplicationRef} from '../application/application_ref';
|
|||
* If there are any dehydrated `@defer` blocks found along the way,
|
||||
* they are also stored and returned from the function (as a list of ids).
|
||||
*/
|
||||
export function findFirstKnownParentDeferBlock(deferBlockId: string, injector: Injector) {
|
||||
export function findFirstHydratedParentDeferBlock(deferBlockId: string, injector: Injector) {
|
||||
const deferBlockRegistry = injector.get(DeferBlockRegistry);
|
||||
const transferState = injector.get(TransferState);
|
||||
const deferBlockParents = transferState.get(NGH_DEFER_BLOCKS_KEY, {});
|
||||
|
|
@ -69,7 +69,7 @@ async function hydrateFromBlockNameImpl(
|
|||
// Make sure we don't hydrate/trigger the same thing multiple times
|
||||
if (deferBlockRegistry.hydrating.has(blockName)) return {deferBlock: null, hydratedBlocks};
|
||||
|
||||
const {blockId, deferBlock, dehydratedBlocks} = findFirstKnownParentDeferBlock(
|
||||
const {blockId, deferBlock, dehydratedBlocks} = findFirstHydratedParentDeferBlock(
|
||||
blockName,
|
||||
injector,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -32,14 +32,14 @@ import {BLOCK_ELEMENT_MAP, EVENT_REPLAY_ENABLED_DEFAULT, IS_EVENT_REPLAY_ENABLED
|
|||
import {
|
||||
sharedStashFunction,
|
||||
sharedMapFunction,
|
||||
BLOCKNAME_ATTRIBUTE,
|
||||
DEFER_BLOCK_SSR_ID_ATTRIBUTE,
|
||||
EventContractDetails,
|
||||
JSACTION_EVENT_CONTRACT,
|
||||
removeListenersFromBlocks,
|
||||
} from '../event_delegation_utils';
|
||||
import {APP_ID} from '../application/application_tokens';
|
||||
import {performanceMarkFeature} from '../util/performance';
|
||||
import {hydrateFromBlockName, findFirstKnownParentDeferBlock} from './blocks';
|
||||
import {hydrateFromBlockName, findFirstHydratedParentDeferBlock} from './blocks';
|
||||
import {DeferBlock, DeferBlockTrigger, HydrateTriggerDetails} from '../defer/interfaces';
|
||||
import {triggerAndWaitForCompletion} from '../defer/instructions';
|
||||
import {cleanupDehydratedViews, cleanupLContainer} from './cleanup';
|
||||
|
|
@ -226,7 +226,8 @@ export function invokeRegisteredReplayListeners(
|
|||
event: Event,
|
||||
currentTarget: Element | null,
|
||||
) {
|
||||
const blockName = (currentTarget && currentTarget.getAttribute(BLOCKNAME_ATTRIBUTE)) ?? '';
|
||||
const blockName =
|
||||
(currentTarget && currentTarget.getAttribute(DEFER_BLOCK_SSR_ID_ATTRIBUTE)) ?? '';
|
||||
if (/d\d+/.test(blockName)) {
|
||||
hydrateAndInvokeBlockListeners(blockName, injector, event, currentTarget!);
|
||||
} else if (event.eventPhase === EventPhase.REPLAY) {
|
||||
|
|
@ -259,7 +260,7 @@ async function triggerBlockHydration(
|
|||
onTriggerFn: (deferBlock: any) => void,
|
||||
) {
|
||||
// grab the list of dehydrated blocks and queue them up
|
||||
const {dehydratedBlocks} = findFirstKnownParentDeferBlock(blockName, injector);
|
||||
const {dehydratedBlocks} = findFirstHydratedParentDeferBlock(blockName, injector);
|
||||
for (let block of dehydratedBlocks) {
|
||||
hydratingBlocks.add(block);
|
||||
}
|
||||
|
|
@ -279,7 +280,7 @@ function replayQueuedBlockEvents(hydratedBlocks: Set<string>, injector: Injector
|
|||
// empty it
|
||||
blockEventQueue = [];
|
||||
for (let {event, currentTarget} of queue) {
|
||||
const blockName = currentTarget.getAttribute(BLOCKNAME_ATTRIBUTE)!;
|
||||
const blockName = currentTarget.getAttribute(DEFER_BLOCK_SSR_ID_ATTRIBUTE)!;
|
||||
if (hydratedBlocks.has(blockName)) {
|
||||
invokeListeners(event, currentTarget);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -459,7 +459,10 @@
|
|||
"name": "SOURCE"
|
||||
},
|
||||
{
|
||||
"name": "SSR_STATE"
|
||||
"name": "SSR_BLOCK_STATE"
|
||||
},
|
||||
{
|
||||
"name": "SSR_UNIQUE_ID"
|
||||
},
|
||||
{
|
||||
"name": "SVG_NAMESPACE"
|
||||
|
|
@ -509,9 +512,6 @@
|
|||
{
|
||||
"name": "TYPE"
|
||||
},
|
||||
{
|
||||
"name": "UNIQUE_SSR_ID"
|
||||
},
|
||||
{
|
||||
"name": "USE_VALUE"
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue