From d5b5f86612de4b1ace73002a4f9313d2fc167cb0 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Mon, 6 Apr 2026 17:40:48 +0530 Subject: [PATCH] fix(@angular/build): use stable worker filenames during dev server to prevent hash changes on rebuild During `ng serve`, web workers were always bundled with `entryNames: 'worker-[hash]'` regardless of whether the parent build used hashing. Combined with inherited `footer` and `splitting` options from the parent build that could change between rebuilds, this caused the worker filename hash to change on every rebuild even when the worker content was unchanged, breaking debugging sessions. The fix conditionally uses `worker-[hash]` only when the parent build uses hashing (production builds), and `worker-[name]` otherwise (dev server). It also explicitly sets `splitting: false` and `footer: undefined` in the worker build to prevent non-deterministic output that could affect hashing. Closes #30494 --- .../tests/behavior/rebuild-web-workers_spec.ts | 3 ++- .../behavior/web-workers-application_spec.ts | 3 ++- .../tools/esbuild/angular/compiler-plugin.ts | 17 +++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts index 2fdad10f8d8d..886b8daa6b8d 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts @@ -17,9 +17,10 @@ import { /** * A regular expression used to check if a built worker is correctly referenced in application code. + * Matches both hashed (worker-ABCD1234.js) and non-hashed (worker-worker.js) filenames. */ const REFERENCED_WORKER_REGEXP = - /new Worker\(new URL\("worker-[A-Z0-9]{8}\.js", import\.meta\.url\)/; + /new Worker\(new URL\("worker-.+\.js", import\.meta\.url\)/; describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { describe('Behavior: "Rebuilds when Web Worker files change"', () => { diff --git a/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.ts b/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.ts index 135d5ff68165..15716c496714 100644 --- a/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.ts +++ b/packages/angular/build/src/builders/application/tests/behavior/web-workers-application_spec.ts @@ -11,9 +11,10 @@ import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setu /** * A regular expression used to check if a built worker is correctly referenced in application code. + * Matches both hashed (worker-ABCD1234.js) and non-hashed (worker-worker.js) filenames. */ const REFERENCED_WORKER_REGEXP = - /new Worker\(new URL\("worker-[A-Z0-9]{8}\.js", import\.meta\.url\)/; + /new Worker\(new URL\("worker-.+\.js", import\.meta\.url\)/; describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { describe('Behavior: "Bundles web worker files within application code"', () => { diff --git a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts index 1bcb8c40500a..f3e628dd22a2 100644 --- a/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts +++ b/packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts @@ -286,8 +286,9 @@ export function createCompilerPlugin( ); // Return bundled worker file entry name to be used in the built output + // Match both hashed (worker-ABCD1234.js) and non-hashed (worker-.js) patterns const workerCodeFile = workerResult.outputFiles.find((file) => - /^worker-[A-Z0-9]{8}.[cm]?js$/.test(path.basename(file.path)), + /^worker-.+\.[cm]?js$/.test(path.basename(file.path)), ); assert(workerCodeFile, 'Web Worker bundled code file should always be present.'); const workerCodePath = path.relative( @@ -763,6 +764,13 @@ function bundleWebWorker( workerFile: string, ) { try { + // Use content hashing only when the parent build uses hashing in entry names. + // During dev server (ng serve), hashing is disabled and using it for workers + // causes the filename hash to change on every rebuild even when the worker + // content hasn't changed, breaking debugging sessions. + const parentEntryNames = build.initialOptions.entryNames ?? ''; + const useHashing = parentEntryNames.includes('[hash]'); + return build.esbuild.buildSync({ ...build.initialOptions, platform: 'browser', @@ -770,13 +778,18 @@ function bundleWebWorker( bundle: true, metafile: true, format: 'esm', - entryNames: 'worker-[hash]', + entryNames: useHashing ? 'worker-[hash]' : 'worker-[name]', entryPoints: [workerFile], sourcemap: pluginOptions.sourcemap, // Zone.js is not used in Web workers so no need to disable supported: undefined, // Plugins are not supported in sync esbuild calls plugins: undefined, + // Splitting is not needed for workers and can cause non-deterministic output + splitting: false, + // Footer may contain build-specific data (e.g., i18n hashes) that changes + // between rebuilds, causing unnecessary hash changes for workers + footer: undefined, }); } catch (error) { if (error && typeof error === 'object' && 'errors' in error && 'warnings' in error) {