Command
build
Is this a regression?
The previous version in which this bug was not present was
No response
Description
In our relatively large application (initial bundle 18 MB raw, about 200 JS output files, and 8000 metafile inputs) a production ng build takes 234s, of which about 180s is the OPTIMIZE_CHUNKS phase (NG_BUILD_DEBUG_PERF=1). Rolldown itself accounts for about 3s of that. The remaining 157s is spent in bundleOutputToEsbuildMetafile in packages/angular/build/src/builders/application/chunk-optimizer.ts.
The cause is this loop:
for (const [moduleId, renderedModule] of Object.entries(chunk.modules)) {
...
for (const [originalInputPath, originalInputInfo] of Object.entries(originalOutputEntry.inputs)) {
const proportion = originalInputInfo.bytesInOutput / totalOriginalBytesInModule;
const newBytesInOutput = Math.floor(renderedModule.renderedLength * proportion);
...
}
}
renderedModule.renderedLength is read once per input of the module but it is an uncached getter:
// rolldown/dist/shared/bindingify-input-options-*.mjs
function transformToRenderedModule(bindingRenderedModule) {
return {
get code() { return bindingRenderedModule.code; },
get renderedLength() { return bindingRenderedModule.code?.length || 0; },
...
};
}
The bindingRenderedModule.code getter copies the module's entire rendered code from Rust into a fresh JS string at every read. In the chunk optimizer each "module" is a whole esbuild output chunk, so for our main chunk the inner loop performs 3,588 copies of a 17 MB string, roughly 62 GB of string transfer.
Reading renderedLength once per module before the loop fixes it:
const renderedLength = renderedModule.renderedLength;
for (const [originalInputPath, originalInputInfo] of Object.entries(originalOutputEntry.inputs)) {
const proportion = originalInputInfo.bytesInOutput / totalOriginalBytesInModule;
const newBytesInOutput = Math.floor(renderedLength * proportion);
...
}
With sourceMap: true:
OPTIMIZE_CHUNKS took about 180s before, and 4s after.
The total build went from 234s to 55s, which is huge.
I didn't find any difference in the output.
Minimal Reproduction
The cost is proportional to the number of inputs in a chunk and its size, so it needs a pretty large app to be visible. Any project whose main chunk has a few thousand input files and is several MB will show it.
ng new repro (or any application using @angular/build:application / @angular-devkit/build-angular:application).
- Make the main bundle large with many inputs: import a few thousand small modules, a couple of large libraries, and at least 3 lazy routes so the chunk optimizer runs (default threshold
NG_BUILD_OPTIMIZE_CHUNKS=3).
NG_BUILD_DEBUG_PERF=1 ng build --configuration=production
- Compare
DURATION[OPTIMIZE_CHUNKS] with and without the one-line change above (or with NG_BUILD_OPTIMIZE_CHUNKS=false).
For a direct measurement without a repro app, wrapping the bundleOutputToEsbuildMetafile call in process.hrtime shows it accounts for essentially the whole OPTIMIZE_CHUNKS duration.
Exception or Error
[PLUGIN_TIMINGS] Your build spent significant time in plugin `angular-bundle`. See https://rolldown.rs/reference/InputOptions.checks#plugintimings for more details.
DURATION[OPTIMIZE_CHUNKS]: 179.393928137s
Application bundle generation complete. [234.194 seconds]
With per-phase timers added inside `optimizeChunks`:
[CHUNK-OPT] bundle.generate(): 3.226s
[CHUNK-OPT] bundle.close(): 0.000s
[CHUNK-OPT] metafile: chunk.modules access 0.002s, module iters 184, input iters 3770, inputs in metafile 7911, outputs 1150
[CHUNK-OPT] bundleOutputToEsbuildMetafile: 157.214s
[CHUNK-OPT] push optimized chunks (code/map access + toString): 0.730s
DURATION[OPTIMIZE_CHUNKS]: 161.235368804s
Your Environment
Angular CLI : 22.1.7
Angular : 22.1.5
Node.js : 24.16.0
Package Manager : yarn 1.22.22
Operating System : linux x64
@angular-devkit/build-angular 22.1.7
@angular/build 22.1.7
@angular/cli 22.1.7
@angular/compiler-cli 22.1.5
ng-packagr 22.0.0
typescript 6.0.3
zone.js 0.15.0
rolldown (dependency of @angular/build) 1.2.0
esbuild 0.28.2
Anything else relevant?
- Build options:
optimization: true, sourceMap: true, outputHashing: all, namedChunks: false. The slowdown does not depend on sourcemaps though.
- Happy to open a PR with the fix.
Command
build
Is this a regression?
The previous version in which this bug was not present was
No response
Description
In our relatively large application (initial bundle 18 MB raw, about 200 JS output files, and 8000 metafile inputs) a production
ng buildtakes 234s, of which about 180s is theOPTIMIZE_CHUNKSphase (NG_BUILD_DEBUG_PERF=1). Rolldown itself accounts for about 3s of that. The remaining 157s is spent inbundleOutputToEsbuildMetafileinpackages/angular/build/src/builders/application/chunk-optimizer.ts.The cause is this loop:
renderedModule.renderedLengthis read once per input of the module but it is an uncached getter:The
bindingRenderedModule.codegetter copies the module's entire rendered code from Rust into a fresh JS string at every read. In the chunk optimizer each "module" is a whole esbuild output chunk, so for our main chunk the inner loop performs 3,588 copies of a 17 MB string, roughly 62 GB of string transfer.Reading
renderedLengthonce per module before the loop fixes it:With
sourceMap: true:OPTIMIZE_CHUNKS took about 180s before, and 4s after.
The total build went from 234s to 55s, which is huge.
I didn't find any difference in the output.
Minimal Reproduction
The cost is proportional to the number of inputs in a chunk and its size, so it needs a pretty large app to be visible. Any project whose main chunk has a few thousand input files and is several MB will show it.
ng new repro(or any application using@angular/build:application/@angular-devkit/build-angular:application).NG_BUILD_OPTIMIZE_CHUNKS=3).NG_BUILD_DEBUG_PERF=1 ng build --configuration=productionDURATION[OPTIMIZE_CHUNKS]with and without the one-line change above (or withNG_BUILD_OPTIMIZE_CHUNKS=false).For a direct measurement without a repro app, wrapping the
bundleOutputToEsbuildMetafilecall inprocess.hrtimeshows it accounts for essentially the wholeOPTIMIZE_CHUNKSduration.Exception or Error
Your Environment
Anything else relevant?
optimization: true,sourceMap: true,outputHashing: all,namedChunks: false. The slowdown does not depend on sourcemaps though.