diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts
index c80aca3670c1..734637c5aead 100644
--- a/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts
+++ b/packages/angular_devkit/build_angular/src/builders/dev-server/tests/options/serve-path_spec.ts
@@ -18,7 +18,9 @@ describeServeBuilder(
(harness, setupTarget, isViteRun) => {
describe('option: "servePath"', () => {
beforeEach(async () => {
- setupTarget(harness);
+ setupTarget(harness, {
+ assets: ['src/assets'],
+ });
// Application code is not needed for these tests
await harness.writeFile('src/main.ts', 'console.log("foo");');
@@ -96,6 +98,23 @@ describeServeBuilder(
expect(result?.success).toBeTrue();
expect(await response?.text()).toContain('
');
});
+
+ it('serves assets at specified path when option is used', async () => {
+ await harness.writeFile('src/assets/test.txt', 'hello world!');
+
+ harness.useTarget('serve', {
+ ...BASE_OPTIONS,
+ servePath: 'test',
+ });
+
+ const { result, response } = await executeOnceAndFetch(harness, '/test/assets/test.txt', {
+ // fallback processing requires an accept header
+ request: { headers: { accept: 'text/html' } },
+ });
+
+ expect(result?.success).toBeTrue();
+ expect(await response?.text()).toContain('hello world');
+ });
});
},
);
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 13f92d427110..055ccdfbcecb 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
@@ -448,6 +448,7 @@ export async function setupServer(
css: {
devSourcemap: true,
},
+ // Vite will normalize the `base` option by adding a leading and trailing forward slash.
base: serverOptions.servePath,
resolve: {
mainFields: ['es2020', 'browser', 'module', 'main'],
@@ -583,7 +584,7 @@ export async function setupServer(
// Parse the incoming request.
// The base of the URL is unused but required to parse the URL.
- const pathname = pathnameWithoutServePath(req.url, serverOptions);
+ const pathname = pathnameWithoutBasePath(req.url, server.config.base);
const extension = extname(pathname);
// Rewrite all build assets to a vite raw fs URL
@@ -591,7 +592,7 @@ export async function setupServer(
if (assetSourcePath !== undefined) {
// The encoding needs to match what happens in the vite static middleware.
// ref: https://github.com/vitejs/vite/blob/d4f13bd81468961c8c926438e815ab6b1c82735e/packages/vite/src/node/server/middlewares/static.ts#L163
- req.url = `/@fs/${encodeURI(assetSourcePath)}`;
+ req.url = `${server.config.base}@fs/${encodeURI(assetSourcePath)}`;
next();
return;
@@ -689,7 +690,7 @@ export async function setupServer(
// Parse the incoming request.
// The base of the URL is unused but required to parse the URL.
- const pathname = pathnameWithoutServePath(req.url, serverOptions);
+ const pathname = pathnameWithoutBasePath(req.url, server.config.base);
if (pathname === '/' || pathname === `/index.html`) {
const rawHtml = outputFiles.get('/index.html')?.contents;
@@ -801,17 +802,12 @@ async function loadViteClientCode(file: string) {
return contents;
}
-function pathnameWithoutServePath(url: string, serverOptions: NormalizedDevServerOptions): string {
+function pathnameWithoutBasePath(url: string, basePath: string): string {
const parsedUrl = new URL(url, 'http://localhost');
- let pathname = decodeURIComponent(parsedUrl.pathname);
- if (serverOptions.servePath && pathname.startsWith(serverOptions.servePath)) {
- pathname = pathname.slice(serverOptions.servePath.length);
- if (pathname[0] !== '/') {
- pathname = '/' + pathname;
- }
- }
+ const pathname = decodeURIComponent(parsedUrl.pathname);
- return pathname;
+ // slice(basePath.length - 1) to retain the trailing slash
+ return pathname.startsWith(basePath) ? pathname.slice(basePath.length - 1) : pathname;
}
type ViteEsBuildPlugin = NonNullable<