Skip to content

Commit dae7320

Browse files
committed
fix(@angular/build): serve build assets and styles in vitest
Adds a Vite middleware to the Vitest runner to serve assets and global stylesheets that are part of the build output. Previously, when running component tests with Vitest, any referenced assets (such as images in an `<img>` tag) or component stylesheets would result in a 404 error because the Vite server was not configured to serve them. This change introduces a middleware that intercepts requests for non-script files. It checks against the build's output files and serves the corresponding asset directly from memory or disk. This ensures that the test environment accurately reflects the assets available during a real build, allowing components to render correctly for testing.
1 parent b343c83 commit dae7320

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import assert from 'node:assert';
1010
import { readFile } from 'node:fs/promises';
1111
import path from 'node:path';
1212
import type { VitestPlugin } from 'vitest/node';
13+
import { createBuildAssetsMiddleware } from '../../../../tools/vite/middlewares/assets-middleware';
1314
import { toPosixPath } from '../../../../utils/path';
1415
import type { ResultFile } from '../../../application/results';
1516
import type { NormalizedUnitTestBuilderOptions } from '../../options';
@@ -129,6 +130,11 @@ export function createVitestPlugins(
129130
};
130131
}
131132
},
133+
configureServer: (server) => {
134+
server.middlewares.use(
135+
createBuildAssetsMiddleware(server.config.base, buildResultFiles),
136+
);
137+
},
132138
},
133139
{
134140
name: 'angular:html-index',

packages/angular/build/src/tools/vite/middlewares/assets-middleware.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { readFileSync } from 'node:fs';
1212
import type { ServerResponse } from 'node:http';
1313
import { extname } from 'node:path';
1414
import type { Connect, ViteDevServer } from 'vite';
15+
import { ResultFile } from '../../../builders/application/results';
1516
import { AngularMemoryOutputFiles, AngularOutputAssets, pathnameWithoutBasePath } from '../utils';
1617

1718
export interface ComponentStyleRecord {
@@ -214,3 +215,45 @@ function checkAndHandleEtag(
214215

215216
return false;
216217
}
218+
219+
export function createBuildAssetsMiddleware(
220+
basePath: string,
221+
buildResultFiles: ReadonlyMap<string, ResultFile>,
222+
readHandler: (path: string) => Buffer = readFileSync,
223+
): Connect.NextHandleFunction {
224+
return function buildAssetsMiddleware(req, res, next) {
225+
if (req.url === undefined || res.writableEnded) {
226+
return;
227+
}
228+
229+
// Parse the incoming request.
230+
// The base of the URL is unused but required to parse the URL.
231+
const pathname = pathnameWithoutBasePath(req.url, basePath);
232+
const extension = extname(pathname);
233+
if (extension && !/\.[mc]?[jt]s(?:\.map)?$/.test(extension)) {
234+
const outputFile = buildResultFiles.get(pathname.slice(1));
235+
if (outputFile) {
236+
const contents =
237+
outputFile.origin === 'memory' ? outputFile.contents : readHandler(outputFile.inputPath);
238+
239+
const etag = `W/${createHash('sha256').update(contents).digest('hex')}`;
240+
if (checkAndHandleEtag(req, res, etag)) {
241+
return;
242+
}
243+
244+
const mimeType = lookupMimeType(extension);
245+
if (mimeType) {
246+
res.setHeader('Content-Type', mimeType);
247+
}
248+
249+
res.setHeader('ETag', etag);
250+
res.setHeader('Cache-Control', 'no-cache');
251+
res.end(contents);
252+
253+
return;
254+
}
255+
}
256+
257+
next();
258+
};
259+
}

0 commit comments

Comments
 (0)