mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
perf(web): keep the NgRx store devtools out of production bundles (#1810)
* perf(web): keep the NgRx store devtools out of production bundles app.config.ts imported @ngrx/store-devtools statically and gated it on AppConfig.production at runtime, so the optimizer kept the module in main.js for the production, PWA and performance builds. The providers now come from environments/store-devtools.providers.ts, an empty list in every build; only the development and electron-e2e configurations swap in the devtools through fileReplacements. A build-config test keeps it that way. renderer.initialBytes: 1,608,610 -> 1,596,045 bytes (-12,565) on a local production build; the baseline is lowered to the measured value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(web): cite #1810 as the initial-bytes baseline evidence Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
8 files changed
+109
-8
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
type: perf
|
||||
area: performance
|
||||
---
|
||||
|
||||
The app loads slightly less code at startup: a developer-only state
|
||||
inspection tool is no longer shipped in the desktop and web builds.
|
||||
@@ -177,6 +177,59 @@ test('only the web performance build installs the tick counter the journeys read
|
||||
}
|
||||
});
|
||||
|
||||
// @ngrx/store-devtools must not reach the production, PWA or performance
|
||||
// bundles (renderer.initialBytes): the default providers file is empty and
|
||||
// only the development configurations swap in the devtools.
|
||||
test('only development web builds provide the NgRx store devtools', () => {
|
||||
const environments = join(workspaceRoot, 'apps/web/src/environments');
|
||||
const devtoolsReplacement = {
|
||||
replace: 'apps/web/src/environments/store-devtools.providers.ts',
|
||||
with: 'apps/web/src/environments/store-devtools.providers.dev.ts',
|
||||
};
|
||||
// electron-e2e-zoneless is electron-e2e plus the zoneless swap.
|
||||
const developmentConfigurations = new Set([
|
||||
'development',
|
||||
'electron-e2e',
|
||||
'electron-e2e-zoneless',
|
||||
]);
|
||||
|
||||
assert.match(
|
||||
readFileSync(join(environments, 'store-devtools.providers.ts'), 'utf8'),
|
||||
/export const storeDevtoolsProviders: EnvironmentProviders\[\] = \[\];/
|
||||
);
|
||||
// The replacement must still provide them, or development builds would
|
||||
// lose the devtools while this test passed.
|
||||
assert.match(
|
||||
readFileSync(
|
||||
join(environments, 'store-devtools.providers.dev.ts'),
|
||||
'utf8'
|
||||
),
|
||||
/storeDevtoolsProviders: EnvironmentProviders\[\] = \[\s*provideStoreDevtools\(/
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
readFileSync(
|
||||
join(workspaceRoot, 'apps/web/src/app/app.config.ts'),
|
||||
'utf8'
|
||||
),
|
||||
/@ngrx\/store-devtools/
|
||||
);
|
||||
for (const [name, configuration] of Object.entries(
|
||||
webProject.targets['build'].configurations ?? {}
|
||||
)) {
|
||||
const replacements = (configuration['fileReplacements'] ??
|
||||
[]) as unknown[];
|
||||
if (developmentConfigurations.has(name)) {
|
||||
assert.deepEqual(replacements[0], devtoolsReplacement, name);
|
||||
} else {
|
||||
assert.doesNotMatch(
|
||||
JSON.stringify(replacements),
|
||||
/store-devtools/,
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Plan item C6 measures zoneless change detection behind a build-time flag:
|
||||
// each *-zoneless configuration is its base configuration plus one swap of
|
||||
// the change-detection providers, and nothing else selects that swap.
|
||||
|
||||
+18
-2
@@ -120,7 +120,13 @@
|
||||
"serviceWorker": false,
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/web/src/environments/store-devtools.providers.ts",
|
||||
"with": "apps/web/src/environments/store-devtools.providers.dev.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"electron-performance": {
|
||||
"baseHref": "./",
|
||||
@@ -173,7 +179,13 @@
|
||||
"serviceWorker": false,
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/web/src/environments/store-devtools.providers.ts",
|
||||
"with": "apps/web/src/environments/store-devtools.providers.dev.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"electron-e2e-zoneless": {
|
||||
"baseHref": "./",
|
||||
@@ -182,6 +194,10 @@
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/web/src/environments/store-devtools.providers.ts",
|
||||
"with": "apps/web/src/environments/store-devtools.providers.dev.ts"
|
||||
},
|
||||
{
|
||||
"replace": "apps/web/src/environments/change-detection.providers.ts",
|
||||
"with": "apps/web/src/environments/change-detection.providers.zoneless.ts"
|
||||
|
||||
@@ -16,7 +16,6 @@ import { provideServiceWorker } from '@angular/service-worker';
|
||||
import { provideEffects } from '@ngrx/effects';
|
||||
import { provideRouterStore, routerReducer } from '@ngrx/router-store';
|
||||
import { provideStore } from '@ngrx/store';
|
||||
import { provideStoreDevtools } from '@ngrx/store-devtools';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { PlaylistEffects, playlistReducer } from '@iptvnator/m3u-state';
|
||||
@@ -33,8 +32,8 @@ import {
|
||||
DataService,
|
||||
} from '@iptvnator/services';
|
||||
import { dbConfig } from '@iptvnator/shared/interfaces';
|
||||
import { AppConfig } from '../environments/environment';
|
||||
import { changeDetectionProviders } from '../environments/change-detection.providers';
|
||||
import { storeDevtoolsProviders } from '../environments/store-devtools.providers';
|
||||
import { routes } from './app.routes';
|
||||
import { ElectronService } from './services/electron.service';
|
||||
import { ExternalPlaybackService } from './services/external-playback.service';
|
||||
@@ -122,7 +121,7 @@ export const appConfig: ApplicationConfig = {
|
||||
}),
|
||||
provideEffects([PlaylistEffects]),
|
||||
provideRouterStore(),
|
||||
...(AppConfig.production ? [] : [provideStoreDevtools({ maxAge: 25 })]),
|
||||
...storeDevtoolsProviders,
|
||||
provideServiceWorker('ngsw-worker.js', {
|
||||
enabled: shouldEnableServiceWorker(),
|
||||
registrationStrategy: 'registerWhenStable:30000',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { EnvironmentProviders } from '@angular/core';
|
||||
import { provideStoreDevtools } from '@ngrx/store-devtools';
|
||||
|
||||
// Swapped in for store-devtools.providers.ts by the development,
|
||||
// electron-e2e and electron-e2e-zoneless build configurations only.
|
||||
export const storeDevtoolsProviders: EnvironmentProviders[] = [
|
||||
provideStoreDevtools({ maxAge: 25 }),
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
import { EnvironmentProviders } from '@angular/core';
|
||||
|
||||
// Every build ships without the NgRx store devtools. The development and
|
||||
// electron-e2e configurations replace this file with
|
||||
// store-devtools.providers.dev.ts, so @ngrx/store-devtools never enters the
|
||||
// production, PWA or performance bundles.
|
||||
export const storeDevtoolsProviders: EnvironmentProviders[] = [];
|
||||
@@ -1066,6 +1066,17 @@ pnpm --silent run perf:initial-bytes -- --json # machine-readable; --silent ke
|
||||
node tools/performance/measure-initial-bytes.mjs --summary dist/performance/journey-summary.json
|
||||
```
|
||||
|
||||
Development-only code must stay out of the counter by construction, not by a
|
||||
runtime flag: `if (!AppConfig.production)` keeps the imported module in
|
||||
`main.js` because the optimizer does not fold the property read. The NgRx
|
||||
store devtools therefore come from
|
||||
`apps/web/src/environments/store-devtools.providers.ts`, an empty list in
|
||||
every build, which only the `development`, `electron-e2e` and
|
||||
`electron-e2e-zoneless` configurations replace with
|
||||
`store-devtools.providers.dev.ts`; a build-config test in
|
||||
`performance-build-config.spec.ts` keeps it that way. Removing the static
|
||||
import lowered the counter by 12,565 bytes.
|
||||
|
||||
`--summary` writes the journey summary shape (`journeys.<journey>.counters`)
|
||||
that the ratchet checker consumes. `--dist <dir>` points the script at another
|
||||
build output, for example the `electron-performance` configuration.
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"journeys": {
|
||||
"launch": {
|
||||
"renderer.initialBytes": {
|
||||
"value": 1605046,
|
||||
"value": 1595777,
|
||||
"unit": "bytes",
|
||||
"slack": 4096,
|
||||
"updatedAt": "2026-10-01",
|
||||
"evidencePr": 1775,
|
||||
"updatedAt": "2026-10-07",
|
||||
"evidencePr": 1810,
|
||||
"measuredWith": "pnpm nx build web && pnpm run perf:initial-bytes"
|
||||
},
|
||||
"renderer.ipcCallsToFirstCard": {
|
||||
|
||||
Reference in new issue
Block a user