Skip to content

Commit de589f3

Browse files
committed
fix(@angular-devkit/build-angular): return 404 for assets that are not found
This commit updates the vite dev-server to return 404 for assets and files that are not found. Closes angular#26917
1 parent 68a1ccd commit de589f3

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,23 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT
5555
expect(result?.success).toBeTrue();
5656
expect(await response?.text()).toContain(javascriptFileContent);
5757
});
58+
59+
it('should return 404 for non existing assets', async () => {
60+
setupTarget(harness, {
61+
assets: ['src/extra.js'],
62+
optimization: {
63+
scripts: true,
64+
},
65+
});
66+
67+
harness.useTarget('serve', {
68+
...BASE_OPTIONS,
69+
});
70+
71+
const { result, response } = await executeOnceAndFetch(harness, 'extra.js');
72+
73+
expect(result?.success).toBeTrue();
74+
expect(await response?.status).toBe(404);
75+
});
5876
});
5977
});

packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ export async function setupServer(
462462
publicDir: false,
463463
esbuild: false,
464464
mode: 'development',
465-
appType: 'spa',
465+
appType: 'mpa',
466466
css: {
467467
devSourcemap: true,
468468
},

packages/angular_devkit/build_angular/src/tools/vite/angular-memory-plugin.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
9090
map: mapContents && Buffer.from(mapContents).toString('utf-8'),
9191
};
9292
},
93+
// eslint-disable-next-line max-lines-per-function
9394
configureServer(server) {
9495
const originalssrTransform = server.ssrTransform;
9596
server.ssrTransform = async (code, map, url, originalCode) => {
@@ -169,6 +170,8 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
169170
// Returning a function, installs middleware after the main transform middleware but
170171
// before the built-in HTML middleware
171172
return () => {
173+
server.middlewares.use(angularHtmlFallbackMiddleware);
174+
172175
function angularSSRMiddleware(
173176
req: Connect.IncomingMessage,
174177
res: ServerResponse,
@@ -180,8 +183,8 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
180183
// Skip if path is not defined.
181184
!url ||
182185
// Skip if path is like a file.
183-
// NOTE: We use a regexp to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
184-
/^\.[a-z]{2,4}$/i.test(extname(url.split('?')[0]))
186+
// NOTE: We use a mime type lookup to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
187+
lookupMimeTypeFromRequest(url)
185188
) {
186189
next();
187190

@@ -307,3 +310,34 @@ function pathnameWithoutBasePath(url: string, basePath: string): string {
307310
? pathname.slice(basePath.length - 1)
308311
: pathname;
309312
}
313+
314+
function angularHtmlFallbackMiddleware(
315+
req: Connect.IncomingMessage,
316+
res: ServerResponse,
317+
next: Connect.NextFunction,
318+
): void {
319+
// Similar to how it is handled in vite
320+
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L15C19-L15C45
321+
if (
322+
(req.method === 'GET' || req.method === 'HEAD') &&
323+
(!req.url || !lookupMimeTypeFromRequest(req.url)) &&
324+
(!req.headers.accept ||
325+
req.headers.accept.includes('text/html') ||
326+
req.headers.accept.includes('text/*') ||
327+
req.headers.accept.includes('*/*'))
328+
) {
329+
req.url = '/index.html';
330+
}
331+
332+
next();
333+
}
334+
335+
function lookupMimeTypeFromRequest(url: string): string | undefined {
336+
const extension = extname(url.split('?')[0]);
337+
338+
if (extension === '.ico') {
339+
return 'image/x-icon';
340+
}
341+
342+
return extension && lookupMimeType(extension);
343+
}

0 commit comments

Comments
 (0)