From 9d02f90dfe21ea79980e96bfd0c57551f380bc6b Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:57:44 +0200 Subject: [PATCH] perf(web): keep backup/restore and portal helpers off the initial path (#1734) * perf(web): keep backup/restore and portal helpers off the initial path #1601 (parental lock) put about 35 KB onto the renderer's initial path by design (the lock service, lock store and enforcement gate the workspace resolver and the catalog data sources) and was merged with the ratchet red: renderer.initialBytes 1,655,428 against the 1,619,993 baseline. Offset it without touching the lock gate. Code splitting puts a module in the chunk shared by every entry that reaches it, so helpers only lazy routes use landed in initial chunks because eager files reach them through barrels: - PlaylistBackupService (only the lazy settings page) moves to @iptvnator/services/playlist-backup and out of the services barrel. - The eager Xtream data layer and root shell import the portal logger and DI tokens through @iptvnator/portal/shared/util/logger and /tokens instead of the barrel, whose navigation, keyboard-shortcut and download helpers (about 45 KB) belong to the lazy portal routes. renderer.initialBytes 1,655,428 -> 1,598,232 bytes (-57,196). Co-Authored-By: Claude Opus 5.5 * chore(performance): lower the initial-bytes baseline to 1,598,232 bytes Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: 4gray Co-authored-by: Claude Opus 5.5 --- apps/web/src/app/app.config.ts | 2 +- apps/web/src/app/services/electron.service.ts | 2 +- .../portal-playback-positions.service.ts | 2 +- apps/web/src/app/services/pwa.service.ts | 2 +- .../workspace-shell-actions.service.ts | 2 +- .../settings/settings-backup.facade.spec.ts | 2 +- .../app/settings/settings-backup.facade.ts | 4 ++-- .../test-stubs/settings-test-harness.stub.ts | 2 +- docs/architecture/nx-workspace-boundaries.md | 19 +++++++++++++++++-- docs/architecture/parental-lock.md | 6 ++++-- libs/portal/shared/util/src/tokens.ts | 7 +++++++ .../data-sources/pwa-xtream-data-source.ts | 2 +- .../src/lib/services/epg-queue.service.ts | 2 +- .../src/lib/services/xtream-api.service.ts | 2 +- .../services/xtream-xmltv-fallback.service.ts | 2 +- .../stores/features/with-content.feature.ts | 2 +- .../lib/stores/features/with-epg.feature.ts | 2 +- .../stores/features/with-player.feature.ts | 2 +- .../stores/features/with-portal.feature.ts | 2 +- .../stores/features/with-search.feature.ts | 2 +- .../src/lib/stores/xtream.store.ts | 2 +- .../src/lib/with-favorites.feature.ts | 2 +- .../data-access/src/lib/with-recent-items.ts | 2 +- libs/services/src/index.ts | 1 - .../navigation/workspace-shell-route.utils.ts | 2 +- tools/performance/journey-baselines.json | 4 ++-- tsconfig.base.json | 9 +++++++++ 27 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 libs/portal/shared/util/src/tokens.ts diff --git a/apps/web/src/app/app.config.ts b/apps/web/src/app/app.config.ts index 70001db7b..1fc0d8950 100644 --- a/apps/web/src/app/app.config.ts +++ b/apps/web/src/app/app.config.ts @@ -30,7 +30,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { PORTAL_EXTERNAL_PLAYBACK, PORTAL_PLAYER, -} from '@iptvnator/portal/shared/util'; +} from '@iptvnator/portal/shared/util/tokens'; import { STALKER_PLAYLIST_CONNECTION_EDITOR } from '@iptvnator/playlist/shared/ui/stalker-connection-editor'; import { provideXtreamDataSource } from '@iptvnator/portal/xtream/data-access'; import { diff --git a/apps/web/src/app/services/electron.service.ts b/apps/web/src/app/services/electron.service.ts index dd5be4f0b..ec2c7e85f 100644 --- a/apps/web/src/app/services/electron.service.ts +++ b/apps/web/src/app/services/electron.service.ts @@ -36,7 +36,7 @@ import { createLogger, createPortalDebugRequestContext, logPortalDebugEvent, -} from '@iptvnator/portal/shared/util'; +} from '@iptvnator/portal/shared/util/logger'; interface PlayerLaunchPayload { readonly headers?: Record; diff --git a/apps/web/src/app/services/portal-playback-positions.service.ts b/apps/web/src/app/services/portal-playback-positions.service.ts index bcc29b73b..8ffe27383 100644 --- a/apps/web/src/app/services/portal-playback-positions.service.ts +++ b/apps/web/src/app/services/portal-playback-positions.service.ts @@ -2,7 +2,7 @@ import { Injectable, inject } from '@angular/core'; import { PORTAL_PLAYBACK_POSITIONS, PortalPlaybackPositions, -} from '@iptvnator/portal/shared/util'; +} from '@iptvnator/portal/shared/util/tokens'; import { PlaybackPositionRuntimeBridgeService, RuntimeCapabilitiesService, diff --git a/apps/web/src/app/services/pwa.service.ts b/apps/web/src/app/services/pwa.service.ts index 497e07e65..2dc932a75 100644 --- a/apps/web/src/app/services/pwa.service.ts +++ b/apps/web/src/app/services/pwa.service.ts @@ -34,7 +34,7 @@ import { logPortalDebugEvent, logPortalDebugRequest, createLogger, -} from '@iptvnator/portal/shared/util'; +} from '@iptvnator/portal/shared/util/logger'; import { getRuntimeBackendUrl } from './runtime-config'; /** diff --git a/apps/web/src/app/services/workspace-shell-actions.service.ts b/apps/web/src/app/services/workspace-shell-actions.service.ts index a110883a6..3e7dc0921 100644 --- a/apps/web/src/app/services/workspace-shell-actions.service.ts +++ b/apps/web/src/app/services/workspace-shell-actions.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable, Provider } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; -import { PORTAL_SHELL_ACTIONS } from '@iptvnator/portal/shared/util'; +import { PORTAL_SHELL_ACTIONS } from '@iptvnator/portal/shared/util/tokens'; import { WORKSPACE_SHELL_ACTIONS, WorkspaceAccountInfoData, diff --git a/apps/web/src/app/settings/settings-backup.facade.spec.ts b/apps/web/src/app/settings/settings-backup.facade.spec.ts index a14201475..0fcce600a 100644 --- a/apps/web/src/app/settings/settings-backup.facade.spec.ts +++ b/apps/web/src/app/settings/settings-backup.facade.spec.ts @@ -5,7 +5,7 @@ import { selectIsEpgAvailable, } from '@iptvnator/m3u-state'; import { XtreamStore } from '@iptvnator/portal/xtream/data-access'; -import { PlaylistBackupService } from '@iptvnator/services'; +import { PlaylistBackupService } from '@iptvnator/services/playlist-backup'; import { provideMockStore } from '@ngrx/store/testing'; import { TranslateModule } from '@ngx-translate/core'; import { MockProvider } from 'ng-mocks'; diff --git a/apps/web/src/app/settings/settings-backup.facade.ts b/apps/web/src/app/settings/settings-backup.facade.ts index bebfc6004..91ed0d714 100644 --- a/apps/web/src/app/settings/settings-backup.facade.ts +++ b/apps/web/src/app/settings/settings-backup.facade.ts @@ -3,11 +3,11 @@ import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { PlaylistActions } from '@iptvnator/m3u-state'; import { XtreamStore } from '@iptvnator/portal/xtream/data-access'; +import { RuntimeCapabilitiesService } from '@iptvnator/services'; import { PlaylistBackupImportSummary, PlaylistBackupService, - RuntimeCapabilitiesService, -} from '@iptvnator/services'; +} from '@iptvnator/services/playlist-backup'; import { SettingsSnackbarService } from './settings-snackbar.service'; @Injectable() diff --git a/apps/web/src/app/settings/test-stubs/settings-test-harness.stub.ts b/apps/web/src/app/settings/test-stubs/settings-test-harness.stub.ts index 7c7925187..9d1af7ba8 100644 --- a/apps/web/src/app/settings/test-stubs/settings-test-harness.stub.ts +++ b/apps/web/src/app/settings/test-stubs/settings-test-harness.stub.ts @@ -34,9 +34,9 @@ import { import { DatabaseService, DataService, - PlaylistBackupService, PlaylistsService, } from '@iptvnator/services'; +import { PlaylistBackupService } from '@iptvnator/services/playlist-backup'; import { ELECTRON_BRIDGE_APP_UPDATE_STATUSES, ElectronBridgeAppUpdateStatus, diff --git a/docs/architecture/nx-workspace-boundaries.md b/docs/architecture/nx-workspace-boundaries.md index 05b48e61e..86daacfcb 100644 --- a/docs/architecture/nx-workspace-boundaries.md +++ b/docs/architecture/nx-workspace-boundaries.md @@ -237,7 +237,10 @@ reason: the main `workspace-shell-util` barrel is imported eagerly, and the settings search index must stay in the lazy settings and shell chunks. `@iptvnator/workspace/shell/util/settings-context` keeps `SettingsContextService`, which only the lazy settings page and settings -context panel use, out of that barrel for the same reason. +context panel use, out of that barrel for the same reason, and +`@iptvnator/services/playlist-backup` keeps `PlaylistBackupService` (about +22 KB, used only by the lazy settings page) out of the `@iptvnator/services` +barrel. The web app's root shell (`app.component.ts`, `app.config.ts` and the services they construct) is on the renderer's initial path, where a barrel costs its @@ -260,7 +263,19 @@ PIN dialog and the Stalker step of the parental-lock enforcement, which runs only while a Stalker route is open). A local file, not the library alias, is the dynamic-import target because `@nx/enforce-module-boundaries` forbids static imports of a library the same -project also loads dynamically. `renderer.initialBytes` in +project also loads dynamically. + +A barrel the initial path imports also costs code it never runs itself: +code splitting places a module in the chunk shared by every entry point +that reaches it, so a helper that only lazy routes use, but that an eager +file can reach through a barrel's re-export, lands in an initial chunk even +though tree shaking would drop it from a single bundle. The eager Xtream +data layer and root shell therefore import `@iptvnator/portal/shared/util/logger` +and `@iptvnator/portal/shared/util/tokens` (the portal DI tokens), not the +`@iptvnator/portal/shared/util` barrel, whose navigation, keyboard-shortcut +and download helpers (about 45 KB) belong to the lazy portal routes. A +type-only import of a barrel uses `import type`, so it can never keep the +barrel reachable. `renderer.initialBytes` in [performance journeys](performance-journeys.md) guards the result; to see why a module is eager, build with `pnpm nx build web --stats-json` and follow the static imports in `dist/apps/web/stats.json` from `apps/web/src/main.ts`. diff --git a/docs/architecture/parental-lock.md b/docs/architecture/parental-lock.md index e54d6fcad..331645296 100644 --- a/docs/architecture/parental-lock.md +++ b/docs/architecture/parental-lock.md @@ -419,8 +419,10 @@ opens, so a relock there runs the step synchronously; if it is not loaded yet (still fetching, or it cannot load — a stale PWA page after a deployment) the step fails closed at once by navigating to `/workspace/sources`: leaving the Stalker route clears its selection and -stops its playback, and the Xtream step still runs. The feature costs about 30 KB of -`renderer.initialBytes`. +stops its playback, and the Xtream step still runs. The feature costs about 35 KB of +`renderer.initialBytes`; the baseline was not raised for it, the growth was +offset by moving lazy-only barrel re-exports (backup/restore and the portal +helpers) off the initial path. ## Lock store lifetime diff --git a/libs/portal/shared/util/src/tokens.ts b/libs/portal/shared/util/src/tokens.ts new file mode 100644 index 000000000..3595e3a5f --- /dev/null +++ b/libs/portal/shared/util/src/tokens.ts @@ -0,0 +1,7 @@ +// Dependency-free entry for the DI tokens the web app's root shell and the +// eager Xtream data layer provide or inject. The main barrel would put every +// portal helper that lazy routes use onto the renderer's initial path. +export * from './lib/portal-external-playback'; +export * from './lib/portal-playback-positions'; +export * from './lib/portal-player'; +export * from './lib/portal-shell-actions'; diff --git a/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts b/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts index 252836900..1d3bf56e2 100644 --- a/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts +++ b/libs/portal/xtream/data-access/src/lib/data-sources/pwa-xtream-data-source.ts @@ -11,7 +11,7 @@ import { XtreamSerieItem, XtreamVodStream, } from '@iptvnator/shared/interfaces'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { CategoryType, StreamType, diff --git a/libs/portal/xtream/data-access/src/lib/services/epg-queue.service.ts b/libs/portal/xtream/data-access/src/lib/services/epg-queue.service.ts index eccc81f8b..11cb26476 100644 --- a/libs/portal/xtream/data-access/src/lib/services/epg-queue.service.ts +++ b/libs/portal/xtream/data-access/src/lib/services/epg-queue.service.ts @@ -8,7 +8,7 @@ import { import { EpgSourceSettingsService, SettingsStore } from '@iptvnator/services'; import { XtreamApiService, XtreamCredentials } from './xtream-api.service'; import { XtreamXmltvFallbackService } from './xtream-xmltv-fallback.service'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; interface CacheEntry { data: EpgItem[]; diff --git a/libs/portal/xtream/data-access/src/lib/services/xtream-api.service.ts b/libs/portal/xtream/data-access/src/lib/services/xtream-api.service.ts index 55ea8fa58..3a05889a5 100644 --- a/libs/portal/xtream/data-access/src/lib/services/xtream-api.service.ts +++ b/libs/portal/xtream/data-access/src/lib/services/xtream-api.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { DataService } from '@iptvnator/services'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { EpgItem, XtreamCategory, diff --git a/libs/portal/xtream/data-access/src/lib/services/xtream-xmltv-fallback.service.ts b/libs/portal/xtream/data-access/src/lib/services/xtream-xmltv-fallback.service.ts index de0b42676..9f5d115cd 100644 --- a/libs/portal/xtream/data-access/src/lib/services/xtream-xmltv-fallback.service.ts +++ b/libs/portal/xtream/data-access/src/lib/services/xtream-xmltv-fallback.service.ts @@ -4,7 +4,7 @@ import { EpgProgram, epgProviderClockMs, } from '@iptvnator/shared/interfaces'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { EpgSourceSettingsService, SettingsStore } from '@iptvnator/services'; type ElectronEpgBridge = { diff --git a/libs/portal/xtream/data-access/src/lib/stores/features/with-content.feature.ts b/libs/portal/xtream/data-access/src/lib/stores/features/with-content.feature.ts index 9ed5a7bb0..0b8c0874a 100644 --- a/libs/portal/xtream/data-access/src/lib/stores/features/with-content.feature.ts +++ b/libs/portal/xtream/data-access/src/lib/stores/features/with-content.feature.ts @@ -16,7 +16,7 @@ import { measureRendererPerformancePhase, RENDERER_PERFORMANCE_PHASE, } from '@iptvnator/shared/logging'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { DataService, DatabaseService, diff --git a/libs/portal/xtream/data-access/src/lib/stores/features/with-epg.feature.ts b/libs/portal/xtream/data-access/src/lib/stores/features/with-epg.feature.ts index 024f7b32e..da0dc7f3c 100644 --- a/libs/portal/xtream/data-access/src/lib/stores/features/with-epg.feature.ts +++ b/libs/portal/xtream/data-access/src/lib/stores/features/with-epg.feature.ts @@ -22,7 +22,7 @@ import { XtreamCredentials, } from '../../services/xtream-api.service'; import { XtreamXmltvFallbackService } from '../../services/xtream-xmltv-fallback.service'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; /** * EPG state for managing Electronic Program Guide data diff --git a/libs/portal/xtream/data-access/src/lib/stores/features/with-player.feature.ts b/libs/portal/xtream/data-access/src/lib/stores/features/with-player.feature.ts index dd12bdebc..2b85a6807 100644 --- a/libs/portal/xtream/data-access/src/lib/stores/features/with-player.feature.ts +++ b/libs/portal/xtream/data-access/src/lib/stores/features/with-player.feature.ts @@ -5,7 +5,7 @@ import { withMethods, withState, } from '@ngrx/signals'; -import { PORTAL_PLAYER } from '@iptvnator/portal/shared/util'; +import { PORTAL_PLAYER } from '@iptvnator/portal/shared/util/tokens'; import { XtreamSerieEpisode, XtreamVodDetails, diff --git a/libs/portal/xtream/data-access/src/lib/stores/features/with-portal.feature.ts b/libs/portal/xtream/data-access/src/lib/stores/features/with-portal.feature.ts index 3f2737c79..69532df62 100644 --- a/libs/portal/xtream/data-access/src/lib/stores/features/with-portal.feature.ts +++ b/libs/portal/xtream/data-access/src/lib/stores/features/with-portal.feature.ts @@ -14,7 +14,7 @@ import { XtreamCredentials, } from '../../services/xtream-api.service'; import { PortalStatusType } from '../../xtream-state'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { resolveXtreamPortalStatus, resolveXtreamServerTimezone, diff --git a/libs/portal/xtream/data-access/src/lib/stores/features/with-search.feature.ts b/libs/portal/xtream/data-access/src/lib/stores/features/with-search.feature.ts index ed1a038b9..c2ebcebbc 100644 --- a/libs/portal/xtream/data-access/src/lib/stores/features/with-search.feature.ts +++ b/libs/portal/xtream/data-access/src/lib/stores/features/with-search.feature.ts @@ -14,7 +14,7 @@ import { XTREAM_DATA_SOURCE, XtreamContentItem, } from '../../data-sources/xtream-data-source.interface'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { XtreamSearchResultItem } from '../../xtream-state'; /** diff --git a/libs/portal/xtream/data-access/src/lib/stores/xtream.store.ts b/libs/portal/xtream/data-access/src/lib/stores/xtream.store.ts index fe8ca8df9..1c21edae4 100644 --- a/libs/portal/xtream/data-access/src/lib/stores/xtream.store.ts +++ b/libs/portal/xtream/data-access/src/lib/stores/xtream.store.ts @@ -12,7 +12,7 @@ import { XTREAM_DATA_SOURCE } from '../data-sources/xtream-data-source.interface import { XtreamApiService } from '../services/xtream-api.service'; import { TmdbEnrichmentService } from '@iptvnator/services'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { withContent, withEpg, diff --git a/libs/portal/xtream/data-access/src/lib/with-favorites.feature.ts b/libs/portal/xtream/data-access/src/lib/with-favorites.feature.ts index 49c3168f9..4bcadd4fe 100644 --- a/libs/portal/xtream/data-access/src/lib/with-favorites.feature.ts +++ b/libs/portal/xtream/data-access/src/lib/with-favorites.feature.ts @@ -5,7 +5,7 @@ import { withMethods, withState, } from '@ngrx/signals'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { XTREAM_DATA_SOURCE } from './data-sources/xtream-data-source.interface'; export const withFavorites = function () { diff --git a/libs/portal/xtream/data-access/src/lib/with-recent-items.ts b/libs/portal/xtream/data-access/src/lib/with-recent-items.ts index c420bdce1..7bfa11129 100644 --- a/libs/portal/xtream/data-access/src/lib/with-recent-items.ts +++ b/libs/portal/xtream/data-access/src/lib/with-recent-items.ts @@ -15,7 +15,7 @@ import { Playlist, PortalRecentItem, } from '@iptvnator/shared/interfaces'; -import { createLogger } from '@iptvnator/portal/shared/util'; +import { createLogger } from '@iptvnator/portal/shared/util/logger'; import { XTREAM_DATA_SOURCE } from './data-sources/xtream-data-source.interface'; export interface RecentlyViewedItem extends PortalRecentItem { diff --git a/libs/services/src/index.ts b/libs/services/src/index.ts index 1e3e0d47a..7eaa9165c 100644 --- a/libs/services/src/index.ts +++ b/libs/services/src/index.ts @@ -10,7 +10,6 @@ export * from './lib/playback-position.service'; export * from './lib/playlist-delete-cleanup.token'; export * from './lib/playlist-delete-action.service'; export * from './lib/parental-lock'; -export * from './lib/playlist-backup.service'; export * from './lib/playlist-cross-context-lock'; export * from './lib/playlist-refresh.service'; export * from './lib/playlists.service'; diff --git a/libs/workspace/shell/util/src/lib/navigation/workspace-shell-route.utils.ts b/libs/workspace/shell/util/src/lib/navigation/workspace-shell-route.utils.ts index 3515d722d..d3e923c23 100644 --- a/libs/workspace/shell/util/src/lib/navigation/workspace-shell-route.utils.ts +++ b/libs/workspace/shell/util/src/lib/navigation/workspace-shell-route.utils.ts @@ -1,4 +1,4 @@ -import { +import type { PortalProvider, PortalRailSection, } from '@iptvnator/portal/shared/util'; diff --git a/tools/performance/journey-baselines.json b/tools/performance/journey-baselines.json index e6fbd58da..f6198f06f 100644 --- a/tools/performance/journey-baselines.json +++ b/tools/performance/journey-baselines.json @@ -3,10 +3,10 @@ "journeys": { "launch": { "renderer.initialBytes": { - "value": 1619993, + "value": 1598232, "unit": "bytes", "updatedAt": "2026-09-27", - "evidencePr": 1729, + "evidencePr": 1734, "measuredWith": "pnpm nx build web && pnpm run perf:initial-bytes" } } diff --git a/tsconfig.base.json b/tsconfig.base.json index c0213ec59..7bcd05ff4 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,6 +32,12 @@ "@iptvnator/portal/shared/util": [ "libs/portal/shared/util/src/index.ts" ], + "@iptvnator/portal/shared/util/logger": [ + "libs/portal/shared/util/src/lib/logger.ts" + ], + "@iptvnator/portal/shared/util/tokens": [ + "libs/portal/shared/util/src/tokens.ts" + ], "@iptvnator/epg/data-access": ["libs/epg/data-access/src/index.ts"], "@iptvnator/playback/util": ["libs/playback/util/src/index.ts"], "@iptvnator/playlist/shared/ui": [ @@ -112,6 +118,9 @@ "libs/shared/host-health/src/index.ts" ], "@iptvnator/services": ["libs/services/src/index.ts"], + "@iptvnator/services/playlist-backup": [ + "libs/services/src/lib/playlist-backup.service.ts" + ], "@iptvnator/shared/interfaces": [ "libs/shared/interfaces/src/index.ts" ],