Skip to content

Commit 8d5af1d

Browse files
committed
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
1 parent b59f663 commit 8d5af1d

File tree

3 files changed

+89
-2
lines changed

3 files changed

+89
-2
lines changed

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { executeOnceAndFetch } from '../execute-fetch';
1111
import { describeServeBuilder } from '../jasmine-helpers';
1212
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';
1313

14-
describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => {
14+
describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget, isVite) => {
1515
const javascriptFileContent =
1616
"import {foo} from 'unresolved'; /* a comment */const foo = `bar`;\n\n\n";
1717

@@ -70,5 +70,74 @@ describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupT
7070
expect(result?.success).toBeTrue();
7171
expect(await response?.status).toBe(404);
7272
});
73+
74+
it('should return 404 for non existing assets', async () => {
75+
setupTarget(harness, {
76+
assets: ['src/extra.js'],
77+
optimization: {
78+
scripts: true,
79+
},
80+
});
81+
82+
harness.useTarget('serve', {
83+
...BASE_OPTIONS,
84+
});
85+
86+
const { result, response } = await executeOnceAndFetch(harness, 'extra.js');
87+
88+
expect(result?.success).toBeTrue();
89+
expect(await response?.status).toBe(404);
90+
});
91+
92+
it(`should return the asset that matches 'index.html' when path has a trailing '/'`, async () => {
93+
await harness.writeFile(
94+
'src/login/index.html',
95+
'<html><body><h1>Login page</h1></body><html>',
96+
);
97+
98+
setupTarget(harness, {
99+
assets: ['src/login'],
100+
optimization: {
101+
scripts: true,
102+
},
103+
});
104+
105+
harness.useTarget('serve', {
106+
...BASE_OPTIONS,
107+
});
108+
109+
const { result, response } = await executeOnceAndFetch(harness, 'login/');
110+
111+
expect(result?.success).toBeTrue();
112+
expect(await response?.status).toBe(200);
113+
expect(await response?.text()).toContain('<h1>Login page</h1>');
114+
});
115+
116+
(isVite ? it : xit)(
117+
`should return the asset that matches '.html' when path has no trailing '/'`,
118+
async () => {
119+
await harness.writeFile(
120+
'src/login/new.html',
121+
'<html><body><h1>Login page</h1></body><html>',
122+
);
123+
124+
setupTarget(harness, {
125+
assets: ['src/login'],
126+
optimization: {
127+
scripts: true,
128+
},
129+
});
130+
131+
harness.useTarget('serve', {
132+
...BASE_OPTIONS,
133+
});
134+
135+
const { result, response } = await executeOnceAndFetch(harness, 'login/new');
136+
137+
expect(result?.success).toBeTrue();
138+
expect(await response?.status).toBe(200);
139+
expect(await response?.text()).toContain('<h1>Login page</h1>');
140+
},
141+
);
73142
});
74143
});

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ export async function setupServer(
461461
publicDir: false,
462462
esbuild: false,
463463
mode: 'development',
464-
appType: 'mpa',
464+
// We use custom as we do not rely on Vite's htmlFallbackMiddleware and indexHtmlMiddleware.
465+
appType: 'custom',
465466
css: {
466467
devSourcemap: true,
467468
},

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,23 @@ export function createAngularMemoryPlugin(options: AngularMemoryPluginOptions):
140140
return;
141141
}
142142

143+
// HTML fallbacking
144+
// This matches what happens in the vite html fallback middleware.
145+
// ref: https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L9
146+
const htmlAssetSourcePath =
147+
pathname[pathname.length - 1] === '/'
148+
? // Trailing slash check for `index.html`.
149+
assets.get(pathname + 'index.html')
150+
: // Non-trailing slash check for fallback `.html`
151+
assets.get(pathname + '.html');
152+
153+
if (htmlAssetSourcePath) {
154+
req.url = `${server.config.base}@fs/${encodeURI(htmlAssetSourcePath)}`;
155+
next();
156+
157+
return;
158+
}
159+
143160
// Resource files are handled directly.
144161
// Global stylesheets (CSS files) are currently considered resources to workaround
145162
// dev server sourcemap issues with stylesheets.

0 commit comments

Comments
 (0)