From ab59d0887cf22cc4919c5aa34ee83a41254ca1a7 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:58:51 -0400 Subject: [PATCH] refactor(@angular/build): use ETag headers for development external component styles When using the development server, Angular component stylesheet requests that use hot replacement (default for v19) will now use the `ETag` header to remove the need to reprocess and resend encapsulated component styles if the styles have not changed since last usage. The ETag value used is a combination of the style content and the encapsulation component identifier. --- .../angular/build/src/builders/dev-server/vite-server.ts | 3 ++- .../build/src/tools/vite/middlewares/assets-middleware.ts | 8 ++++++++ packages/angular/build/src/tools/vite/utils.ts | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/angular/build/src/builders/dev-server/vite-server.ts b/packages/angular/build/src/builders/dev-server/vite-server.ts index 7a4acb7a00d1..0977257ad782 100644 --- a/packages/angular/build/src/builders/dev-server/vite-server.ts +++ b/packages/angular/build/src/builders/dev-server/vite-server.ts @@ -42,7 +42,7 @@ import type { DevServerBuilderOutput } from './output'; interface OutputFileRecord { contents: Uint8Array; size: number; - hash?: string; + hash: string; updated: boolean; servable: boolean; type: BuildOutputFileType; @@ -540,6 +540,7 @@ function analyzeResultFiles( contents: file.contents, servable, size: file.contents.byteLength, + hash: file.hash, type: file.type, updated: false, }); 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 a2633c21af27..0fd95baeea30 100644 --- a/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts +++ b/packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts @@ -78,6 +78,13 @@ export function createAngularAssetsMiddleware( // Inject component ID for view encapsulation if requested const componentId = new URL(req.url, 'http://localhost').searchParams.get('ngcomp'); if (componentId !== null) { + const etag = `W/"${outputFile.contents.byteLength}-${outputFile.hash}-${componentId}"`; + if (req.headers['if-none-match'] === etag) { + res.statusCode = 304; + res.end(); + + return; + } // Record the component style usage for HMR updates const usedIds = usedComponentStyles.get(pathname); if (usedIds === undefined) { @@ -98,6 +105,7 @@ export function createAngularAssetsMiddleware( res.setHeader('Content-Type', 'text/css'); res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('ETag', etag); res.end(encapsulatedData); }) .catch((e) => next(e)); diff --git a/packages/angular/build/src/tools/vite/utils.ts b/packages/angular/build/src/tools/vite/utils.ts index 4e922ee1d458..ae2cd59ec693 100644 --- a/packages/angular/build/src/tools/vite/utils.ts +++ b/packages/angular/build/src/tools/vite/utils.ts @@ -9,7 +9,10 @@ import { lookup as lookupMimeType } from 'mrmime'; import { extname } from 'node:path'; -export type AngularMemoryOutputFiles = Map; +export type AngularMemoryOutputFiles = Map< + string, + { contents: Uint8Array; hash: string; servable: boolean } +>; export function pathnameWithoutBasePath(url: string, basePath: string): string { const parsedUrl = new URL(url, 'http://localhost');