From 8d5af1d5c78ce9aa996f6ba138b99d0bb5005d46 Mon Sep 17 00:00:00 2001 From: Alan Agius <alanagius@google.com> Date: Wed, 7 Feb 2024 09:12:07 +0000 Subject: [PATCH] fix(@angular-devkit/build-angular): ensure correct `.html` served with Vite dev-server Prior to this commit, the Vite html fallback middleware failed to handle the in-memory assets generated by Angular CLI, resulting in incorrect fallback behavior. For instance, when an `index.html` existed as an asset under a specific path, the generated `index.html` would be served instead. This fix addresses the issue, ensuring that the appropriate `.html` is served when using the Vite dev-server. Closes #27044 --- .../tests/behavior/build-assets_spec.ts | 71 ++++++++++++++++++- .../src/builders/dev-server/vite-server.ts | 3 +- .../src/tools/vite/angular-memory-plugin.ts | 17 +++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts index 01b06d52d2af..23dc7722647f 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/behavior/build-assets_spec.ts @@ -11,7 +11,7 @@ import { executeOnceAndFetch } from '../execute-fetch'; import { describeServeBuilder } from '../jasmine-helpers'; import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup'; -describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => { +describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget, isVite) => { const javascriptFileContent = "import {foo} from 'unresolved'; /* a comment */const foo = `bar`;\n\n\n"; @@ -70,5 +70,74 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT expect(result?.success).toBeTrue(); expect(await response?.status).toBe(404); }); + + it('should return 404 for non existing assets', async () => { + setupTarget(harness, { + assets: ['src/extra.js'], + optimization: { + scripts: true, + }, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, 'extra.js'); + + expect(result?.success).toBeTrue(); + expect(await response?.status).toBe(404); + }); + + it(`should return the asset that matches 'index.html' when path has a trailing '/'`, async () => { + await harness.writeFile( + 'src/login/index.html', + '<html><body><h1>Login page</h1></body><html>', + ); + + setupTarget(harness, { + assets: ['src/login'], + optimization: { + scripts: true, + }, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, 'login/'); + + expect(result?.success).toBeTrue(); + expect(await response?.status).toBe(200); + expect(await response?.text()).toContain('<h1>Login page</h1>'); + }); + + (isVite ? it : xit)( + `should return the asset that matches '.html' when path has no trailing '/'`, + async () => { + await harness.writeFile( + 'src/login/new.html', + '<html><body><h1>Login page</h1></body><html>', + ); + + setupTarget(harness, { + assets: ['src/login'], + optimization: { + scripts: true, + }, + }); + + harness.useTarget('serve', { + ...BASE_OPTIONS, + }); + + const { result, response } = await executeOnceAndFetch(harness, 'login/new'); + + expect(result?.success).toBeTrue(); + expect(await response?.status).toBe(200); + expect(await response?.text()).toContain('<h1>Login page</h1>'); + }, + ); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts index 9951754fb6f2..4cdad6e6da80 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts @@ -461,7 +461,8 @@ export async function setupServer( publicDir: false, esbuild: false, mode: 'development', - appType: 'mpa', + // We use custom as we do not rely on Vite's htmlFallbackMiddleware and indexHtmlMiddleware. + appType: 'custom', css: { devSourcemap: true, }, diff --git a/packages/angular_devkit/build_angular/src/tools/vite/angular-memory-plugin.ts b/packages/angular_devkit/build_angular/src/tools/vite/angular-memory-plugin.ts index 8f9a12d1676b..294c8cd7295b 100644 --- a/packages/angular_devkit/build_angular/src/tools/vite/angular-memory-plugin.ts +++ b/packages/angular_devkit/build_angular/src/tools/vite/angular-memory-plugin.ts @@ -140,6 +140,23 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): return; } + // HTML fallbacking + // This matches what happens in the vite html fallback middleware. + // ref: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L9 + const htmlAssetSourcePath = + pathname[pathname.length - 1] === '/' + ? // Trailing slash check for `index.html`. + assets.get(pathname + 'index.html') + : // Non-trailing slash check for fallback `.html` + assets.get(pathname + '.html'); + + if (htmlAssetSourcePath) { + req.url = `${server.config.base}@fs/${encodeURI(htmlAssetSourcePath)}`; + next(); + + return; + } + // Resource files are handled directly. // Global stylesheets (CSS files) are currently considered resources to workaround // dev server sourcemap issues with stylesheets.