Skip to content

fix(@angular/build): count statically imported chunks in the initial total - #33793

Merged
alan-agius4 merged 1 commit into
angular:mainfrom
imaksp:fix-initial-files-after-chunk-optimization
Aug 6, 2026
Merged

fix(@angular/build): count statically imported chunks in the initial total#33793
alan-agius4 merged 1 commit into
angular:mainfrom
imaksp:fix-initial-files-after-chunk-optimization

Conversation

@imaksp

@imaksp imaksp commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #33773

Problem

optimizeChunks drops every used chunk from initialFiles, keeping only the main entry:

for (const usedFile of usedChunks) {
  if (usedFile === mainFile) {
    entriesToAnalyze.push([mainFile, original.initialFiles.get(mainFile)!]);
    continue;
  }
  original.initialFiles.delete(usedFile);
}

It then re-walks the optimized graph to restore the chunks the entry still imports statically — but the record it builds is never written back to the map:

const record = createInitialFileRecord(entryRecord.depth + 1);

entriesToAnalyze.push([importPath, record]);   // record is never stored

Since those entries were just deleted, existingRecord is always undefined, so every chunk is analyzed and then discarded. initialFiles ends up containing only the configured entry points, which is exactly what "Initial total" sums — so chunks the browser must download before the app can run are excluded from the total and reported under "Lazy chunk files".

BundlerContext#bundle performs the equivalent walk in tools/esbuild/bundler-context.ts and does store the record before pushing:

initialFiles.set(initialImport.path, record);
if (!initialImport.external) {
  entriesToAnalyze.push([initialImport.path, record]);
}

This change aligns the optimizer with that behaviour.

Note the esbuild metafile is correct — --stats-json reports these as kind: "import-statement" from main. Only the post-optimization bookkeeping is wrong.

Effect

Same application, production build, only this line changed.

Before

Initial chunk files     | Names         |  Raw size | Estimated transfer size
main-32IOW7BN.js        | main          | 199.93 kB |                48.59 kB
styles-5EGQDBXI.css     | styles        | 149.33 kB |                19.43 kB
polyfills-6XSCAVXF.js   | polyfills     |  28.09 kB |                 7.74 kB

                        | Initial total | 377.35 kB |                75.76 kB

Lazy chunk files        | Names         |  Raw size | Estimated transfer size
chunk-BJebXLml.js       | -             | 919.49 kB |               222.35 kB   <-- imported statically by main

After

Initial chunk files     | Names         |  Raw size | Estimated transfer size
chunk-BJebXLml.js       | -             | 919.49 kB |               222.35 kB
main-32IOW7BN.js        | main          | 199.93 kB |                48.59 kB
styles-5EGQDBXI.css     | styles        | 149.33 kB |                19.43 kB
chunk-pTHVPgQM.js       | -             |  36.09 kB |                 9.84 kB
polyfills-6XSCAVXF.js   | polyfills     |  28.09 kB |                 7.74 kB
chunk-jpabHedg.js       | -             |   9.95 kB |                 3.24 kB
chunk-Dss6OLyd.js       | -             |   3.07 kB |                 1.25 kB
chunk-XzxpTrya.js       | -             |   2.28 kB |                 1.02 kB
chunk-qP99rXNo.js       | -             | 710 bytes |               710 bytes
chunk-BL5j3Uqh.js       | -             | 534 bytes |               534 bytes

                        | Initial total |   1.35 MB |               314.70 kB

Independently cross-checked by walking index.html through static import/from edges only (skipping import()): 1172.0 kB of JS plus 149.3 kB of CSS, matching the corrected total.

Beyond the reported number, this also feeds generateIndexHtml and the server manifest's initial-files set, so those consume the same corrected map.

On test coverage

I was not able to build a fixture that exercises this path, and I would appreciate guidance.

The failure is only observable when the optimizer's output still contains main -> chunk static edges. In small applications Rolldown hoists all entry-reachable shared code into the main chunk, so no such chunk survives to be lost — which chunk-optimization-server_spec.ts already relies on, asserting hasFileMatch('dist/browser', /^chunk-/) is false as proof the pass ran.

I tried a fresh ng new on 22.1.3 across several shapes — 1 and 8 lazy routes, a shared component, a ~2 MB shared module producing a 1.72 MB main, and eight ~154 kB lazy routes sharing vendor code — and every one produced zero static chunks from main, leaving nothing to misreport. The affected application has ~114 lazy chunks.

If a fixture shape that retains a shared chunk would be acceptable, or if you would prefer a unit test asserting the invariant directly (every importsPerFile[mainFile] entry present in initialFiles after optimizeChunks), I am happy to add it.

@google-cla

google-cla Bot commented Aug 6, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the chunk optimizer in the Angular build package to register the newly created file record in the 'original.initialFiles' map before pushing it to the analysis queue. I have no feedback to provide as there are no review comments.

…total

`optimizeChunks` removes every used chunk from `initialFiles` and then re-walks
the optimized graph to restore the ones the main entry still imports statically.
The walk builds an `InitialFileRecord` for each import but never stores it, so
the entries are analyzed and then dropped.

Because the records were deleted just above, `existingRecord` is always
undefined, and the map ends up holding only the configured entry points. Chunks
the browser must fetch before the application can run are therefore excluded
from "Initial total" and reported under "Lazy chunk files" instead. On a large
application this understated the initial payload by roughly 3x: 377.35 kB
reported against 1.35 MB actually required, with a 919 kB statically imported
chunk listed as lazy.

`BundlerContext#bundle` performs the equivalent walk and does call
`initialFiles.set` before pushing the entry; this aligns the optimizer with it.

Fixes angular#33773
@imaksp
imaksp force-pushed the fix-initial-files-after-chunk-optimization branch from c43e69e to 1b59423 Compare August 6, 2026 11:30

@alan-agius4 alan-agius4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@alan-agius4 alan-agius4 added target: patch This PR is targeted for the next patch release action: merge The PR is ready for merge by the caretaker and removed area: @angular/build labels Aug 6, 2026
@alan-agius4
alan-agius4 merged commit 04b5327 into angular:main Aug 6, 2026
41 checks passed
@alan-agius4

Copy link
Copy Markdown
Collaborator

This PR was merged into the repository. The changes were merged into the following branches:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: merge The PR is ready for merge by the caretaker target: patch This PR is targeted for the next patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect initial total reporting with Rolldown based optimizer (22.1.x)

2 participants