You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
No response
Description
When the dev server serves an optimized/minified build (ng serve with a configuration
where optimization is enabled) and the build has externalDependencies, most external imports
reach the browser with Vite's internal /@id/ prefix still attached instead of being restored
to bare specifiers.
Vite's import-analysis rewrites external bare imports to <base>@id/<pkg>. The angular-plugin-remove-id-prefix transform (id-prefix-plugin.ts)
is supposed to strip that prefix, but its regex appends a greedy (?:/.+)? to each external
name. On minified (single-line) output, the first match on an external with a deep import path
(e.g. @angular/common/http) consumes the remainder of the line into the capture group, so
every later /@id/ occurrence on that line is never matched and is served unstripped. The
browser then requests e.g. http://localhost:4200/@id/@angular/router, receives the index.html fallback, and module loading fails.
Serving an unoptimized build of the same app works, because esbuild emits one import per line
and the greedy match is clipped by the newline.
A detailed root-cause analysis, a suggested one-line fix (verified end-to-end; PR prepared with
a regression spec), and a patch-package workaround are in the attached additional-info.md.
Minimal Reproduction
The defect is deterministic at the regex level (no app needed):
// node repro.js — the exact regex construction from id-prefix-plugin.tsconstescapeRegexSpecialChars=(s)=>s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');constexternals=['@angular/common','@angular/common/http','@angular/core','@angular/router'].sort();constescaped=externals.map((e)=>escapeRegexSpecialChars(e)+'(?:/.+)?');constreg=newRegExp('/@id/('+escaped.join('|')+')','g');// one line, as in any minified bundleconstcode='import{a}from"/@id/@angular/common/http";import{b}from"/@id/@angular/router";import{c}from"/@id/@angular/core";';console.log(code.replace(reg,(_,name)=>name));// Actual: import{a}from"@angular/common/http";import{b}from"/@id/@angular/router";import{c}from"/@id/@angular/core";// Expected: import{a}from"@angular/common/http";import{b}from"@angular/router";import{c}from"@angular/core";
Steps to observe it in an app:
ng new repro-app --minimal --skip-git && cd repro-app
In angular.json, add to projects.repro-app.architect.build.options:
Actual: unstripped specifiers such as "/@id/@angular/router" are listed, and the browser
console shows the module-loading error below. Expected: no /@id/ occurrences — all externals restored to bare specifiers (as happens
when serving without optimization, step 4 with the default configuration).
Exception or Error
Error: Unsupported Content-Type "text/html" loading http://localhost:4200/@id/@angular/router
imported from http://localhost:4200/chunk-XXXXXXXX.js.
Modules must be served with a valid MIME type like application/javascript.
Not browser specific (the served JS is already wrong on the wire); observed on macOS, but the
defect is platform-independent.
The regex is unchanged in @angular/build 21.2.0, 21.2.18, 22.0.5, 22.1.0-next.2, and
current main.
vite's @id prefix isn't removed everywhere #30642 reported the same symptom and was closed as not reproducible — presumably because a
default (unoptimized) ng serve never triggers it; optimization must be enabled.
Root-cause analysis, suggested one-line fix (verified; PR with regression spec prepared), and
a patch-package workaround: see attached additional-info.md.
Command
serve
Is this a regression?
The previous version in which this bug was not present was
No response
Description
When the dev server serves an optimized/minified build (
ng servewith a configurationwhere
optimizationis enabled) and the build hasexternalDependencies, most external importsreach the browser with Vite's internal
/@id/prefix still attached instead of being restoredto bare specifiers.
Vite's import-analysis rewrites external bare imports to
<base>@id/<pkg>. Theangular-plugin-remove-id-prefixtransform (id-prefix-plugin.ts)is supposed to strip that prefix, but its regex appends a greedy
(?:/.+)?to each externalname. On minified (single-line) output, the first match on an external with a deep import path
(e.g.
@angular/common/http) consumes the remainder of the line into the capture group, soevery later
/@id/occurrence on that line is never matched and is served unstripped. Thebrowser then requests e.g.
http://localhost:4200/@id/@angular/router, receives theindex.htmlfallback, and module loading fails.Serving an unoptimized build of the same app works, because esbuild emits one import per line
and the greedy match is clipped by the newline.
A detailed root-cause analysis, a suggested one-line fix (verified end-to-end; PR prepared with
a regression spec), and a patch-package workaround are in the attached additional-info.md.
Minimal Reproduction
The defect is deterministic at the regex level (no app needed):
Steps to observe it in an app:
ng new repro-app --minimal --skip-git && cd repro-appangular.json, add toprojects.repro-app.architect.build.options:app.config.ts:ng serve --configuration productionActual: unstripped specifiers such as
"/@id/@angular/router"are listed, and the browserconsole shows the module-loading error below.
Expected: no
/@id/occurrences — all externals restored to bare specifiers (as happenswhen serving without optimization, step 4 with the default configuration).
Exception or Error
Your Environment
Anything else relevant?
defect is platform-independent.
@angular/build21.2.0, 21.2.18, 22.0.5, 22.1.0-next.2, andcurrent
main.default (unoptimized)
ng servenever triggers it; optimization must be enabled.a patch-package workaround: see attached
additional-info.md.