From d6a91d79e9d1b398c3802598cf5928441a4ff347 Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 1 May 2026 23:10:10 +0200 Subject: [PATCH] perf(bundle): dynamic-import iptv-playlist-parser to drop 130KB validator from eager bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iptv-playlist-parser statically imports the entire validator library (~130KB across 113 files). Because PlaylistsService is eager (re-exported through the 'services' barrel that AppComponent imports), validator was landing in the cold-start preloaded chunk even though parse() only runs on user-triggered playlist imports (FILE/URL/TEXT add). Switch handlePlaylistParsing() to dynamic-import the parser. The two callers (playlist-backup.service and the parsePlaylist$ NgRx effect) needed minor adjustments — the effect now uses mergeMap(from(...)) to flatten the Promise back into the action stream. Cold-start preloaded chunk: 1511KB -> 1374KB (-9%). Validator now sits in a separate ~129KB chunk that loads only when the user adds a playlist. Co-Authored-By: Claude Opus 4.7 (1M context) Entire-Checkpoint: 3ec205264762 --- libs/m3u-state/src/lib/effects.ts | 16 ++++++++++------ libs/services/src/lib/playlist-backup.service.ts | 2 +- libs/services/src/lib/playlists.service.ts | 7 +++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/libs/m3u-state/src/lib/effects.ts b/libs/m3u-state/src/lib/effects.ts index 0e335ab41..bef31d0ff 100644 --- a/libs/m3u-state/src/lib/effects.ts +++ b/libs/m3u-state/src/lib/effects.ts @@ -11,7 +11,9 @@ import { EMPTY, filter, firstValueFrom, + from, map, + mergeMap, switchMap, tap, withLatestFrom, @@ -277,12 +279,14 @@ export class PlaylistEffects { parsePlaylist$ = createEffect(() => { return this.actions$.pipe( ofType(PlaylistActions.parsePlaylist), - map((action) => - this.playlistsService.handlePlaylistParsing( - action.uploadType, - action.playlist, - action.title, - action.path + mergeMap((action) => + from( + this.playlistsService.handlePlaylistParsing( + action.uploadType, + action.playlist, + action.title, + action.path + ) ) ), map((playlist) => diff --git a/libs/services/src/lib/playlist-backup.service.ts b/libs/services/src/lib/playlist-backup.service.ts index 521918106..b987254c1 100644 --- a/libs/services/src/lib/playlist-backup.service.ts +++ b/libs/services/src/lib/playlist-backup.service.ts @@ -610,7 +610,7 @@ export class PlaylistBackupService { playlistId: string, existing: Playlist | null ): Promise { - const parsedPlaylist = this.playlistsService.handlePlaylistParsing( + const parsedPlaylist = await this.playlistsService.handlePlaylistParsing( 'TEXT', entry.source.rawM3u, entry.title diff --git a/libs/services/src/lib/playlists.service.ts b/libs/services/src/lib/playlists.service.ts index 47d36afbf..92f4272e6 100644 --- a/libs/services/src/lib/playlists.service.ts +++ b/libs/services/src/lib/playlists.service.ts @@ -2,7 +2,6 @@ import { inject, Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { TranslateService } from '@ngx-translate/core'; -import { parse } from 'iptv-playlist-parser'; import { aggregateFavoriteChannels, createFavoritesPlaylist, @@ -760,13 +759,17 @@ export class PlaylistsService { ); } - handlePlaylistParsing( + async handlePlaylistParsing( uploadType: 'FILE' | 'URL' | 'TEXT', playlist: string, title: string, path?: string ) { try { + // Dynamic import keeps the ~130KB validator dep (transitively pulled + // by iptv-playlist-parser) out of the eager bundle. parse() only runs + // on user-triggered imports. + const { parse } = await import('iptv-playlist-parser'); const parsedPlaylist = parse(playlist); return createPlaylistObject( title,