Skip to content

Commit 2e11b6f

Browse files
thePunderWomanalxhub
authored andcommitted
refactor(core): prevent unnecessary hydration work in csr cases (angular#58366)
This adds a shouldHydrate check that prevents any additional work in cases when hydration is not necessary. PR Close angular#58366
1 parent acd4f80 commit 2e11b6f

File tree

2 files changed

+82
-60
lines changed

2 files changed

+82
-60
lines changed

packages/core/src/defer/instructions.ts

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,19 @@ export const DEFER_BLOCK_CONFIG = new InjectionToken<DeferBlockConfig>(
122122
);
123123

124124
/**
125-
* Determines whether defer blocks should be fully rendered through on the server side
126-
* for incremental hydration.
125+
* Determines whether "hydrate" triggers should be activated. Triggers are activated in the following cases:
126+
* - on the server, when incremental hydration is enabled, to trigger the block and render the main content
127+
* - on the client for blocks that were server-side rendered, to start hydration process
127128
*/
128-
function shouldTriggerWhenOnServer(injector: Injector) {
129-
return !isPlatformBrowser(injector) && isIncrementalHydrationEnabled(injector);
129+
function shouldActivateHydrateTrigger(lView: LView, tNode: TNode): boolean {
130+
const lDetails = getLDeferBlockDetails(lView, tNode);
131+
const injector = lView[INJECTOR]!;
132+
// TODO(incremental-hydration): ideally, this check should only happen once and then stored on
133+
// LDeferBlockDetails as a flag. This would make subsequent lookups very cheap.
134+
return (
135+
isIncrementalHydrationEnabled(injector) &&
136+
(!isPlatformBrowser(injector) || lDetails[SSR_UNIQUE_ID] !== null)
137+
);
130138
}
131139

132140
// TODO(incremental-hydration): Optimize this further by moving the calculation to earlier
@@ -347,9 +355,6 @@ export function ɵɵdeferWhen(rawValue: unknown) {
347355
renderedState === DeferBlockState.Placeholder) &&
348356
shouldTriggerWhenOnClient(lView[INJECTOR]!, lDetails, tDetails)
349357
) {
350-
// The `when` condition has changed to `true`, trigger defer block loading
351-
// if the block is either in initial (nothing is rendered) or a placeholder
352-
// state.
353358
triggerDeferBlock(lView, tNode);
354359
}
355360
} finally {
@@ -392,18 +397,22 @@ export function ɵɵdeferPrefetchWhen(rawValue: unknown) {
392397
*/
393398
export function ɵɵdeferHydrateWhen(rawValue: unknown) {
394399
const lView = getLView();
400+
const tNode = getSelectedTNode();
401+
402+
if (!shouldActivateHydrateTrigger(lView, tNode)) {
403+
return;
404+
}
395405

396406
// TODO(incremental-hydration): audit all defer instructions to reduce unnecessary work by
397407
// moving function calls inside their relevant control flow blocks
398408
const bindingIndex = nextBindingIndex();
399-
const tNode = getSelectedTNode();
400409
const tView = getTView();
401410
const hydrateTriggers = getHydrateTriggers(tView, tNode);
402411
hydrateTriggers.set(DeferBlockTrigger.When, null);
403412

404413
if (bindingUpdated(lView, bindingIndex, rawValue)) {
405414
const injector = lView[INJECTOR]!;
406-
if (shouldTriggerWhenOnServer(injector)) {
415+
if (!isPlatformBrowser(injector)) {
407416
// We are on the server and SSR for defer blocks is enabled.
408417
triggerDeferBlock(lView, tNode);
409418
} else {
@@ -434,12 +443,14 @@ export function ɵɵdeferHydrateWhen(rawValue: unknown) {
434443
export function ɵɵdeferHydrateNever() {
435444
const lView = getLView();
436445
const tNode = getCurrentTNode()!;
437-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
438-
hydrateTriggers.set(DeferBlockTrigger.Never, null);
446+
if (shouldActivateHydrateTrigger(lView, tNode)) {
447+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
448+
hydrateTriggers.set(DeferBlockTrigger.Never, null);
439449

440-
if (shouldTriggerWhenOnServer(lView[INJECTOR]!)) {
441-
// We are on the server and SSR for defer blocks is enabled.
442-
triggerDeferBlock(lView, tNode);
450+
if (!isPlatformBrowser(lView[INJECTOR]!)) {
451+
// We are on the server and SSR for defer blocks is enabled.
452+
triggerDeferBlock(lView, tNode);
453+
}
443454
}
444455
}
445456

@@ -466,14 +477,16 @@ export function ɵɵdeferPrefetchOnIdle() {
466477
export function ɵɵdeferHydrateOnIdle() {
467478
const lView = getLView();
468479
const tNode = getCurrentTNode()!;
469-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
470-
hydrateTriggers.set(DeferBlockTrigger.Idle, null);
480+
if (shouldActivateHydrateTrigger(lView, tNode)) {
481+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
482+
hydrateTriggers.set(DeferBlockTrigger.Idle, null);
471483

472-
if (shouldTriggerWhenOnServer(lView[INJECTOR]!)) {
473-
// We are on the server and SSR for defer blocks is enabled.
474-
triggerDeferBlock(lView, tNode);
475-
} else {
476-
scheduleDelayedHydrating(onIdle, lView, tNode);
484+
if (!isPlatformBrowser(lView[INJECTOR]!)) {
485+
// We are on the server and SSR for defer blocks is enabled.
486+
triggerDeferBlock(lView, tNode);
487+
} else {
488+
scheduleDelayedHydrating(onIdle, lView, tNode);
489+
}
477490
}
478491
}
479492

@@ -524,22 +537,26 @@ export function ɵɵdeferPrefetchOnImmediate() {
524537
export function ɵɵdeferHydrateOnImmediate() {
525538
const lView = getLView();
526539
const tNode = getCurrentTNode()!;
527-
const injector = lView[INJECTOR]!;
528-
const lDetails = getLDeferBlockDetails(lView, tNode);
529-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
530-
hydrateTriggers.set(DeferBlockTrigger.Immediate, null);
540+
if (shouldActivateHydrateTrigger(lView, tNode)) {
541+
const injector = lView[INJECTOR]!;
542+
const lDetails = getLDeferBlockDetails(lView, tNode);
543+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
544+
hydrateTriggers.set(DeferBlockTrigger.Immediate, null);
531545

532-
if (shouldTriggerWhenOnServer(injector)) {
533-
triggerDeferBlock(lView, tNode);
534-
} else {
535-
incrementallyHydrateFromBlockName(
536-
injector,
537-
lDetails[SSR_UNIQUE_ID]!,
538-
(deferBlock: DeferBlock) => triggerAndWaitForCompletion(deferBlock),
539-
);
546+
if (!isPlatformBrowser(injector)) {
547+
triggerDeferBlock(lView, tNode);
548+
} else {
549+
// TODO(incremental-hydration): see if we can resolve the circular dep issue
550+
// that required passing cleanup fns via the 3rd param here. Ideally we could
551+
// move the `triggerAndWaitForCompletion` call to a better location.
552+
incrementallyHydrateFromBlockName(
553+
injector,
554+
lDetails[SSR_UNIQUE_ID]!,
555+
(deferBlock: DeferBlock) => triggerAndWaitForCompletion(deferBlock),
556+
);
557+
}
540558
}
541559
}
542-
543560
/**
544561
* Creates runtime data structures for the `on timer` deferred trigger.
545562
* @param delay Amount of time to wait before loading the content.
@@ -566,14 +583,16 @@ export function ɵɵdeferPrefetchOnTimer(delay: number) {
566583
export function ɵɵdeferHydrateOnTimer(delay: number) {
567584
const lView = getLView();
568585
const tNode = getCurrentTNode()!;
569-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
570-
hydrateTriggers.set(DeferBlockTrigger.Timer, delay);
586+
if (shouldActivateHydrateTrigger(lView, tNode)) {
587+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
588+
hydrateTriggers.set(DeferBlockTrigger.Timer, delay);
571589

572-
if (shouldTriggerWhenOnServer(lView[INJECTOR]!)) {
573-
// We are on the server and SSR for defer blocks is enabled.
574-
triggerDeferBlock(lView, tNode);
575-
} else {
576-
scheduleDelayedHydrating(onTimer(delay), lView, tNode);
590+
if (!isPlatformBrowser(lView[INJECTOR]!)) {
591+
// We are on the server and SSR for defer blocks is enabled.
592+
triggerDeferBlock(lView, tNode);
593+
} else {
594+
scheduleDelayedHydrating(onTimer(delay), lView, tNode);
595+
}
577596
}
578597
}
579598

@@ -636,12 +655,14 @@ export function ɵɵdeferPrefetchOnHover(triggerIndex: number, walkUpTimes?: num
636655
export function ɵɵdeferHydrateOnHover() {
637656
const lView = getLView();
638657
const tNode = getCurrentTNode()!;
639-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
640-
hydrateTriggers.set(DeferBlockTrigger.Hover, null);
658+
if (shouldActivateHydrateTrigger(lView, tNode)) {
659+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
660+
hydrateTriggers.set(DeferBlockTrigger.Hover, null);
641661

642-
if (shouldTriggerWhenOnServer(lView[INJECTOR]!)) {
643-
// We are on the server and SSR for defer blocks is enabled.
644-
triggerDeferBlock(lView, tNode);
662+
if (!isPlatformBrowser(lView[INJECTOR]!)) {
663+
// We are on the server and SSR for defer blocks is enabled.
664+
triggerDeferBlock(lView, tNode);
665+
}
645666
}
646667
// The actual triggering of hydration on hover is handled by JSAction in
647668
// event_replay.ts.
@@ -706,12 +727,14 @@ export function ɵɵdeferPrefetchOnInteraction(triggerIndex: number, walkUpTimes
706727
export function ɵɵdeferHydrateOnInteraction() {
707728
const lView = getLView();
708729
const tNode = getCurrentTNode()!;
709-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
710-
hydrateTriggers.set(DeferBlockTrigger.Interaction, null);
730+
if (shouldActivateHydrateTrigger(lView, tNode)) {
731+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
732+
hydrateTriggers.set(DeferBlockTrigger.Interaction, null);
711733

712-
if (shouldTriggerWhenOnServer(lView[INJECTOR]!)) {
713-
// We are on the server and SSR for defer blocks is enabled.
714-
triggerDeferBlock(lView, tNode);
734+
if (!isPlatformBrowser(lView[INJECTOR]!)) {
735+
// We are on the server and SSR for defer blocks is enabled.
736+
triggerDeferBlock(lView, tNode);
737+
}
715738
}
716739
// The actual triggering of hydration on interaction is handled by JSAction in
717740
// event_replay.ts.
@@ -776,13 +799,15 @@ export function ɵɵdeferPrefetchOnViewport(triggerIndex: number, walkUpTimes?:
776799
export function ɵɵdeferHydrateOnViewport() {
777800
const lView = getLView();
778801
const tNode = getCurrentTNode()!;
779-
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
780-
hydrateTriggers.set(DeferBlockTrigger.Viewport, null);
781-
const injector = lView[INJECTOR]!;
802+
if (shouldActivateHydrateTrigger(lView, tNode)) {
803+
const hydrateTriggers = getHydrateTriggers(getTView(), tNode);
804+
hydrateTriggers.set(DeferBlockTrigger.Viewport, null);
805+
const injector = lView[INJECTOR]!;
782806

783-
if (shouldTriggerWhenOnServer(injector)) {
784-
// We are on the server and SSR for defer blocks is enabled.
785-
triggerDeferBlock(lView, tNode);
807+
if (!isPlatformBrowser(injector)) {
808+
// We are on the server and SSR for defer blocks is enabled.
809+
triggerDeferBlock(lView, tNode);
810+
}
786811
}
787812
// The actual triggering of hydration on viewport happens in incremental.ts,
788813
// since these instructions won't exist for dehydrated content.

packages/core/src/hydration/blocks.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,6 @@ export async function hydrateFromBlockName(
6262
deferBlock: DeferBlock | null;
6363
hydratedBlocks: Set<string>;
6464
}> {
65-
if (blockName == null) {
66-
return {deferBlock: null, hydratedBlocks};
67-
}
6865
const deferBlockRegistry = injector.get(DeferBlockRegistry);
6966

7067
// Make sure we don't hydrate/trigger the same thing multiple times

0 commit comments

Comments
 (0)