@@ -13,9 +13,10 @@ import fs from 'node:fs/promises';
1313import { createRequire } from 'node:module' ;
1414import os from 'node:os' ;
1515import path from 'node:path' ;
16+ import { pathToFileURL } from 'node:url' ;
1617
17- import type { BuildOutputFile } from '../../../dist/@ angular/build/src/tools/esbuild/bundler-files.d.ts ' ;
18- import type { LocaleInlineOptions } from '../../../dist/@ angular/build/src/tools/esbuild/i18n-inliner.d.ts ' ;
18+ import type { BuildOutputFile } from '../../../packages/ angular/build/src/tools/esbuild/bundler-files.js ' ;
19+ import type { LocaleInlineOptions } from '../../../packages/ angular/build/src/tools/esbuild/i18n-inliner.js ' ;
1920import { generateSyntheticBundle , generateTranslations , initializeFixtures } from './fixtures.mts' ;
2021import type { BenchmarkScenario } from './harness.mts' ;
2122
@@ -25,7 +26,7 @@ const requireFromBuild = createRequire(
2526
2627const { I18nInliner } = requireFromBuild (
2728 '../../../dist/@angular/build/src/tools/esbuild/i18n-inliner.js' ,
28- ) as typeof import ( '../../../dist/@ angular/build/src/tools/esbuild/i18n-inliner.d.ts ' ) ;
29+ ) as typeof import ( '../../../packages/ angular/build/src/tools/esbuild/i18n-inliner.js ' ) ;
2930
3031export interface ScenarioFactoryOptions {
3132 concurrency ?: number ;
@@ -124,6 +125,15 @@ function calculateInputSizeBytes(files: BuildOutputFile[]): number {
124125 } , 0 ) ;
125126}
126127
128+ /**
129+ * Note on Worker Pool Lifecycle:
130+ * Each scenario's run() method instantiates and closes an I18nInliner per iteration.
131+ * This is designed as a macro benchmark to reflect the cold-start behavior of single-shot
132+ * CLI build invocations (including worker thread pool initialization, task dispatch,
133+ * inlining transformations, and thread pool shutdown). Warmup iterations warm up the
134+ * main-thread V8 isolate and runtime paths, while worker threads are initialized per iteration.
135+ */
136+
127137/**
128138 * 1. Standard App Scenario:
129139 * 1 main bundle (1 MB) + 20 route chunks (50 KB) = ~2 MB input JS, 8 locales, maps enabled.
@@ -350,15 +360,28 @@ export function createPersistentCacheWarmScenario(
350360
351361 // Prime the persistent cache out-of-process so cold worker thread allocations
352362 // do not inflate this process's RSS metrics.
363+ const scenariosUrl = pathToFileURL ( path . resolve ( import . meta. dirname , './scenarios.mts' ) ) . href ;
353364 const primerCode =
354- `import { primeCache } from ${ JSON . stringify ( path . resolve ( import . meta . dirname , './scenarios.mts' ) ) } ;\n` +
365+ `import { primeCache } from ${ JSON . stringify ( scenariosUrl ) } ;\n` +
355366 `await primeCache(${ JSON . stringify ( cacheDir ) } , ${ options . concurrency ?? 'undefined' } );\n` ;
356367
357- spawnSync (
368+ const primerProc = spawnSync (
358369 process . execPath ,
359- [ '--no-warnings=ExperimentalWarning' , '--experimental-transform-types' , '-e' , primerCode ] ,
370+ [
371+ '--no-warnings=ExperimentalWarning' ,
372+ '--experimental-transform-types' ,
373+ '--input-type=module' ,
374+ '-e' ,
375+ primerCode ,
376+ ] ,
360377 { stdio : 'inherit' } ,
361378 ) ;
379+
380+ if ( primerProc . status !== 0 || primerProc . error ) {
381+ throw new Error (
382+ `Failed to prime cache for persistent-cache-warm scenario: ${ primerProc . error ?. message ?? primerProc . status } ` ,
383+ ) ;
384+ }
362385 } ,
363386
364387 async run ( ) {
@@ -384,11 +407,60 @@ export function createPersistentCacheWarmScenario(
384407 } ;
385408}
386409
410+ /**
411+ * 6. Large Enterprise (10k translations) Scenario:
412+ * 1 main bundle (3 MB) + 100 chunks (50 KB), 32 locales, 10,000 translations, sourcemaps ON.
413+ * Maximum scale stress test for binary translation tables, memory retention, and multi-locale windows.
414+ */
415+ export function createLargeEnterpriseScenario (
416+ options : ScenarioFactoryOptions = { } ,
417+ ) : BenchmarkScenario {
418+ let workload : GeneratedWorkload | undefined ;
419+
420+ return {
421+ name : 'large-enterprise-10k' ,
422+ description :
423+ 'Large Enterprise (10k msgs): 1 main (3 MB) + 100 chunks (50 KB), 32 locales, 10,000 translations' ,
424+ get inputSizeBytes ( ) {
425+ return workload ?. totalInputSizeBytes ?? 0 ;
426+ } ,
427+ get localeCount ( ) {
428+ return DEFAULT_LOCALES_32 . length ;
429+ } ,
430+ async setup ( ) {
431+ await initializeFixtures ( ) ;
432+ const files = createBundleSet ( 3 * 1024 * 1024 , 100 , 50 * 1024 , 10000 , true ) ;
433+ const locales = generateTranslations ( DEFAULT_LOCALES_32 , 10000 ) ;
434+
435+ workload = {
436+ files,
437+ locales,
438+ totalInputSizeBytes : calculateInputSizeBytes ( files ) ,
439+ } ;
440+ } ,
441+ async run ( ) {
442+ if ( ! workload ) {
443+ return ;
444+ }
445+ const inliner = new I18nInliner ( {
446+ missingTranslation : 'warning' ,
447+ maxConcurrency : options . concurrency ,
448+ } ) ;
449+ try {
450+ await inliner . inlineAll ( workload . files , workload . locales ) ;
451+ } finally {
452+ await inliner . close ( ) ;
453+ }
454+ } ,
455+ } ;
456+ }
457+
387458export function getAllScenarios ( options : ScenarioFactoryOptions = { } ) : BenchmarkScenario [ ] {
388459 return [
389460 createStandardAppScenario ( options ) ,
390461 createStandardAppNoMapsScenario ( options ) ,
391462 createEnterpriseScenario ( options ) ,
463+ createLargeEnterpriseScenario ( options ) ,
392464 createMonolithicScenario ( options ) ,
393465 createPersistentCacheWarmScenario ( options ) ,
394466 ] ;
0 commit comments