Skip to content

Build hangs in a retry loop when the undeclared esbuild import resolves to a different version than @angular/build uses #106

Description

@Ionaru

Summary

@angular-architects/native-federation-v4 imports esbuild without declaring it as a dependency (the same class of problem as the mrmime gap in #100). The import resolves through the package manager's hoisting to whatever esbuild happens to sit at the workspace root, which is not necessarily the version @angular/build uses internally.

When the two versions differ, the federation build's error handling can break: an esbuild build that fails does not surface its failure. Instead, every attempt ends in

✘ [ERROR] Unexpected end of JSON input [plugin onEnd]
    <ws>/node_modules/esbuild/lib/main.js:1882:14:
      1882 │   return JSON.parse(text);
    at requestCallbacks.on-end (<ws>/node_modules/esbuild/lib/main.js:1216:54)

and the build is retried in a hot loop, roughly 8 times per second, forever. The compile errors are printed on every iteration (we killed one run after 10 minutes with the same two TS errors printed 13,467 times into a 16 MB log), the process never exits, and CI has to be aborted by hand.

With matched versions, the identical failure exits code 1 within seconds with ERRR Error building federation artifacts, so only the mismatch separates "clean failure" from "infinite loop".

A correction to an earlier version of this report: not every mismatched pair breaks. Re-measured on the same tree with a deliberate compile error, 0.27.4 against 0.27.3 loops (409 JSON crashes in 75s) while 0.28.1 against 0.27.3 exits 1 normally. A mismatch appears to be necessary but not sufficient, presumably depending on whether the two versions' service protocol differs. Since which pairs are safe is not predictable from the outside, treating any mismatch as a defect still seems right, but the declared dependency is what removes the whole class.

Affected versions

  • @angular-architects/native-federation-v4@21.2.8 (dist-tag latest): esbuild absent from dependencies, imported in src/builders/build/builder.js and src/utils/angular-esbuild-adapter.js
  • @angular-architects/native-federation-v4@20.4.3 (dist-tag v20-support): same

Observed against @angular/build 21.2.15 (esbuild 0.27.3) and 21.2.19 (esbuild 0.28.1), Node 24, macOS.

Minimal reproduction

https://github.com/Ionaru/nf-v4-esbuild-mismatch-repro

git clone https://github.com/Ionaru/nf-v4-esbuild-mismatch-repro
cd nf-v4-esbuild-mismatch-repro && npm install
npx ng build --configuration production   # never exits

It is a stock ng new plus native-federation-v4:init, one type error in the exposed component,
and esbuild pinned away from the version @angular/build uses. To confirm the cause in place:

npm i -D esbuild@0.28.1 --save-exact      # match @angular/build
npx ng build --configuration production   # exits 1 in ~1s with ERRR Error building federation artifacts
bridge resolves @angular/build resolves failing build
0.27.4 0.28.1 hangs, JSON-crash storm
0.28.1 0.28.1 exits 1 in ~1s

The pin only makes it deterministic. npm ls esbuild in that repo shows @angular/build -> vite -> esbuild@0.27.4 next to esbuild@0.28.1, so hoisting can produce the same split on its own as
unrelated dependencies shift; under pnpm the undeclared import falls back to .pnpm/node_modules.

Diagnosis one-liner for any workspace, both lines must match:

node -e "const {createRequire}=require('module');const fs=require('fs');
console.log('bridge  :', createRequire(fs.realpathSync('node_modules/@angular-architects/native-federation-v4')+'/src/builders/build/builder.js')('esbuild/package.json').version);
console.log('ng/build:', createRequire(fs.realpathSync('node_modules/@angular/build')+'/src/index.js')('esbuild/package.json').version);"

Isolation evidence in a real workspace

In a large pnpm workspace where the bridge resolved 0.27.4 (pnpm's hoisted fallback) and @angular/build resolved 0.27.3, we flipped only the hoisted node_modules/.pnpm/node_modules/esbuild symlink back and forth, with an intentionally failing build:

bridge esbuild @angular/build esbuild failing build
0.27.4 0.27.3 hangs, JSON-crash storm
0.27.3 0.27.3 exits 1 in ~60s, ERRR Error building federation artifacts
0.27.4 0.27.3 hangs again

Same tree, same code, same config in all three runs.

This is easy to hit in real projects: under npm/yarn hoisting any dependency that drags in another esbuild at the root changes what the bridge gets, and under pnpm the undeclared import falls back to .pnpm/node_modules, whose esbuild version shifts as unrelated dependencies change. It is invisible while builds succeed and turns the first real compile error into a CI hang.

Suggested fix

  • Declare esbuild in dependencies, aligned with the @angular/build version in use. The mainline @angular-architects/native-federation already declares it, so this is drift in the bridge, like native-federation-v4 imports mrmime without declaring it as a dependency #100.
  • Defence in depth: since the adapter passes @angular/build's compiler plugin into a context created by its own esbuild import, resolving that esbuild instance from @angular/build (or asserting the versions match at startup) would fail fast with a clear message instead of looping.

Possibly related: #95 reports a build that does not exit after a successful run on the mainline package; if that setup also carries two esbuild versions, it may be the same mechanism.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions