Command
serve
Is this a regression?
The previous version in which this bug was not present was
No response
Description
When serving with sourcemaps enabled, createAngularMemoryPlugin's load hook removes sourcemap URL comments from the served chunk before handing it to Vite:
https://github.com/angular/angular-cli/blob/main/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts#L107
code: mapContents ? code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '') : code,
I believe this is done to strip the chunk's own trailing comment, but the g and m flags make the regex match EVERY line in the file that starts with //# sourceMappingURL= , including lines that are content inside a template literal, not real comments.
The consequence is: any library that embeds bundled code as a string (the classic worker-loader inline-worker pattern, where a webpack-built worker bundle is embedded as a string and instantiated via new Blob(...)). The embedded bundle text ends with its own //# sourceMappingURL=xxx.worker.js.map line. The emitted chunk then looks like:
createInlineWorker(`/******/ (() => { // webpackBootstrap
...thousands of lines of worker code...
//# sourceMappingURL=abc123.worker.js.map`, null);
Note the template literal's closing backtick is on the same line as the embedded comment. The replace deletes that entire line — closing backtick and , null); included — producing an unterminated template literal. Vite's import-analysis then fails to parse the chunk and the dev server responds 500 with:
Failed to parse source for import analysis because the content contains invalid JS syntax.
In the browser, if the chunk is lazy, this surfaces as:
TypeError: Failed to fetch dynamically imported module: https://.../my-component.entry-XXXXXXXX.js
Important to note is that ng build is unaffected because the plugin only runs in the dev server.
Minimal Reproduction
ng new repro --defaults && cd repro
- Add to
src/main.ts (this simulates what a bundled inline-worker dependency looks like after esbuild re-emits it — a string with mixed quotes and \n escapes is printed as a multiline template literal):
export const workerSource =
'console.log("double") + console.log(\'single\');\n' +
'console.log("double") + console.log(\'single\');\n' +
'//# sourceMappingURL=inline-worker.js.map';
console.log(workerSource.length);
ng serve (development configuration, sourceMap enabled — the default)
- Open the app. The served
main.js contains an unterminated template literal and fails to parse; Vite returns 500 with Failed to parse source for import analysis.
You can observe the mechanism standalone: esbuild emits the string above as
var workerSource = `console.log("double") + console.log('single');
console.log("double") + console.log('single');
//# sourceMappingURL=inline-worker.js.map`;
and applying code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '') to it removes the last line including the closing backtick — invalid JS.
Exception or Error
[vite] Internal server error: Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
Plugin: vite:import-analysis
File: .angular/vite-root/<project>/chunk-XXXXXXXX.js
Browser (when the broken chunk is a dependency of a lazy chunk):
TypeError: Failed to fetch dynamically imported module: https://localhost:4200/.../component.entry-XXXXXXXX.js
Your Environment
- Angular CLI / `@angular/build`: 22.0.0 (regex verified still present in 22.0.5 and on `main`)
- Vite: 7.3.2
- Node: 24.x
- OS: Linux
Anything else relevant?
Suggested fix — only strip the chunk's own trailing comment by anchoring the match to the end of the file instead of using g/m:
code: mapContents ? code.replace(/(^|\n)\/\/# sourceMappingURL=[^\r\n]*\s*$/, '$1') : code,
As a workaround we've been running this as a patch-package patch and it fixes the issue. Mid-file occurrences (which are always string content, since esbuild already consumes real sourcemap comments during bundling) are preserved, and Vite still appends its own inline map when serving.
The same greedy pattern also appears in javascript-transformer-worker.ts (data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '')) and may deserve the same anchoring, although we have not observed a failure through that path.
Command
serve
Is this a regression?
The previous version in which this bug was not present was
No response
Description
When serving with sourcemaps enabled,
createAngularMemoryPlugin'sloadhook removes sourcemap URL comments from the served chunk before handing it to Vite:https://github.com/angular/angular-cli/blob/main/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts#L107
I believe this is done to strip the chunk's own trailing comment, but the g and m flags make the regex match EVERY line in the file that starts with
//# sourceMappingURL=, including lines that are content inside a template literal, not real comments.The consequence is: any library that embeds bundled code as a string (the classic
worker-loaderinline-worker pattern, where a webpack-built worker bundle is embedded as a string and instantiated vianew Blob(...)). The embedded bundle text ends with its own//# sourceMappingURL=xxx.worker.js.mapline. The emitted chunk then looks like:Note the template literal's closing backtick is on the same line as the embedded comment. The
replacedeletes that entire line — closing backtick and, null);included — producing an unterminated template literal. Vite's import-analysis then fails to parse the chunk and the dev server responds 500 with:In the browser, if the chunk is lazy, this surfaces as:
Important to note is that
ng buildis unaffected because the plugin only runs in the dev server.Minimal Reproduction
ng new repro --defaults && cd reprosrc/main.ts(this simulates what a bundled inline-worker dependency looks like after esbuild re-emits it — a string with mixed quotes and\nescapes is printed as a multiline template literal):ng serve(development configuration,sourceMapenabled — the default)main.jscontains an unterminated template literal and fails to parse; Vite returns 500 withFailed to parse source for import analysis.You can observe the mechanism standalone:
esbuildemits the string above asand applying
code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '')to it removes the last line including the closing backtick — invalid JS.Exception or Error
Your Environment
Anything else relevant?
Suggested fix — only strip the chunk's own trailing comment by anchoring the match to the end of the file instead of using
g/m:As a workaround we've been running this as a
patch-packagepatch and it fixes the issue. Mid-file occurrences (which are always string content, since esbuild already consumes real sourcemap comments during bundling) are preserved, and Vite still appends its own inline map when serving.The same greedy pattern also appears in
javascript-transformer-worker.ts(data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '')) and may deserve the same anchoring, although we have not observed a failure through that path.