mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
perf(web): load Angular date locales lazily per language (#1695)
Plan thread C1, journey **J1 `launch`**, counter **`renderer.initialBytes`**. Stacked on #1694 → #1693 → #1692; merge in order. `apps/web/src/app/app-date-locales.ts` imported the locale data of all 18 supported languages eagerly, so every user shipped and parsed all of it at startup. Each locale is now a dynamic import keyed by the Angular locale id that `normalizeDateLocale()` derives from the app language (`by` → `be`, `ary` → `ar-MA`, `zhtw` → `zh-Hant`); English needs no data. Ordering is preserved so no template renders a locale whose data has not arrived (Angular throws in that case): - `main.ts` awaits the initial language's data (from `getInitialLanguage()`) before `bootstrapApplication`. - Both `TranslateService.use()` call sites, `AppComponent.initSettings()` and `SettingsFormFacade.applySavedSettings()`, register the data first through the new `AppDateLocaleService`. - A failed load never leaves the locale without data: English formatting is registered under the requested id (eager 1.1 KB `@angular/common/locales/en`), so `DatePipe` renders instead of throwing; the locale is not marked registered, so the next call retries and a success replaces the fallback (review follow-up). - `AppDateLocaleService.use()` orders switches by request, not by completion: a switch whose data arrives after a newer request is dropped, so the language chosen last wins (review follow-up). No kill switch: behavior is identical once the locale resolves, and the only new failure mode (a same-origin chunk failing to load) is shared with every lazy route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
d3b6e548cc
commit
512e9787a8
10 files changed
+284
-59
No files matched your search
@@ -0,0 +1,8 @@
|
||||
---
|
||||
type: perf
|
||||
area: web
|
||||
---
|
||||
|
||||
The app now loads date and time formatting data only for the language you
|
||||
use instead of shipping all 18 languages in the startup bundle, so it has
|
||||
less to download and parse on every launch; English needs none at all.
|
||||
@@ -0,0 +1,123 @@
|
||||
import { formatDate } from '@angular/common';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { normalizeDateLocale } from '@iptvnator/pipes';
|
||||
import { Language } from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
APP_DATE_LOCALE_LOADERS,
|
||||
AppDateLocaleService,
|
||||
registerAppDateLocale,
|
||||
} from './app-date-locales';
|
||||
|
||||
const sampleDate = new Date(2024, 0, 15, 13, 5);
|
||||
|
||||
describe('registerAppDateLocale', () => {
|
||||
it('resolves for English without loading anything', async () => {
|
||||
const loader = jest.fn(() => import('@angular/common/locales/de'));
|
||||
|
||||
await registerAppDateLocale('en', { en: loader });
|
||||
await registerAppDateLocale('', { en: loader });
|
||||
await registerAppDateLocale(undefined, { en: loader });
|
||||
|
||||
expect(loader).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('loads the locale once and shares the in-flight import', async () => {
|
||||
const loader = jest.fn(() => import('@angular/common/locales/fr'));
|
||||
|
||||
await Promise.all([
|
||||
registerAppDateLocale('fr', { fr: loader }),
|
||||
registerAppDateLocale('fr', { fr: loader }),
|
||||
]);
|
||||
await registerAppDateLocale('fr', { fr: loader });
|
||||
|
||||
expect(loader).toHaveBeenCalledTimes(1);
|
||||
expect(formatDate(sampleDate, 'MMMM', 'fr')).toBe('janvier');
|
||||
});
|
||||
|
||||
it('maps app language aliases to Angular locale ids before loading', async () => {
|
||||
const loader = jest.fn(() => import('@angular/common/locales/be'));
|
||||
|
||||
await registerAppDateLocale('by', { be: loader });
|
||||
|
||||
expect(loader).toHaveBeenCalledTimes(1);
|
||||
expect(formatDate(sampleDate, 'MMMM', 'be')).toBe('студзеня');
|
||||
});
|
||||
|
||||
it('falls back to English formatting on a failed import and retries on the next call', async () => {
|
||||
const failing = jest.fn(() => Promise.reject(new Error('offline')));
|
||||
const working = jest.fn(() => import('@angular/common/locales/pl'));
|
||||
|
||||
await expect(
|
||||
registerAppDateLocale('pl', { pl: failing })
|
||||
).resolves.toBeUndefined();
|
||||
expect(formatDate(sampleDate, 'MMMM', 'pl')).toBe('January');
|
||||
|
||||
await registerAppDateLocale('pl', { pl: working });
|
||||
|
||||
expect(working).toHaveBeenCalledTimes(1);
|
||||
expect(formatDate(sampleDate, 'MMMM', 'pl')).toBe('stycznia');
|
||||
});
|
||||
|
||||
it('resolves for a language without bundled data instead of rejecting', async () => {
|
||||
await expect(registerAppDateLocale('xx', {})).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('bundles a loader for every supported language except English', () => {
|
||||
const expectedLocales = Object.values(Language)
|
||||
.filter((language) => language !== Language.ENGLISH)
|
||||
.map((language) => normalizeDateLocale(language))
|
||||
.sort();
|
||||
|
||||
expect(Object.keys(APP_DATE_LOCALE_LOADERS).sort()).toEqual(
|
||||
expectedLocales
|
||||
);
|
||||
});
|
||||
|
||||
it('registers working data for every supported language', async () => {
|
||||
for (const language of Object.values(Language)) {
|
||||
await registerAppDateLocale(language);
|
||||
const locale = normalizeDateLocale(language);
|
||||
expect(formatDate(sampleDate, 'MMMM', locale)).not.toBe('');
|
||||
}
|
||||
expect(formatDate(sampleDate, 'MMMM', 'ar-MA')).toBe('يناير');
|
||||
expect(formatDate(sampleDate, 'MMMM', 'zh-Hant')).toBe('1月');
|
||||
});
|
||||
});
|
||||
|
||||
describe('AppDateLocaleService', () => {
|
||||
let translate: { use: jest.Mock };
|
||||
let service: AppDateLocaleService;
|
||||
|
||||
beforeEach(() => {
|
||||
translate = { use: jest.fn() };
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{ provide: TranslateService, useValue: translate }],
|
||||
});
|
||||
service = TestBed.inject(AppDateLocaleService);
|
||||
});
|
||||
|
||||
it('delegates register to registerAppDateLocale', async () => {
|
||||
await expect(
|
||||
service.register(Language.GERMAN)
|
||||
).resolves.toBeUndefined();
|
||||
expect(formatDate(sampleDate, 'MMMM', 'de')).toBe('Januar');
|
||||
});
|
||||
|
||||
it('registers the locale data before switching the language', async () => {
|
||||
await service.use(Language.HUNGARIAN);
|
||||
|
||||
expect(translate.use).toHaveBeenCalledWith(Language.HUNGARIAN);
|
||||
expect(formatDate(sampleDate, 'MMMM', 'hu')).toBe('január');
|
||||
});
|
||||
|
||||
it('applies only the language requested last when switches overlap', async () => {
|
||||
await Promise.all([
|
||||
service.use(Language.ITALIAN),
|
||||
service.use(Language.TURKISH),
|
||||
]);
|
||||
|
||||
expect(translate.use).toHaveBeenCalledTimes(1);
|
||||
expect(translate.use).toHaveBeenCalledWith(Language.TURKISH);
|
||||
});
|
||||
});
|
||||
@@ -1,48 +1,123 @@
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import localeAr from '@angular/common/locales/ar';
|
||||
import localeArMa from '@angular/common/locales/ar-MA';
|
||||
import localeBe from '@angular/common/locales/be';
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import localeEl from '@angular/common/locales/el';
|
||||
import localeEs from '@angular/common/locales/es';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import localeHu from '@angular/common/locales/hu';
|
||||
import localeIt from '@angular/common/locales/it';
|
||||
import localeJa from '@angular/common/locales/ja';
|
||||
import localeKo from '@angular/common/locales/ko';
|
||||
import localeNl from '@angular/common/locales/nl';
|
||||
import localePl from '@angular/common/locales/pl';
|
||||
import localePt from '@angular/common/locales/pt';
|
||||
import localeRu from '@angular/common/locales/ru';
|
||||
import localeTr from '@angular/common/locales/tr';
|
||||
import localeZh from '@angular/common/locales/zh';
|
||||
import localeZhHant from '@angular/common/locales/zh-Hant';
|
||||
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 { createDevLogger } from '@iptvnator/shared/interfaces';
|
||||
|
||||
let localesRegistered = false;
|
||||
type LocaleDataModule = { default: unknown };
|
||||
export type AppDateLocaleLoader = () => Promise<LocaleDataModule>;
|
||||
|
||||
export function registerAppDateLocales(): void {
|
||||
if (localesRegistered) {
|
||||
return;
|
||||
const debug = createDevLogger('AppDateLocales');
|
||||
|
||||
/**
|
||||
* Angular ships only English locale data; every other UI language needs its
|
||||
* data registered before `DatePipe` can format with it. Each entry is a
|
||||
* separate lazy chunk, keyed by the Angular locale id that
|
||||
* `normalizeDateLocale()` produces for an app language (`by` -> `be`,
|
||||
* `ary` -> `ar-MA`, `zhtw` -> `zh-Hant`). Importing all of them eagerly put
|
||||
* the data for 18 languages into the initial bundle of every user.
|
||||
*/
|
||||
export const APP_DATE_LOCALE_LOADERS: Readonly<
|
||||
Record<string, AppDateLocaleLoader>
|
||||
> = {
|
||||
ar: () => import('@angular/common/locales/ar'),
|
||||
'ar-MA': () => import('@angular/common/locales/ar-MA'),
|
||||
be: () => import('@angular/common/locales/be'),
|
||||
de: () => import('@angular/common/locales/de'),
|
||||
el: () => import('@angular/common/locales/el'),
|
||||
es: () => import('@angular/common/locales/es'),
|
||||
fr: () => import('@angular/common/locales/fr'),
|
||||
hu: () => import('@angular/common/locales/hu'),
|
||||
it: () => import('@angular/common/locales/it'),
|
||||
ja: () => import('@angular/common/locales/ja'),
|
||||
ko: () => import('@angular/common/locales/ko'),
|
||||
nl: () => import('@angular/common/locales/nl'),
|
||||
pl: () => import('@angular/common/locales/pl'),
|
||||
pt: () => import('@angular/common/locales/pt'),
|
||||
ru: () => import('@angular/common/locales/ru'),
|
||||
tr: () => import('@angular/common/locales/tr'),
|
||||
zh: () => import('@angular/common/locales/zh'),
|
||||
'zh-Hant': () => import('@angular/common/locales/zh-Hant'),
|
||||
};
|
||||
|
||||
const registeredLocales = new Set<string>(['en']);
|
||||
const pendingLocales = new Map<string, Promise<void>>();
|
||||
|
||||
/**
|
||||
* Registers the Angular locale data for an app language, loading it on first
|
||||
* use. Resolves once `DatePipe` can format with that locale, so call it before
|
||||
* `TranslateService.use()`: the language switch re-renders every date with the
|
||||
* new locale, and a locale whose data has not arrived throws. English resolves
|
||||
* immediately, as does a locale that is already registered or in flight.
|
||||
*
|
||||
* A failed load resolves rather than rejects, but never leaves the locale
|
||||
* without data: English formatting is registered under the requested id so
|
||||
* `DatePipe` keeps rendering instead of throwing in every template that passes
|
||||
* the locale. The locale is not marked registered, so the next call retries
|
||||
* the import and a success replaces the fallback.
|
||||
*/
|
||||
export function registerAppDateLocale(
|
||||
language: string | null | undefined,
|
||||
loaders: Readonly<
|
||||
Record<string, AppDateLocaleLoader>
|
||||
> = APP_DATE_LOCALE_LOADERS
|
||||
): Promise<void> {
|
||||
const locale = normalizeDateLocale(language);
|
||||
if (registeredLocales.has(locale)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const inFlight = pendingLocales.get(locale);
|
||||
if (inFlight) {
|
||||
return inFlight;
|
||||
}
|
||||
const loader = loaders[locale];
|
||||
if (!loader) {
|
||||
debug('No bundled Angular locale data for', locale);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
registerLocaleData(localeAr, 'ar');
|
||||
registerLocaleData(localeArMa, 'ar-MA');
|
||||
registerLocaleData(localeBe, 'be');
|
||||
registerLocaleData(localeDe, 'de');
|
||||
registerLocaleData(localeEl, 'el');
|
||||
registerLocaleData(localeEs, 'es');
|
||||
registerLocaleData(localeFr, 'fr');
|
||||
registerLocaleData(localeHu, 'hu');
|
||||
registerLocaleData(localeIt, 'it');
|
||||
registerLocaleData(localeJa, 'ja');
|
||||
registerLocaleData(localeKo, 'ko');
|
||||
registerLocaleData(localeNl, 'nl');
|
||||
registerLocaleData(localePl, 'pl');
|
||||
registerLocaleData(localePt, 'pt');
|
||||
registerLocaleData(localeRu, 'ru');
|
||||
registerLocaleData(localeTr, 'tr');
|
||||
registerLocaleData(localeZh, 'zh');
|
||||
registerLocaleData(localeZhHant, 'zh-Hant');
|
||||
|
||||
localesRegistered = true;
|
||||
const load = loader()
|
||||
.then((module) => {
|
||||
registerLocaleData(module.default, locale);
|
||||
registeredLocales.add(locale);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
debug(
|
||||
'Loading Angular locale data failed; using English formatting for',
|
||||
locale,
|
||||
error
|
||||
);
|
||||
registerLocaleData(localeEn, locale);
|
||||
})
|
||||
.finally(() => {
|
||||
pendingLocales.delete(locale);
|
||||
});
|
||||
pendingLocales.set(locale, load);
|
||||
return load;
|
||||
}
|
||||
|
||||
/** Gates UI language switches on the locale data they render with. */
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AppDateLocaleService {
|
||||
private readonly translate = inject(TranslateService);
|
||||
private latestRequest = 0;
|
||||
|
||||
register(language: string | null | undefined): Promise<void> {
|
||||
return registerAppDateLocale(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the locale data, then switches the UI language. Switches are
|
||||
* ordered by request, not by completion: a switch whose data arrives
|
||||
* after a newer request was made is dropped, so the language chosen last
|
||||
* is the one that ends up active.
|
||||
*/
|
||||
async use(language: string): Promise<void> {
|
||||
const request = ++this.latestRequest;
|
||||
await registerAppDateLocale(language);
|
||||
if (request === this.latestRequest) {
|
||||
this.translate.use(language);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { PlaylistActions } from '@iptvnator/m3u-state';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppDateLocaleService } from './app-date-locales';
|
||||
import { ElectronServiceStub } from './services/electron.service.stub';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
|
||||
@@ -136,6 +137,11 @@ describe('AppComponent', () => {
|
||||
setDefaultLang: jest.fn(),
|
||||
use: jest.fn(),
|
||||
}),
|
||||
// The real service imports locale chunks; the language switch
|
||||
// is gated on it, so it must resolve deterministically here.
|
||||
MockProvider(AppDateLocaleService, {
|
||||
use: jest.fn().mockResolvedValue(undefined),
|
||||
}),
|
||||
{
|
||||
provide: WORKSPACE_SHELL_ACTIONS,
|
||||
useValue: {
|
||||
@@ -223,12 +229,12 @@ describe('AppComponent', () => {
|
||||
});
|
||||
settingsService.getValueFromLocalStorage.mockReturnValue(of(settings));
|
||||
jest.spyOn(settingsService, 'changeTheme');
|
||||
jest.spyOn(translateService, 'use');
|
||||
const dateLocales = TestBed.inject(AppDateLocaleService);
|
||||
|
||||
component.initSettings();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(translateService.use).toHaveBeenCalledWith(Language.SPANISH);
|
||||
expect(dateLocales.use).toHaveBeenCalledWith(Language.SPANISH);
|
||||
expect(settingsService.changeTheme).toHaveBeenCalledWith(
|
||||
Theme.DarkTheme
|
||||
);
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
Theme,
|
||||
createDevLogger,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { AppDateLocaleService } from './app-date-locales';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
import { PlaybackKeepAwakeService } from './services/playback-keep-awake.service';
|
||||
import { PlaylistOpenRequestService } from './services/playlist-open-request.service';
|
||||
@@ -69,6 +70,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
private actions$ = inject(Actions);
|
||||
private dataService = inject(DataService);
|
||||
private readonly dateLocales = inject(AppDateLocaleService);
|
||||
private epgBridge = inject(EpgRuntimeBridgeService);
|
||||
private epgService = inject(EpgService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
@@ -149,7 +151,9 @@ export class AppComponent implements OnInit {
|
||||
// Only specific Electron settings (MPV/VLC paths) are sent when changed in settings component
|
||||
|
||||
const resolvedLang = settings.language ?? this.DEFAULT_LANG;
|
||||
this.translate.use(resolvedLang);
|
||||
// The switch re-renders every date with the new locale;
|
||||
// its data is a lazy chunk that must be registered first.
|
||||
void this.dateLocales.use(resolvedLang);
|
||||
// Mirror the active language to localStorage so the next
|
||||
// cold start can read it synchronously in app.config.ts's
|
||||
// getInitialLanguage() and avoid the English-then-localized
|
||||
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
Language,
|
||||
Theme,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SettingsSnackbarService } from './settings-snackbar.service';
|
||||
import { SettingsStore } from '../services/settings-store.service';
|
||||
import { SettingsService } from '../services/settings.service';
|
||||
import { AppDateLocaleService } from '../app-date-locales';
|
||||
import {
|
||||
applyEpgUrlsToFormArray,
|
||||
createEpgUrlControl,
|
||||
@@ -32,6 +32,7 @@ type SettingsFormPatch = Parameters<SettingsForm['patchValue']>[0];
|
||||
*/
|
||||
@Injectable()
|
||||
export class SettingsFormFacade {
|
||||
private readonly dateLocales = inject(AppDateLocaleService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly epgBridge = inject(EpgRuntimeBridgeService);
|
||||
private readonly formBuilder = inject(FormBuilder);
|
||||
@@ -39,7 +40,6 @@ export class SettingsFormFacade {
|
||||
private readonly settingsService = inject(SettingsService);
|
||||
private readonly settingsSnackbar = inject(SettingsSnackbarService);
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
private readonly translate = inject(TranslateService);
|
||||
|
||||
readonly supportsEpg =
|
||||
this.epgBridge.supportsImport && this.epgBridge.supportsDataManagement;
|
||||
@@ -192,7 +192,10 @@ export class SettingsFormFacade {
|
||||
/** Applies the saved language/theme and resets the dirty state */
|
||||
applySavedSettings(): void {
|
||||
this.form.markAsPristine();
|
||||
this.translate.use(this.form.value.language ?? Language.ENGLISH);
|
||||
// The switch re-renders every date with the new locale; its data is
|
||||
// a lazy chunk that must be registered first, and a newer choice
|
||||
// must win over an older one whose data arrives later.
|
||||
void this.dateLocales.use(this.form.value.language ?? Language.ENGLISH);
|
||||
this.settingsService.changeTheme(
|
||||
this.form.value.theme ?? Theme.SystemTheme
|
||||
);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { resolveRestoredRendererRoute } from '@iptvnator/shared/interfaces';
|
||||
import { registerAppDateLocales } from './app/app-date-locales';
|
||||
import { registerAppDateLocale } from './app/app-date-locales';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appConfig } from './app/app.config';
|
||||
|
||||
registerAppDateLocales();
|
||||
import { appConfig, getInitialLanguage } from './app/app.config';
|
||||
|
||||
// A reloaded packaged renderer arrives on index.html with the route it was
|
||||
// on carried in the query string (the Electron main process recovers the
|
||||
@@ -18,7 +16,12 @@ if (restoredHref !== null) {
|
||||
window.history.replaceState(window.history.state, '', restoredHref);
|
||||
}
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
// Angular ships only English locale data; the preferred language's data is a
|
||||
// lazy chunk. It is awaited before bootstrapping because the first route
|
||||
// renders dates with that locale, and a locale without data throws. English
|
||||
// (the default) resolves immediately.
|
||||
registerAppDateLocale(getInitialLanguage())
|
||||
.then(() => bootstrapApplication(AppComponent, appConfig))
|
||||
.then(() => {
|
||||
// Splash is rendered eagerly by index.html so the user sees something
|
||||
// immediately instead of a blank Material-grey background. Once Angular
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
- Date display should follow the user-selected app language from `TranslateService`.
|
||||
- When a template renders localized month or weekday names, pass the normalized app locale explicitly to `DatePipe`.
|
||||
- Angular locale data is registered in [apps/web/src/app/app-date-locales.ts](/apps/web/src/app/app-date-locales.ts).
|
||||
- Angular locale data is loaded lazily per language by `registerAppDateLocale()` in [apps/web/src/app/app-date-locales.ts](/apps/web/src/app/app-date-locales.ts): `main.ts` awaits the initial language's data before bootstrapping, and every `TranslateService.use()` call site registers the data first (through `AppDateLocaleService`), because the switch re-renders dates with the new locale and a locale without data throws. English needs no data. `AppDateLocaleService.use()` applies only the language requested last when switches overlap, and a locale whose chunk fails to load falls back to English formatting under that locale id until a later call loads it.
|
||||
- App language aliases are normalized in [libs/ui/pipes/src/lib/date-format.util.ts](/libs/ui/pipes/src/lib/date-format.util.ts):
|
||||
- `ary` -> `ar-MA`
|
||||
- `by` -> `be`
|
||||
|
||||
@@ -31,9 +31,12 @@ The bytes a browser fetches before Angular can bootstrap, read from the built
|
||||
- every `<link rel="modulepreload">` chunk.
|
||||
|
||||
Manifest, icons, external URLs, commented-out tags and lazy chunks are not
|
||||
counted. A file that `index.html` references but the build did not emit is an
|
||||
error, never zero bytes. The value is raw (uncompressed) size, which is what the
|
||||
renderer parses. It is Angular's "Initial total" plus `index.html` and
|
||||
counted, so the value is the same for every language: a non-English launch
|
||||
additionally fetches that language's Angular locale chunk (about 2 KB), which
|
||||
belongs to the per-profile J1 benchmark rather than to this counter. A file
|
||||
that `index.html` references but the build did not emit is an error, never
|
||||
zero bytes. The value is raw (uncompressed) size, which is what the renderer
|
||||
parses. It is Angular's "Initial total" plus `index.html` and
|
||||
`assets/app-config.js` (about 4 KB together), so it sits slightly above the
|
||||
rounded figure the build prints; never copy that figure into a baseline, use
|
||||
the script's output. The bundle embeds only the app version from
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"journeys": {
|
||||
"launch": {
|
||||
"renderer.initialBytes": {
|
||||
"value": 2739510,
|
||||
"value": 2715087,
|
||||
"unit": "bytes",
|
||||
"updatedAt": "2026-09-26",
|
||||
"evidencePr": 1693,
|
||||
"evidencePr": 1695,
|
||||
"measuredWith": "pnpm nx build web && pnpm run perf:initial-bytes"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user