Command
serve
Is this a regression?
The previous version in which this bug was not present was
21.2.17
Description
When a local barrel file re-exports a third-party dependency with export *, and that dependency is prebundled by the dev server's Vite dependency optimizer, esbuild cannot enumerate the dependency's exports. Depending on the shape of the module graph this produces two different failures in ng serve only (ng build is always fine, since it does not use Vite prebundling):
- Deterministic build error (minimal repro below): every named import going through the barrel fails with
X [ERROR] No matching export in "src/app/tracking/index.ts" for import "init" — although the export exists and the same code builds fine with ng build.
- Silent runtime break (observed in a large real-world workspace): the build succeeds, esbuild emits a runtime namespace object for the barrel (
var amplitude_exports = {}; __reExport(amplitude_exports, ...)) in a shared chunk and rewrites consumers to (0, amplitude_exports.withAmplitude)(...) — but the entry chunk that uses the namespace does not import it from the shared chunk (it imports the module's init_* function from that same chunk, so the chunk reference itself is correct — only the namespace binding is missing). The app then throws Uncaught ReferenceError: amplitude_exports is not defined on load. Whether the import is emitted or dropped depends on the overall chunk graph: an app with an almost identical setup in another workspace gets the correct import { amplitude_exports } from "/chunk-….js" and works.
Minimal Reproduction
npx @angular/cli@22 new repro --defaults --style=css --ssr=false
cd repro
npm install @amplitude/analytics-browser@2.45.2 # any prebundled dep works
src/app/tracking/index.ts (the barrel):
export * from '@amplitude/analytics-browser';
src/app/app.config.ts (eager consumer):
import { init } from './tracking';
init('dummy-api-key', { autocapture: false });
// ...unchanged appConfig
src/app/app.routes.ts (two lazy consumers):
export const routes: Routes = [
{ path: 'a', loadComponent: () => import('./page-a/page-a').then(m => m.PageA) },
{ path: 'b', loadComponent: () => import('./page-b/page-b').then(m => m.PageB) },
];
src/app/page-a/page-a.ts:
import { Component } from '@angular/core';
import { track } from '../tracking';
@Component({ selector: 'app-page-a', template: '<p>A</p>' })
export class PageA {
constructor() {
track('page-a');
}
}
src/app/page-b/page-b.ts (also imports the barrel dynamically — this makes the failure deterministic):
import { Component } from '@angular/core';
import { setUserId } from '../tracking';
@Component({ selector: 'app-page-b', template: '<p>B</p>' })
export class PageB {
constructor() {
setUserId('b');
void import('../tracking').then(m => m.track('dynamic'));
}
}
Then:
X [ERROR] No matching export in "src/app/tracking/index.ts" for import "init"
src/app/app.config.ts:4:9:
4 │ import { init } from './tracking';
╵ ~~~~
X [ERROR] No matching export in "src/app/tracking/index.ts" for import "track"
X [ERROR] No matching export in "src/app/tracking/index.ts" for import "setUserId"
Expected behavior
ng serve should behave like ng build: named imports through an export * barrel of a (prebundled) dependency resolve normally, and any runtime namespace object emitted for such a barrel is imported by every chunk that references it.
Workaround
Excluding the dependency from prebundling fixes both variants, because esbuild then sees the dependency's exports statically and needs no runtime namespace:
"serve": {
"options": {
"prebundle": { "exclude": ["@amplitude/analytics-browser"] }
}
}
Exception or Error
Your Environment
Angular CLI : 22.0.7
Angular : 22.0.7
Node.js : 24.18.0
Package Manager : npm 11.16.0
Operating System : win32 x64
┌────────────────────────────┬───────────────────┬───────────────────┐
│ Package │ Installed Version │ Requested Version │
├────────────────────────────┼───────────────────┼───────────────────┤
│ @angular-devkit/core │ 22.0.7 │ 22.0.7 │
│ @angular-devkit/schematics │ 22.0.7 │ 22.0.7 │
│ @angular/animations │ 22.0.7 │ 22.0.7 │
│ @angular/build │ 22.0.7 │ 22.0.7 │
│ @angular/cdk │ 22.0.5 │ 22.0.5 │
│ @angular/cli │ 22.0.7 │ 22.0.7 │
│ @angular/common │ 22.0.7 │ 22.0.7 │
│ @angular/compiler │ 22.0.7 │ 22.0.7 │
│ @angular/compiler-cli │ 22.0.7 │ 22.0.7 │
│ @angular/core │ 22.0.7 │ 22.0.7 │
│ @angular/forms │ 22.0.7 │ 22.0.7 │
│ @angular/platform-browser │ 22.0.7 │ 22.0.7 │
│ @angular/router │ 22.0.7 │ 22.0.7 │
│ rxjs │ 7.8.2 │ 7.8.2 │
│ typescript │ 6.0.3 │ 6.0.3 │
│ vitest │ 4.1.10 │ 4.1.10 │
│ zone.js │ 0.16.2 │ 0.16.2 │
└────────────────────────────┴───────────────────┴───────────────────┘
Anything else relevant?
No response
Command
serve
Is this a regression?
The previous version in which this bug was not present was
21.2.17
Description
When a local barrel file re-exports a third-party dependency with
export *, and that dependency is prebundled by the dev server's Vite dependency optimizer, esbuild cannot enumerate the dependency's exports. Depending on the shape of the module graph this produces two different failures inng serveonly (ng buildis always fine, since it does not use Vite prebundling):X [ERROR] No matching export in "src/app/tracking/index.ts" for import "init"— although the export exists and the same code builds fine withng build.var amplitude_exports = {}; __reExport(amplitude_exports, ...)) in a shared chunk and rewrites consumers to(0, amplitude_exports.withAmplitude)(...)— but the entry chunk that uses the namespace does not import it from the shared chunk (it imports the module'sinit_*function from that same chunk, so the chunk reference itself is correct — only the namespace binding is missing). The app then throwsUncaught ReferenceError: amplitude_exports is not definedon load. Whether the import is emitted or dropped depends on the overall chunk graph: an app with an almost identical setup in another workspace gets the correctimport { amplitude_exports } from "/chunk-….js"and works.Minimal Reproduction
src/app/tracking/index.ts(the barrel):src/app/app.config.ts(eager consumer):src/app/app.routes.ts(two lazy consumers):src/app/page-a/page-a.ts:src/app/page-b/page-b.ts(also imports the barrel dynamically — this makes the failure deterministic):Then:
Expected behavior
ng serveshould behave likeng build: named imports through anexport *barrel of a (prebundled) dependency resolve normally, and any runtime namespace object emitted for such a barrel is imported by every chunk that references it.Workaround
Excluding the dependency from prebundling fixes both variants, because esbuild then sees the dependency's exports statically and needs no runtime namespace:
Exception or Error
Your Environment
Anything else relevant?
No response