From f0e51d2806fd99cc618f28ee5322d913848d0305 Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Sun, 27 Sep 2026 14:21:40 +0200 Subject: [PATCH] perf(web): keep lazy-only services and SafePipe out of main.js (#1729) * perf(web): keep lazy-only services and SafePipe out of main.js The eager shell imported barrels that re-export Angular injectables and a pipe it never uses, and their static definitions keep those modules in main.js: PlaylistFileImportService came with PlaylistContextFacade, normalizeDateLocale with SafePipe, and the workspace-shell-util barrel with SettingsContextService, which #1714 grew with match counts. That growth put master 108 bytes over the renderer.initialBytes baseline #1712 had measured on a branch without #1714. Add file-level entries for the three modules and use them from the eager and settings code: renderer.initialBytes 1,626,127 -> 1,619,993 bytes. Co-Authored-By: Claude Opus 5.5 * chore(performance): lower the initial-bytes baseline to 1,619,993 bytes Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: 4gray Co-authored-by: Claude Opus 5.5 --- apps/web/src/app/app-date-locales.ts | 2 +- .../app/services/playlist-open-request.service.ts | 2 +- .../src/app/settings/settings-search.facade.ts | 2 +- .../settings/settings.component.search.spec.ts | 2 +- apps/web/src/app/settings/settings.component.ts | 2 +- docs/architecture/nx-workspace-boundaries.md | 15 +++++++++++---- docs/architecture/performance-journeys.md | 11 +++++++++++ .../epg-progress-panel.component.ts | 2 +- ...space-settings-context-panel.component.spec.ts | 2 +- .../workspace-settings-context-panel.component.ts | 6 ++---- libs/workspace/shell/util/src/index.ts | 1 - tools/performance/journey-baselines.json | 4 ++-- tsconfig.base.json | 9 +++++++++ 13 files changed, 42 insertions(+), 18 deletions(-) diff --git a/apps/web/src/app/app-date-locales.ts b/apps/web/src/app/app-date-locales.ts index df69e39cb..54c58d7df 100644 --- a/apps/web/src/app/app-date-locales.ts +++ b/apps/web/src/app/app-date-locales.ts @@ -2,7 +2,7 @@ import { registerLocaleData } from '@angular/common'; import localeEn from '@angular/common/locales/en'; import { inject, Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { normalizeDateLocale } from '@iptvnator/pipes'; +import { normalizeDateLocale } from '@iptvnator/pipes/date-format'; import { createDevLogger } from '@iptvnator/shared/interfaces'; type LocaleDataModule = { default: unknown }; diff --git a/apps/web/src/app/services/playlist-open-request.service.ts b/apps/web/src/app/services/playlist-open-request.service.ts index 06af1781b..6b1be9fcc 100644 --- a/apps/web/src/app/services/playlist-open-request.service.ts +++ b/apps/web/src/app/services/playlist-open-request.service.ts @@ -1,7 +1,7 @@ import { Injectable, inject } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslateService } from '@ngx-translate/core'; -import { PlaylistFileImportService } from '@iptvnator/playlist/shared/util'; +import { PlaylistFileImportService } from '@iptvnator/playlist/shared/util/playlist-file-import'; import type { ElectronBridgePlaylistOpenRequest } from '@iptvnator/shared/interfaces'; type PlaylistOpenRequestBridge = { diff --git a/apps/web/src/app/settings/settings-search.facade.ts b/apps/web/src/app/settings/settings-search.facade.ts index 5d8c2ce55..036c80b73 100644 --- a/apps/web/src/app/settings/settings-search.facade.ts +++ b/apps/web/src/app/settings/settings-search.facade.ts @@ -12,7 +12,7 @@ import { import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { SettingsContextService } from '@iptvnator/workspace/shell/util'; +import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context'; import { SettingsRevealRequest, SettingsSearchEntry, diff --git a/apps/web/src/app/settings/settings.component.search.spec.ts b/apps/web/src/app/settings/settings.component.search.spec.ts index 197ffd3e4..075357c25 100644 --- a/apps/web/src/app/settings/settings.component.search.spec.ts +++ b/apps/web/src/app/settings/settings.component.search.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { Router } from '@angular/router'; -import { SettingsContextService } from '@iptvnator/workspace/shell/util'; +import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context'; import { SETTINGS_SEARCH_ENTRIES, SettingsSearchEntry, diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index e66083e33..9e0c9c004 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -16,7 +16,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { ActivatedRoute, Router } from '@angular/router'; -import { SettingsContextService } from '@iptvnator/workspace/shell/util'; +import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { EpgSourceReconciliationError, diff --git a/docs/architecture/nx-workspace-boundaries.md b/docs/architecture/nx-workspace-boundaries.md index 13bc2fe5a..95c2ece24 100644 --- a/docs/architecture/nx-workspace-boundaries.md +++ b/docs/architecture/nx-workspace-boundaries.md @@ -235,18 +235,25 @@ and value-imports those two dependency-free modules directly. `@iptvnator/workspace/shell/util/settings-search` exists for the opposite 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. 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 whole library: esbuild keeps every Angular component module a barrel -re-exports, because their static definitions count as side effects. Importing +re-exports, because their static definitions count as side effects (so do an +`@Injectable` service's and a pipe's). Importing `WindowControlsComponent` and `DialogService` from `@iptvnator/ui/components` once put the channel lists, EPG views, `@angular/forms`, `date-fns` and the Stalker data layer into `main.js`. The root shell therefore uses file-level entries: `@iptvnator/ui/components/window-controls`, -`@iptvnator/ui/components/confirm-dialog` and -`@iptvnator/playlist/shared/ui/stalker-connection-editor` (like the existing -`@iptvnator/ui/epg/progress-panel`), and loads anything used only on demand +`@iptvnator/ui/components/confirm-dialog`, +`@iptvnator/playlist/shared/ui/stalker-connection-editor`, +`@iptvnator/playlist/shared/util/playlist-file-import` (the barrel would add +`PlaylistContextFacade`) and `@iptvnator/pipes/date-format` (the barrel would +add `SafePipe`; the eager EPG progress panel uses it too), like the existing +`@iptvnator/ui/epg/progress-panel`, and loads anything used only on demand through a local file it imports dynamically (the Stalker connection editor, the release-notes dialog, the external-player info dialog). A local file, not the library alias, is the dynamic-import target because diff --git a/docs/architecture/performance-journeys.md b/docs/architecture/performance-journeys.md index 05ae0e4e0..e15aea3bd 100644 --- a/docs/architecture/performance-journeys.md +++ b/docs/architecture/performance-journeys.md @@ -273,6 +273,17 @@ the first measurements was otherwise `package.json` text embedded in `main.js`, which moved with every script edit; #1692 fixed that by importing only the version.) +Two effects make the exact counter move for reasons outside a PR's own diff. +A baseline lowered on a branch that predates a concurrent `master` merge can +sit below what the merged code measures: #1712 lowered it on a branch without +#1714, so `master` measured 108 bytes over and every later PR failed the job +until a follow-up moved lazy-only modules out of `main.js`. Re-run the job on +an up-to-date branch before merging a baseline change. And the bundler's +chunk-level identifier renaming shifts when a module enters or leaves +`main.js`: moving one service out once renamed an imported identifier at 162 +call sites, eating about 320 of the bytes saved. Judge a small change by the +`--stats-json` input sizes, not only by the counter. + The job also refuses a weakened baselines file: `tools/performance/check-baseline-direction.mjs` compares `journey-baselines.json` with the revision the change is measured against diff --git a/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts b/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts index 9b633a84f..d0e47b8e9 100644 --- a/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts +++ b/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts @@ -20,7 +20,7 @@ import { EpgProgressService, } from '@iptvnator/epg/data-access'; import { ELECTRON_BRIDGE_SECURITY_ERROR_CODES } from '@iptvnator/shared/interfaces'; -import { normalizeDateLocale } from '@iptvnator/pipes'; +import { normalizeDateLocale } from '@iptvnator/pipes/date-format'; import { formatEpgImportDisplayUrl } from './epg-import-display-url'; interface EpgTrustConfirmDialogData { diff --git a/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.spec.ts b/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.spec.ts index ec6ee1e5d..effb04e5c 100644 --- a/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.spec.ts +++ b/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { provideRouter } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { of } from 'rxjs'; -import { SettingsContextService } from '@iptvnator/workspace/shell/util'; +import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context'; import { WorkspaceSettingsContextPanelComponent } from './workspace-settings-context-panel.component'; describe('WorkspaceSettingsContextPanelComponent', () => { diff --git a/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.ts b/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.ts index 92448563f..ced7aef8b 100644 --- a/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.ts +++ b/libs/workspace/shell/feature/src/lib/workspace-context-panel/workspace-settings-context-panel.component.ts @@ -3,10 +3,8 @@ import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { - WorkspaceShellContextDrawerService, - SettingsContextService, -} from '@iptvnator/workspace/shell/util'; +import { WorkspaceShellContextDrawerService } from '@iptvnator/workspace/shell/util'; +import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context'; @Component({ selector: 'app-workspace-settings-context-panel', diff --git a/libs/workspace/shell/util/src/index.ts b/libs/workspace/shell/util/src/index.ts index fee274af0..673c6f2c4 100644 --- a/libs/workspace/shell/util/src/index.ts +++ b/libs/workspace/shell/util/src/index.ts @@ -1,4 +1,3 @@ -export * from './lib/settings-context.service'; export * from './lib/workspace-shell-context-drawer.service'; export * from './lib/navigation/workspace-shell-route.utils'; export * from './lib/navigation/workspace-search-capability'; diff --git a/tools/performance/journey-baselines.json b/tools/performance/journey-baselines.json index 1e690f08f..e6fbd58da 100644 --- a/tools/performance/journey-baselines.json +++ b/tools/performance/journey-baselines.json @@ -3,10 +3,10 @@ "journeys": { "launch": { "renderer.initialBytes": { - "value": 1626019, + "value": 1619993, "unit": "bytes", "updatedAt": "2026-09-27", - "evidencePr": 1712, + "evidencePr": 1729, "measuredWith": "pnpm nx build web && pnpm run perf:initial-bytes" } } diff --git a/tsconfig.base.json b/tsconfig.base.json index 828f042d5..84bf54a8b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -43,6 +43,9 @@ "@iptvnator/playlist/shared/util": [ "libs/playlist/shared/util/src/index.ts" ], + "@iptvnator/playlist/shared/util/playlist-file-import": [ + "libs/playlist/shared/util/src/lib/playlist-file-import.service.ts" + ], "@iptvnator/ui/epg": ["libs/ui/epg/src/index.ts"], "@iptvnator/ui/epg/date": ["libs/ui/epg/src/lib/epg-date.ts"], "@iptvnator/ui/epg/progress-panel": [ @@ -79,10 +82,16 @@ "@iptvnator/workspace/shell/util": [ "libs/workspace/shell/util/src/index.ts" ], + "@iptvnator/workspace/shell/util/settings-context": [ + "libs/workspace/shell/util/src/lib/settings-context.service.ts" + ], "@iptvnator/workspace/shell/util/settings-search": [ "libs/workspace/shell/util/src/lib/settings-search/index.ts" ], "@iptvnator/pipes": ["libs/ui/pipes/src/index.ts"], + "@iptvnator/pipes/date-format": [ + "libs/ui/pipes/src/lib/date-format.util.ts" + ], "@iptvnator/ui/components": ["libs/ui/components/src/index.ts"], "@iptvnator/ui/components/confirm-dialog": [ "libs/ui/components/src/lib/confirm-dialog/dialog.service.ts"