From 6133cb1ef703c506ff748e79569572bbeb5de80f Mon Sep 17 00:00:00 2001 From: Juraj Roka <95219754+jr-rk@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:10:00 +0200 Subject: [PATCH] Backport of Fix home-page SSR->CSR flicker --- _build.log | Bin 0 -> 50494 bytes _install.log | Bin 0 -> 5550 bytes _spec.log | Bin 0 -> 3908 bytes angular.json | 4 +- src/app/app.component.spec.ts | 63 +++++++++++++++- src/app/app.component.ts | 40 +++++++++- src/index.html | 119 ++++++++++++++++++++++++++++++ src/themes/eager-themes.module.ts | 9 ++- src/typings.d.ts | 9 +++ 9 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 _build.log create mode 100644 _install.log create mode 100644 _spec.log diff --git a/_build.log b/_build.log new file mode 100644 index 0000000000000000000000000000000000000000..b55b8f14430dc598e4bb84c1983432393e0dc101 GIT binary patch literal 50494 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 literal 0 HcmV?d00001 diff --git a/_install.log b/_install.log new file mode 100644 index 0000000000000000000000000000000000000000..3a6d92c53e9f878d367dbc53246997c39f7e8648 GIT binary patch literal 5550 zcmds*TW`}q5QXO%iT@Ds#LaEfmICD>b14!MLINHONNr-MN#caWEd(KcI`EyHICg2% zhD42`%Cen!FEeN6?Ci|``8~8f>sw|uOL-0~=YN~4W=B@H6=T*C6yL?-oxAj+%ftJg`P*qQtNk%c)BGL>@%d7?6AgN4Kd}u?{n3v_7^+$k`Z4Tw}~b z{%UVjrxSluKi$a7Uu7wNsF&3vJxbyPl`3OU?qJiSs@M2yMEcCFY&d|N*X>3 z)=I!2KM(CCSU&N)=u>@*X|HL^?Oou3I4N5sa6xU?&*7ol=4=LRGk11GT0LtE$Sh{_&+;IdXVokRijoDh@JdW-!7F8Q%DDD;_E~vl-a4R%c5dzL z5%IeVf0a9WgkBWKs`5N%Ju5;lFRGGn(<3^$d&a^3Tlir8*cv~?gB?6LUDfc#zLKZO z->OHn=icX5?jLb)jH`o5mcA&xdT^{4vfV!Vd>QWp(oL9EF-_t?3zn+dP&Vz=*gpUF zLD`1Ws^+|6QSGAYQe2k>V!O+ns&rO}pQ`1GitPVv8Y8Qe=armCe9d2EHk0*NY*&WV z1c53($Dq7eIk<9k<<${>OGj0n7*+Jk@uccQtzUgt^(>^Gb*Uat`R&=eDpJ|$kP*%& zF|s}vqhe-1O@r8OVpCf7aw2svz-}Kwbm{! zerj%=4*cY*h1^T);pA7v3I94Rla~i=6xLP;YtD{ZOrDIq@SmUi=uE4RClwX6$9g>T zNZ@r&HQLPP%k$2EgtVxsv|~?V=(1=oZn}S_*ZN-@L?I3q(SDV6q!TTjXZWduM+486 zbryF~=Wx;>4kvImWR$tN&fxS#JKFKJw9D>ZoQggTk-iOQ{dA^A)-UTQcARxS`aDEJ S?F=4K)<=@Ikf#$(G59Zj|A=k? literal 0 HcmV?d00001 diff --git a/_spec.log b/_spec.log new file mode 100644 index 0000000000000000000000000000000000000000..65bf531fb05179457eca72ce8ee1f1df7ca9f27a GIT binary patch literal 3908 zcmchaTTh!u5Qe9>mGTFAwL*>LD8)EHXyeLKz=0+$>A}uHRg?>A8{*=VY!m8S^iTJ? zhv%K~#vdegjZo2Q_w(+~&d%YTnf2d)&Fsj=Hn7Ba=HIt=&&sw*i}99yYM-+|uoK3q zb!@_zY@OC+wPig{wpp*SGiJU4&k-$u%d8Ep&&m+`4t)ym3G)s+WoDB(zjIDbS$hMo zV}AG8>CE?jf%nkPePzs8vpRHI>qGOHeuNY~z7tx=H(~e8W$GYp-{ty{v!s*!>n`V8 zevP3#;ajAY9R|q!8@#K|KiN0XNF&it-5RpdMM;z2IzqB8{DV$*5S=WZWlmOt`x_{v zvu@CL3eCvwU?nxlu*+IZCo))tu3r^2%sggK@YsR<0L?2q#SUZFYu#gSY>^9Iy9K^`znfSWk8SS}rsZ&Z$Sx@|Q65!p3i%HUtAU=&nM(1$ zJg1(6k||u3apaj6_XsP>f-SI={9a+H@07B@)wGcnR?$XLAaDJ9)?KE_sxm#m!}3Yx zl1xS3=_9pASypE&@4K@5OKhTui7J-xafJvD?s z-5#9BoD(WwKnE-8zyrob!OQl3Q8#%q^;n-t>Lh{S;2}lH%#k1>1N)3H*?D)Fr{i3 TnLJT7H_0NZ>pLJYUZMX0nc`vi literal 0 HcmV?d00001 diff --git a/angular.json b/angular.json index a57b7582109..cf0b2f24038 100644 --- a/angular.json +++ b/angular.json @@ -99,8 +99,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "3mb", - "maximumError": "5mb" + "maximumWarning": "5.5mb", + "maximumError": "6mb" }, { "type": "anyComponentStyle", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index e921c67acea..9294f1ff1dd 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,9 +1,10 @@ import { Store, StoreModule } from '@ngrx/store'; -import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { ApplicationRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { BehaviorSubject } from 'rxjs'; // Load the implementations that should be tested import { AppComponent } from './app.component'; @@ -127,4 +128,62 @@ describe('App component', () => { }); }); + + describe('removeSsrOverlayWhenStable', () => { + // The inline bootstrap script in src/index.html injects window.__dspaceRemoveSsrOverlay + // and AppComponent must call it exactly once when ApplicationRef.isStable first emits true. + let appRef: ApplicationRef; + let isStable$: BehaviorSubject; + let originalRaF: typeof window.requestAnimationFrame; + + beforeEach(() => { + appRef = TestBed.inject(ApplicationRef); + isStable$ = new BehaviorSubject(false); + // Patch isStable to our controllable subject for this test only + Object.defineProperty(appRef, 'isStable', { value: isStable$.asObservable() }); + + // Force rAF to a synchronous shim so we can flush() through the chain deterministically. + originalRaF = window.requestAnimationFrame; + (window as any).requestAnimationFrame = (cb: FrameRequestCallback) => { + cb(0); + return 0 as any; + }; + }); + + afterEach(() => { + (window as any).requestAnimationFrame = originalRaF; + delete (window as any).__dspaceRemoveSsrOverlay; + }); + + it('removes the overlay once isStable emits true', fakeAsync(() => { + const spy = jasmine.createSpy('__dspaceRemoveSsrOverlay'); + window.__dspaceRemoveSsrOverlay = spy; + + // Re-construct so the constructor-time subscription picks up our patched isStable + global. + const f = TestBed.createComponent(AppComponent); + f.detectChanges(); + + expect(spy).not.toHaveBeenCalled(); + + isStable$.next(true); + tick(50); // matches the 50ms pad after rAF in removeSsrOverlayWhenStable + flush(); + + expect(spy).toHaveBeenCalledTimes(1); + })); + + it('is a no-op when the global is not injected (e.g. CSR-only route, SSR skipped)', fakeAsync(() => { + // Global intentionally absent; constructor should not throw and should not break later. + delete (window as any).__dspaceRemoveSsrOverlay; + + const f = TestBed.createComponent(AppComponent); + expect(() => f.detectChanges()).not.toThrow(); + + isStable$.next(true); + tick(50); + flush(); + + expect(window.__dspaceRemoveSsrOverlay).toBeUndefined(); + })); + }); }); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b77d53c81d4..8200033fc20 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,11 +1,13 @@ -import { distinctUntilChanged, take, withLatestFrom, delay } from 'rxjs/operators'; +import { distinctUntilChanged, filter, first, take, withLatestFrom, delay } from 'rxjs/operators'; import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { AfterViewInit, + ApplicationRef, ChangeDetectionStrategy, Component, HostListener, Inject, + NgZone, OnInit, PLATFORM_ID, } from '@angular/core'; @@ -74,6 +76,8 @@ export class AppComponent implements OnInit, AfterViewInit { private cssService: CSSVariableService, private modalService: NgbModal, private modalConfig: NgbModalConfig, + private appRef: ApplicationRef, + private ngZone: NgZone, ) { this.notificationOptions = environment.notifications; @@ -82,6 +86,7 @@ export class AppComponent implements OnInit, AfterViewInit { if (isPlatformBrowser(this.platformId)) { this.trackIdleModal(); + this.removeSsrOverlayWhenStable(); } this.isThemeLoading$ = this.themeService.isThemeLoading$; @@ -89,6 +94,39 @@ export class AppComponent implements OnInit, AfterViewInit { this.storeCSSVariables(); } + /** + * Drops the SSR mask overlay installed by the inline bootstrap script in src/index.html as soon + * as Angular reaches its first stable state. The overlay is the only thing the user sees while + * Angular 15 rebuilds the SSR DOM; removing it too early would expose the rebuild flicker, too + * late would feel sluggish. We add a short safety pad to let the first paint settle, and there + * is also a 15s hard fallback inside the script itself in case isStable never fires. + */ + private removeSsrOverlayWhenStable(): void { + const w: Window | undefined = this._window?.nativeWindow; + if (!w || typeof w.__dspaceRemoveSsrOverlay !== 'function') { + return; + } + // run outside Angular so we don't keep changeDetection ticking on the overlay timer + this.ngZone.runOutsideAngular(() => { + this.appRef.isStable.pipe( + filter((stable: boolean) => stable), + first(), + ).subscribe(() => { + // one rAF + small pad to let the first stable paint commit before fading the overlay + const remove = () => { + if (typeof w.__dspaceRemoveSsrOverlay === 'function') { + w.__dspaceRemoveSsrOverlay(); + } + }; + if (typeof w.requestAnimationFrame === 'function') { + w.requestAnimationFrame(() => setTimeout(remove, 50)); + } else { + setTimeout(remove, 50); + } + }); + }); + } + ngOnInit() { /** Implement behavior for interface {@link ModalBeforeDismiss} */ this.modalConfig.beforeDismiss = async function () { diff --git a/src/index.html b/src/index.html index 74fc0c9861b..14a782b7f5e 100644 --- a/src/index.html +++ b/src/index.html @@ -7,12 +7,131 @@ DSpace + + diff --git a/src/themes/eager-themes.module.ts b/src/themes/eager-themes.module.ts index 4a46595f358..29d46032de8 100644 --- a/src/themes/eager-themes.module.ts +++ b/src/themes/eager-themes.module.ts @@ -1,6 +1,6 @@ import { NgModule } from '@angular/core'; import { EagerThemeModule as DSpaceEagerThemeModule } from './dspace/eager-theme.module'; -// import { EagerThemeModule as CustomEagerThemeModule } from './custom/eager-theme.module'; +import { EagerThemeModule as CustomEagerThemeModule } from './custom/eager-theme.module'; /** * This module bundles the eager theme modules for all available themes. @@ -8,11 +8,16 @@ import { EagerThemeModule as DSpaceEagerThemeModule } from './dspace/eager-theme * and entry components (to ensure their decorators get picked up). * * Themes that aren't in use should not be imported here so they don't take up unnecessary space in the main bundle. + * + * NOTE: CustomEagerThemeModule is included to prevent the home-page flicker that occurs when + * the active theme is `custom`. Without it, every themed wrapper (footer, header, root, ...) is + * lazy-loaded via webpack code-splitting on the browser, leaving visible gaps after the SSR DOM + * is torn down and before the CSR DOM is materialised. */ @NgModule({ imports: [ DSpaceEagerThemeModule, - // CustomEagerThemeModule, + CustomEagerThemeModule, ], }) export class EagerThemesModule { diff --git a/src/typings.d.ts b/src/typings.d.ts index c1c86511f88..f7397dc290e 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -86,3 +86,12 @@ declare module '*.scss' { const content: any; export default content; } + +/** + * Window global injected by the inline anti-flicker bootstrap script in `src/index.html`. + * Called once by `AppComponent.removeSsrOverlayWhenStable()` when `ApplicationRef.isStable` + * fires, to drop the SSR-mask overlay and let the freshly built CSR DOM become visible. + */ +interface Window { + __dspaceRemoveSsrOverlay?: (() => void) | null; +}