From 5182ab601abe403a79fa5094affb793a2a82f49a Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 12 Dec 2024 17:23:54 -0800 Subject: [PATCH] fix(core): avoid triggering `on timer` and `on idle` on the server This commit updates defer block logic to avoid triggering `on idle` and `on timer` on the server for regular SSR mode (when incremental hydration is not enabled). Triggering the mentioned condition resulted in invoking `setTimeout` calls, which delayed serialization on the server during SSR (the process was waiting for the timeouts to clear). --- packages/core/src/defer/triggering.ts | 10 ++++-- .../test/full_app_hydration_spec.ts | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/core/src/defer/triggering.ts b/packages/core/src/defer/triggering.ts index 68dc986b40904..ebadfe43e5259 100644 --- a/packages/core/src/defer/triggering.ts +++ b/packages/core/src/defer/triggering.ts @@ -73,11 +73,17 @@ export function scheduleDelayedTrigger( ) { const lView = getLView(); const tNode = getCurrentTNode()!; - const injector = lView[INJECTOR]; - const lDetails = getLDeferBlockDetails(lView, tNode); renderPlaceholder(lView, tNode); + // Exit early to avoid invoking `scheduleFn`, which would + // add `setTimeout` call and potentially delay serialization + // on the server unnecessarily. + if (!shouldTriggerDeferBlock(TriggerType.Regular, lView)) return; + + const injector = lView[INJECTOR]; + const lDetails = getLDeferBlockDetails(lView, tNode); + const cleanupFn = scheduleFn( () => triggerDeferBlock(TriggerType.Regular, lView, tNode), injector, diff --git a/packages/platform-server/test/full_app_hydration_spec.ts b/packages/platform-server/test/full_app_hydration_spec.ts index 35849009ddfda..bdb275488c89f 100644 --- a/packages/platform-server/test/full_app_hydration_spec.ts +++ b/packages/platform-server/test/full_app_hydration_spec.ts @@ -3052,6 +3052,41 @@ describe('platform-server full application hydration integration', () => { expect(clientRootNode.outerHTML).toContain('Hi!'); }); + it('should not trigger `setTimeout` calls for `on timer` triggers on the server', async () => { + const setTimeoutSpy = spyOn(globalThis, 'setTimeout').and.callThrough(); + + @Component({ + selector: 'my-lazy-cmp', + standalone: true, + template: 'Hi!', + }) + class MyLazyCmp {} + + @Component({ + standalone: true, + selector: 'app', + imports: [MyLazyCmp], + template: ` + @defer (on timer(123ms)) { + + } + `, + }) + class SimpleComponent {} + + const html = await ssr(SimpleComponent); + + const ssrContents = getAppContents(html); + expect(ssrContents).toContain(' { @Component({ selector: 'my-lazy-cmp',