refactor(core): tree-shake defer block registry (#58424)

This commit updates the code of the incremental hydration feature to make the `DeferBlockRegistry` class tree-shakable. The class is only needed for hydration cases and it should not be included into client bundles for client-only apps.

PR Close #58424
This commit is contained in:
Andrew Kushnir 2024-10-29 20:51:22 -07:00 committed by Pawel Kozlowski
parent 0ca14e2c72
commit 600ef5eff2
9 changed files with 51 additions and 18 deletions

View file

@ -83,6 +83,8 @@ export const enum RuntimeErrorCode {
// (undocumented)
LOOP_TRACK_RECREATE = -956,
// (undocumented)
MISCONFIGURED_INCREMENTAL_HYDRATION = 508,
// (undocumented)
MISSING_DOCUMENT = 210,
// (undocumented)
MISSING_GENERATED_DEF = 906,

View file

@ -89,6 +89,7 @@ import {onTimer, scheduleTimerTrigger} from './timer_scheduler';
import {
addDepsToRegistry,
assertDeferredDependenciesLoaded,
assertIncrementalHydrationIsConfigured,
getLDeferBlockDetails,
getLoadingBlockAfter,
getMinimumDurationForState,
@ -98,7 +99,7 @@ import {
setLDeferBlockDetails,
setTDeferBlockDetails,
} from './utils';
import {DeferBlockRegistry} from './registry';
import {DEFER_BLOCK_REGISTRY, DeferBlockRegistry} from './registry';
import {incrementallyHydrateFromBlockName} from '../hydration/blocks';
import {isIncrementalHydrationEnabled} from '../hydration/utils';
@ -319,11 +320,11 @@ export function ɵɵdefer(
let registry: DeferBlockRegistry | null = null;
if (ssrUniqueId !== null) {
// TODO(incremental-hydration): explore how we can make
// `DeferBlockRegistry` tree-shakable for client-only cases.
registry = injector.get(DeferBlockRegistry);
ngDevMode && assertIncrementalHydrationIsConfigured(injector);
// Also store this defer block in the registry.
// Store this defer block in the registry, to have an access to
// internal data structures from hydration runtime code.
registry = injector.get(DEFER_BLOCK_REGISTRY);
registry.add(ssrUniqueId, {lView, tNode, lContainer});
}
@ -1444,7 +1445,7 @@ export function triggerDeferBlock(lView: LView, tNode: TNode) {
let registry: DeferBlockRegistry | null = null;
if (isIncrementalHydrationEnabled(injector)) {
registry = injector.get(DeferBlockRegistry);
registry = injector.get(DEFER_BLOCK_REGISTRY);
}
// Defer block is triggered, cleanup all registered trigger functions.
invokeAllTriggerCleanupFns(lDetails, registry);

View file

@ -5,10 +5,18 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ɵɵdefineInjectable} from '../di';
import {InjectionToken} from '../di/injection_token';
import {ɵɵdefineInjectable} from '../di/interface/defs';
import {DeferBlock} from './interfaces';
// TODO(incremental-hydration): refactor this so that it's not used in CSR cases
/**
* An internal injection token to reference `DeferBlockRegistry` implementation
* in a tree-shakable way.
*/
export const DEFER_BLOCK_REGISTRY = new InjectionToken<DeferBlockRegistry>(
ngDevMode ? 'DEFER_BLOCK_REGISTRY' : '',
);
/**
* The DeferBlockRegistry is used for incremental hydration purposes. It keeps
* track of the Defer Blocks that need hydration so we can effectively
@ -73,7 +81,7 @@ export class DeferBlockRegistry {
/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ /* @__PURE__ */ ɵɵdefineInjectable({
token: DeferBlockRegistry,
providedIn: 'root',
providedIn: null,
factory: () => new DeferBlockRegistry(),
});
}

View file

@ -6,6 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Injector} from '../di';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {isIncrementalHydrationEnabled} from '../hydration/utils';
import {assertIndexInDeclRange} from '../render3/assert';
import {DependencyDef} from '../render3/interfaces/definition';
import {TContainerNode, TNode} from '../render3/interfaces/node';
@ -181,3 +184,16 @@ export function isDeferBlock(tView: TView, tNode: TNode): boolean {
}
return !!tDetails && isTDeferBlockDetails(tDetails);
}
/** Throws an error if the incremental hydration is not enabled */
export function assertIncrementalHydrationIsConfigured(injector: Injector) {
if (!isIncrementalHydrationEnabled(injector)) {
throw new RuntimeError(
RuntimeErrorCode.MISCONFIGURED_INCREMENTAL_HYDRATION,
'Angular has detected that some `@defer` blocks use `hydrate` triggers, ' +
'but incremental hydration was not enabled. Please ensure that the `withIncrementalHydration()` ' +
'call is added as an argument for the `provideClientHydration()` function call ' +
'in your application config.',
);
}
}

View file

@ -81,6 +81,7 @@ export const enum RuntimeErrorCode {
MISSING_HYDRATION_ANNOTATIONS = -505,
HYDRATION_STABLE_TIMEDOUT = -506,
MISSING_SSR_CONTENT_INTEGRITY_MARKER = -507,
MISCONFIGURED_INCREMENTAL_HYDRATION = 508,
// Signal Errors
SIGNAL_WRITE_FROM_ILLEGAL_CONTEXT = 600,

View file

@ -45,6 +45,7 @@ import {
import {enableRetrieveHydrationInfoImpl, NGH_DATA_KEY, SSR_CONTENT_INTEGRITY_MARKER} from './utils';
import {enableFindMatchingDehydratedViewImpl} from './views';
import {bootstrapIncrementalHydration, enableRetrieveDeferBlockDataImpl} from './incremental';
import {DEFER_BLOCK_REGISTRY, DeferBlockRegistry} from '../defer/registry';
/**
* Indicates whether the hydration-related code was added,
@ -326,6 +327,10 @@ export function withIncrementalHydration(): Provider[] {
provide: IS_INCREMENTAL_HYDRATION_ENABLED,
useValue: true,
},
{
provide: DEFER_BLOCK_REGISTRY,
useClass: DeferBlockRegistry,
},
{
provide: ENVIRONMENT_INITIALIZER,
useValue: () => {

View file

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {DeferBlockRegistry} from '../defer/registry';
import {DEFER_BLOCK_REGISTRY} from '../defer/registry';
import {DEFER_PARENT_BLOCK_ID} from './interfaces';
import {NGH_DEFER_BLOCKS_KEY} from './utils';
import {Injector} from '../di';
@ -25,7 +25,7 @@ import {assertEqual} from '../util/assert';
* Note: This is utilizing serialized information to navigate up the tree
*/
export function getParentBlockHydrationQueue(deferBlockId: string, injector: Injector) {
const deferBlockRegistry = injector.get(DeferBlockRegistry);
const deferBlockRegistry = injector.get(DEFER_BLOCK_REGISTRY);
const transferState = injector.get(TransferState);
const deferBlockParents = transferState.get(NGH_DEFER_BLOCKS_KEY, {});
@ -66,7 +66,7 @@ export async function hydrateFromBlockName(
deferBlock: DeferBlock | null;
hydratedBlocks: Set<string>;
}> {
const deferBlockRegistry = injector.get(DeferBlockRegistry);
const deferBlockRegistry = injector.get(DEFER_BLOCK_REGISTRY);
// Make sure we don't hydrate/trigger the same thing multiple times
if (deferBlockRegistry.hydrating.has(blockName))

View file

@ -9,7 +9,7 @@
import {TransferState} from '../transfer_state';
import {onIdle} from '../defer/idle_scheduler';
import {DeferBlockTrigger} from '../defer/interfaces';
import {DeferBlockRegistry} from '../defer/registry';
import {DEFER_BLOCK_REGISTRY} from '../defer/registry';
import {onTimer} from '../defer/timer_scheduler';
import {Injector} from '../di';
import {assertDefined} from '../util/assert';
@ -168,7 +168,7 @@ function processAndInitTriggers(
async function setIdleTriggers(injector: Injector, elementTriggers: ElementTrigger[]) {
for (const elementTrigger of elementTriggers) {
const registry = injector.get(DeferBlockRegistry);
const registry = injector.get(DEFER_BLOCK_REGISTRY);
const onInvoke = () =>
incrementallyHydrateFromBlockName(
injector,
@ -198,7 +198,7 @@ async function setViewportTriggers(injector: Injector, elementTriggers: ElementT
async function setTimerTriggers(injector: Injector, elementTriggers: ElementTrigger[]) {
for (const elementTrigger of elementTriggers) {
const registry = injector.get(DeferBlockRegistry);
const registry = injector.get(DEFER_BLOCK_REGISTRY);
const onInvoke = async () =>
await incrementallyHydrateFromBlockName(
injector,

View file

@ -116,6 +116,9 @@
{
"name": "DEFER_BLOCK_ID"
},
{
"name": "DEFER_BLOCK_REGISTRY"
},
{
"name": "DEFER_BLOCK_STATE"
},
@ -143,9 +146,6 @@
{
"name": "DeferBlockInternalState"
},
{
"name": "DeferBlockRegistry"
},
{
"name": "DeferBlockState"
},