Skip to content

Commit c57c2b6

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 248b4c9 commit c57c2b6

File tree

3 files changed

+68
-3
lines changed

3 files changed

+68
-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: 49 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) => {
@@ -122,6 +123,11 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
122123
// The base of the URL is unused but required to parse the URL.
123124
const pathname = pathnameWithoutBasePath(req.url, server.config.base);
124125
const extension = extname(pathname);
126+
if (!extension) {
127+
next();
128+
129+
return;
130+
}
125131

126132
// Rewrite all build assets to a vite raw fs URL
127133
const assetSourcePath = assets.get(pathname);
@@ -169,6 +175,8 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
169175
// Returning a function, installs middleware after the main transform middleware but
170176
// before the built-in HTML middleware
171177
return () => {
178+
server.middlewares.use(angularHtmlFallbackMiddleware);
179+
172180
function angularSSRMiddleware(
173181
req: Connect.IncomingMessage,
174182
res: ServerResponse,
@@ -180,8 +188,8 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
180188
// Skip if path is not defined.
181189
!url ||
182190
// 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]))
191+
// NOTE: We use a mime type lookup to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
192+
lookupMimeTypeFromRequest(url)
185193
) {
186194
next();
187195

@@ -306,3 +314,42 @@ function pathnameWithoutBasePath(url: string, basePath: string): string {
306314
? pathname.slice(basePath.length - 1)
307315
: pathname;
308316
}
317+
318+
function angularHtmlFallbackMiddleware(
319+
req: Connect.IncomingMessage,
320+
res: ServerResponse,
321+
next: Connect.NextFunction,
322+
): void {
323+
// Similar to how it is handled in vite
324+
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L15C19-L15C45
325+
if (
326+
// Only accept GET or HEAD
327+
(req.method !== 'GET' && req.method !== 'HEAD') ||
328+
// Has file extensions
329+
(req.url && lookupMimeTypeFromRequest(req.url)) ||
330+
// Require Accept: text/html or */*
331+
!(
332+
req.headers.accept === undefined || // equivalent to `Accept: */*`
333+
req.headers.accept === '' || // equivalent to `Accept: */*`
334+
req.headers.accept.includes('text/html') ||
335+
req.headers.accept.includes('*/*')
336+
)
337+
) {
338+
next();
339+
340+
return;
341+
}
342+
343+
req.url = '/index.html';
344+
next();
345+
}
346+
347+
function lookupMimeTypeFromRequest(url: string): string | undefined {
348+
const extension = extname(url.split('?')[0]);
349+
350+
if (extension === '.ico') {
351+
return 'image/x-icon';
352+
}
353+
354+
return extension && lookupMimeType(extension);
355+
}

0 commit comments

Comments
 (0)