From b3c91b06f46f526bdae10a326ab3db5f80ebe18a Mon Sep 17 00:00:00 2001 From: MeAkib Date: Wed, 28 Oct 2026 18:54:22 +0600 Subject: [PATCH 1/2] fix(@angular/build): quote weak ETags of assets served by the dev server Extensionless, JavaScript, TypeScript and stylesheet assets served by the dev server and the Vitest browser runner sent an unquoted weak ETag such as `W/25a71cd345af2de4`. RFC 9110 requires the entity tag to be quoted (`W/"25a71cd345af2de4"`), which is how the other ETags in the same middleware are already formatted. --- .../tests/behavior/build-assets_spec.ts | 17 +++++++++++++++++ .../tools/vite/middlewares/assets-middleware.ts | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts b/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts index 82ecef04c209..dd99aa4a2ca5 100644 --- a/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts +++ b/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts @@ -41,6 +41,23 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(await response?.text()).toContain(javascriptFileContent); }); + it('serves a project extensionless asset with a valid weak ETag', async () => { + await harness.writeFile('src/extensionless', javascriptFileContent); + + setupTarget(harness, { + assets: ['src/extensionless'], + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, 'extensionless'); + + expect(result?.success).toBeTrue(); + expect(response?.headers.get('ETag')).toMatch(/^W\/"[^"]+"$/); + }); + it('serves a project JavaScript asset unmodified', async () => { await harness.writeFile('src/extra.js', javascriptFileContent); diff --git a/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts b/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts index a1aa19361c5c..4af44ce2dd6b 100644 --- a/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts +++ b/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts @@ -50,7 +50,7 @@ export function createAngularAssetsMiddleware( // This is a workaround to serve extensionless, CSS, JS and TS files without Vite transformations. if (!extension || JS_TS_REGEXP.test(extension) || CSS_PREPROCESSOR_REGEXP.test(extension)) { const contents = readFileSync(asset.source); - const etag = `W/${calculateHash(contents)}`; + const etag = `W/"${calculateHash(contents)}"`; if (checkAndHandleEtag(req, res, etag)) { return; } @@ -238,7 +238,7 @@ export function createBuildAssetsMiddleware( const contents = outputFile.origin === 'memory' ? outputFile.contents : readHandler(outputFile.source); - const etag = `W/${calculateHash(contents)}`; + const etag = `W/"${calculateHash(contents)}"`; if (checkAndHandleEtag(req, res, etag)) { return; } From a0c3ed2dc9dd22f97020b67869d8b375fe34a354 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Wed, 28 Oct 2026 20:01:10 +0600 Subject: [PATCH 2/2] fixup! fix(@angular/build): quote weak ETags of assets served by the dev server --- .../tests/behavior/build-assets_spec.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts b/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts index dd99aa4a2ca5..e5a9684b489f 100644 --- a/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts +++ b/packages/angular/build/src/builders/dev-server/tests/behavior/build-assets_spec.ts @@ -39,22 +39,6 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.text()).toContain(javascriptFileContent); - }); - - it('serves a project extensionless asset with a valid weak ETag', async () => { - await harness.writeFile('src/extensionless', javascriptFileContent); - - setupTarget(harness, { - assets: ['src/extensionless'], - }); - - harness.useTarget('serve', { - ...BASE_OPTIONS, - }); - - const { result, response } = await executeOnceAndFetch(harness, 'extensionless'); - - expect(result?.success).toBeTrue(); expect(response?.headers.get('ETag')).toMatch(/^W\/"[^"]+"$/); });