Skip to content

Bug: esbuild minification collision under NG_BUILD_OPTIMIZE_CHUNKS=1 silently corrupts an unrelated function, causing a silent app hang with no thrown error #33675

Description

@mattiLeBlanc

Which @angular/* package(s) are the source of the bug?

Don't known / other

Is this a regression?

No

Description

With NG_BUILD_OPTIMIZE_CHUNKS=1 set and a production build (optimization: true), we have a case where the minifier assigns the same short identifier to two completely unrelated top-level functions that end up merged into the same scope, and the second silently overwrites the first — with no error thrown, no console output, nothing. The app just hangs on its initial loading screen forever, because one of the two colliding functions is an internal Angular Router helper used during initial navigation, and it silently gets replaced by an unrelated third-party function with an incompatible signature.

Removing the same NG_BUILD_OPTIMIZE_CHUNKS=1 flag (falling back to ng build --configuration production without it), or removing the third-party plugin's usage entirely, or building with --configuration development (optimization: false), all avoid the issue. Only the specific combination of optimization: true + NG_BUILD_OPTIMIZE_CHUNKS=1 + this exact bundle composition reproduces it.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

In the resulting `main-*.js`, we found two distinct `function Do(...)` declarations landing in the same scope:


// Appears to be an internal Angular Router helper (route-children matching)
function Do(t,n,e){return n.length===0&&!t.children[e]}



// This is dayjs's `isBetween` plugin (dayjs/plugin/isBetween), matching its real
// implementation signature (two date args, each normalized, then compared)
function Do(e,t,n,a){var r=ee(e)?e:Y(e),i=ee(t)?t:Y(t)...


Since both are plain `function` declarations sharing the identifier `Do` in what appears to be the same merged scope, the second declaration overwrites the first (standard JS function-hoisting/redeclaration behavior — no error). Anywhere the Router's own code invokes its internal `Do(...)`, it now silently calls the third-party `isBetween` implementation instead, with mismatched arguments, silently producing wrong/undefined results instead of throwing. This corrupts route resolution during the app's initial navigation with **no thrown exception, no unhandled promise rejection, and no `NavigationError`** — the app just never finishes bootstrapping past its static loading screen.

Please provide the environment you discovered this bug in (run ng version)

- `@angular/cli`: 21.2.7
- `@angular/build`: 21.2.7
- esbuild (resolved): 0.25.12
- Node: v22.22.3
- Build command: `NG_BUILD_OPTIMIZE_CHUNKS=1 ng build --configuration production`

Anything else?

We do not yet have a minimal, isolated reproduction — the collision appears to depend on the exact set of identifiers competing for short names across the entire production bundle, so a small standalone repro may not trigger the same collision at all. We can confirm, in our own (large, multi-hundred-component) application:

  1. ng build --configuration development → works.
  2. ng build --configuration production (no NG_BUILD_OPTIMIZE_CHUNKS) → works.
  3. NG_BUILD_OPTIMIZE_CHUNKS=1 ng build --configuration production → app hangs indefinitely on its initial loading screen, no console errors of any kind.
  4. Removing our usage of dayjs/plugin/isBetween (or moving it behind a dynamic import() so it lands in its own chunk instead of being merged into the main chunk) resolves the hang under the same build command.

Happy to work with a maintainer to narrow this further (e.g. sharing an anonymized/minified diff, or a source-mapped stack once we can get one) if a minimal repro turns out to be infeasible to construct by hand.

Expected behavior

Minification should never allow two semantically distinct, unrelated functions to silently collide under the same identifier within a merged scope — at minimum, this class of chunk-merging optimization should not be able to produce a naming collision between framework-internal code and application/library code. Ideally: (a) this shouldn't be possible in the first place, and (b) if a code path fails due to this class of corruption, it should fail loudly (a thrown error) rather than silently returning wrong values.

Additional notes

  • This may be specific to the experimental NG_BUILD_OPTIMIZE_CHUNKS flag's chunk-merging behavior, since removing only that env var (keeping --configuration production) avoids the bug in our case.
  • We only noticed this because we happened to grep the built output for a known library function name (isBetween) after observing the symptom (silent hang, zero console output) and ruling out several other causes over an extended debugging session (browserslist config, dayjs version mismatches, module evaluation order/timing). The root cause was ultimately unrelated to all of those — purely a minifier identifier collision.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: build & ciRelated the build and CI infrastructure of the projectgemini-triagedLabel noting that an issue has been triaged by geminineeds: more infoReporter must clarify the issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions