Skip to content

Commit 955acef

Browse files
committedJan 10, 2025·
fix(@angular/build): trigger browser reload on asset changes with Vite dev server
Ensures the Vite-based development server automatically reloads the browser when asset files are modified. Closes #26141 (cherry picked from commit 87cfefb)
1 parent 8fa682e commit 955acef

File tree

5 files changed

+54
-22
lines changed

5 files changed

+54
-22
lines changed
 

‎packages/angular/build/src/builders/application/build-action.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,10 @@ function* emitOutputResults(
313313
for (const { source, destination } of assetFiles) {
314314
removedAssetFiles.delete(source);
315315

316-
if (changes.modified.has(source)) {
317-
incrementalResult.modified.push(destination);
318-
} else if (!previousAssetsInfo.has(source)) {
316+
if (!previousAssetsInfo.has(source)) {
319317
incrementalResult.added.push(destination);
318+
} else if (changes.modified.has(source)) {
319+
incrementalResult.modified.push(destination);
320320
} else {
321321
continue;
322322
}

‎packages/angular/build/src/builders/dev-server/vite-server.ts

+41-11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ interface OutputFileRecord {
4949
type: BuildOutputFileType;
5050
}
5151

52+
interface OutputAssetRecord {
53+
source: string;
54+
updated: boolean;
55+
}
56+
5257
interface DevServerExternalResultMetadata extends Omit<ExternalResultMetadata, 'explicit'> {
5358
explicitBrowser: string[];
5459
explicitServer: string[];
@@ -168,7 +173,7 @@ export async function* serveWithVite(
168173
let serverUrl: URL | undefined;
169174
let hadError = false;
170175
const generatedFiles = new Map<string, OutputFileRecord>();
171-
const assetFiles = new Map<string, string>();
176+
const assetFiles = new Map<string, OutputAssetRecord>();
172177
const externalMetadata: DevServerExternalResultMetadata = {
173178
implicitBrowser: [],
174179
implicitServer: [],
@@ -229,19 +234,15 @@ export async function* serveWithVite(
229234
assetFiles.clear();
230235
componentStyles.clear();
231236
generatedFiles.clear();
232-
for (const entry of Object.entries(result.files)) {
233-
const [outputPath, file] = entry;
234-
if (file.origin === 'disk') {
235-
assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
236-
continue;
237-
}
238237

238+
for (const [outputPath, file] of Object.entries(result.files)) {
239239
updateResultRecord(
240240
outputPath,
241241
file,
242242
normalizePath,
243243
htmlIndexPath,
244244
generatedFiles,
245+
assetFiles,
245246
componentStyles,
246247
// The initial build will not yet have a server setup
247248
!server,
@@ -265,23 +266,27 @@ export async function* serveWithVite(
265266
generatedFiles.delete(filePath);
266267
assetFiles.delete(filePath);
267268
}
269+
268270
for (const modified of result.modified) {
269271
updateResultRecord(
270272
modified,
271273
result.files[modified],
272274
normalizePath,
273275
htmlIndexPath,
274276
generatedFiles,
277+
assetFiles,
275278
componentStyles,
276279
);
277280
}
281+
278282
for (const added of result.added) {
279283
updateResultRecord(
280284
added,
281285
result.files[added],
282286
normalizePath,
283287
htmlIndexPath,
284288
generatedFiles,
289+
assetFiles,
285290
componentStyles,
286291
);
287292
}
@@ -352,12 +357,16 @@ export async function* serveWithVite(
352357
if (server) {
353358
// Update fs allow list to include any new assets from the build option.
354359
server.config.server.fs.allow = [
355-
...new Set([...server.config.server.fs.allow, ...assetFiles.values()]),
360+
...new Set([
361+
...server.config.server.fs.allow,
362+
...[...assetFiles.values()].map(({ source }) => source),
363+
]),
356364
];
357365

358366
await handleUpdate(
359367
normalizePath,
360368
generatedFiles,
369+
assetFiles,
361370
server,
362371
serverOptions,
363372
context.logger,
@@ -471,15 +480,26 @@ export async function* serveWithVite(
471480
async function handleUpdate(
472481
normalizePath: (id: string) => string,
473482
generatedFiles: Map<string, OutputFileRecord>,
483+
assetFiles: Map<string, OutputAssetRecord>,
474484
server: ViteDevServer,
475485
serverOptions: NormalizedDevServerOptions,
476486
logger: BuilderContext['logger'],
477487
componentStyles: Map<string, ComponentStyleRecord>,
478488
): Promise<void> {
479489
const updatedFiles: string[] = [];
480-
let destroyAngularServerAppCalled = false;
490+
491+
// Invalidate any updated asset
492+
for (const [file, record] of assetFiles) {
493+
if (!record.updated) {
494+
continue;
495+
}
496+
497+
record.updated = false;
498+
updatedFiles.push(file);
499+
}
481500

482501
// Invalidate any updated files
502+
let destroyAngularServerAppCalled = false;
483503
for (const [file, record] of generatedFiles) {
484504
if (!record.updated) {
485505
continue;
@@ -584,10 +604,16 @@ function updateResultRecord(
584604
normalizePath: (id: string) => string,
585605
htmlIndexPath: string,
586606
generatedFiles: Map<string, OutputFileRecord>,
607+
assetFiles: Map<string, OutputAssetRecord>,
587608
componentStyles: Map<string, ComponentStyleRecord>,
588609
initial = false,
589610
): void {
590611
if (file.origin === 'disk') {
612+
assetFiles.set('/' + normalizePath(outputPath), {
613+
source: normalizePath(file.inputPath),
614+
updated: !initial,
615+
});
616+
591617
return;
592618
}
593619

@@ -644,7 +670,7 @@ function updateResultRecord(
644670
export async function setupServer(
645671
serverOptions: NormalizedDevServerOptions,
646672
outputFiles: Map<string, OutputFileRecord>,
647-
assets: Map<string, string>,
673+
assets: Map<string, OutputAssetRecord>,
648674
preserveSymlinks: boolean | undefined,
649675
externalMetadata: DevServerExternalResultMetadata,
650676
ssrMode: ServerSsrMode,
@@ -743,7 +769,11 @@ export async function setupServer(
743769
// The first two are required for Vite to function in prebundling mode (the default) and to load
744770
// the Vite client-side code for browser reloading. These would be available by default but when
745771
// the `allow` option is explicitly configured, they must be included manually.
746-
allow: [cacheDir, join(serverOptions.workspaceRoot, 'node_modules'), ...assets.values()],
772+
allow: [
773+
cacheDir,
774+
join(serverOptions.workspaceRoot, 'node_modules'),
775+
...[...assets.values()].map(({ source }) => source),
776+
],
747777
},
748778
// This is needed when `externalDependencies` is used to prevent Vite load errors.
749779
// NOTE: If Vite adds direct support for externals, this can be removed.

‎packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { lookup as lookupMimeType } from 'mrmime';
1010
import { extname } from 'node:path';
1111
import type { Connect, ViteDevServer } from 'vite';
12-
import { AngularMemoryOutputFiles, pathnameWithoutBasePath } from '../utils';
12+
import { AngularMemoryOutputFiles, AngularOutputAssets, pathnameWithoutBasePath } from '../utils';
1313

1414
export interface ComponentStyleRecord {
1515
rawContent: Uint8Array;
@@ -19,7 +19,7 @@ export interface ComponentStyleRecord {
1919

2020
export function createAngularAssetsMiddleware(
2121
server: ViteDevServer,
22-
assets: Map<string, string>,
22+
assets: AngularOutputAssets,
2323
outputFiles: AngularMemoryOutputFiles,
2424
componentStyles: Map<string, ComponentStyleRecord>,
2525
encapsulateStyle: (style: Uint8Array, componentId: string) => string,
@@ -36,16 +36,16 @@ export function createAngularAssetsMiddleware(
3636
const pathnameHasTrailingSlash = pathname[pathname.length - 1] === '/';
3737

3838
// Rewrite all build assets to a vite raw fs URL
39-
const assetSourcePath = assets.get(pathname);
40-
if (assetSourcePath !== undefined) {
39+
const asset = assets.get(pathname);
40+
if (asset) {
4141
// Workaround to disable Vite transformer middleware.
4242
// See: https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/middlewares/transform.ts#L201 and
4343
// https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/transformRequest.ts#L204-L206
4444
req.headers.accept = 'text/html';
4545

4646
// The encoding needs to match what happens in the vite static middleware.
4747
// ref: https://github.com/vitejs/vite/blob/d4f13bd81468961c8c926438e815ab6b1c82735e/packages/vite/src/node/server/middlewares/static.ts#L163
48-
req.url = `${server.config.base}@fs/${encodeURI(assetSourcePath)}`;
48+
req.url = `${server.config.base}@fs/${encodeURI(asset.source)}`;
4949
next();
5050

5151
return;
@@ -61,7 +61,7 @@ export function createAngularAssetsMiddleware(
6161
assets.get(pathname + '.html');
6262

6363
if (htmlAssetSourcePath) {
64-
req.url = `${server.config.base}@fs/${encodeURI(htmlAssetSourcePath)}`;
64+
req.url = `${server.config.base}@fs/${encodeURI(htmlAssetSourcePath.source)}`;
6565
next();
6666

6767
return;

‎packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
createAngularSsrExternalMiddleware,
1919
createAngularSsrInternalMiddleware,
2020
} from '../middlewares';
21-
import { AngularMemoryOutputFiles } from '../utils';
21+
import { AngularMemoryOutputFiles, AngularOutputAssets } from '../utils';
2222

2323
export enum ServerSsrMode {
2424
/**
@@ -47,7 +47,7 @@ export enum ServerSsrMode {
4747

4848
interface AngularSetupMiddlewaresPluginOptions {
4949
outputFiles: AngularMemoryOutputFiles;
50-
assets: Map<string, string>;
50+
assets: AngularOutputAssets;
5151
extensionMiddleware?: Connect.NextHandleFunction[];
5252
indexHtmlTransformer?: (content: string) => Promise<string>;
5353
componentStyles: Map<string, ComponentStyleRecord>;

‎packages/angular/build/src/tools/vite/utils.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export type AngularMemoryOutputFiles = Map<
1818
{ contents: Uint8Array; hash: string; servable: boolean }
1919
>;
2020

21+
export type AngularOutputAssets = Map<string, { source: string }>;
22+
2123
export function pathnameWithoutBasePath(url: string, basePath: string): string {
2224
const parsedUrl = new URL(url, 'http://localhost');
2325
const pathname = decodeURIComponent(parsedUrl.pathname);

0 commit comments

Comments
 (0)
Please sign in to comment.