From 71529b1a08a9f1bf6db6d0ad0ad2230d8f0d6c2c Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:55:00 -0500 Subject: [PATCH 1/3] refactor(@angular/build): extract sourcemap adjustment logic in Vitest plugin This commit refactors the sourcemap source adjustment logic within the Vitest plugin into a standalone helper function, `adjustSourcemapSources`. This improves the readability of the `load` hook and isolates the logic for verifying and updating sourcemap paths. Additionally, the `map` parameter is now typed using `ExistingRawSourceMap` from `rolldown` instead of `any`. --- .../unit-test/runners/vitest/plugins.ts | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index 6e621ecc588b..afe4589134e5 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -11,6 +11,7 @@ import { readFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { platform } from 'node:os'; import path from 'node:path'; +import type { ExistingRawSourceMap } from 'rolldown'; import type { BrowserConfigOptions, InlineConfig, @@ -286,26 +287,9 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins const sourceMapFile = buildResultFiles.get(sourceMapPath); const sourceMapText = sourceMapFile ? await loadResultFile(sourceMapFile) : undefined; - // Vitest will include files in the coverage report if the sourcemap contains no sources. - // For builder-internal generated code chunks, which are typically helper functions, - // a virtual source is added to the sourcemap to prevent them from being incorrectly - // included in the final coverage report. const map = sourceMapText ? JSON.parse(sourceMapText) : undefined; if (map) { - if (!map.sources?.length && !map.sourcesContent?.length && !map.mappings) { - map.sources = ['virtual:builder']; - } else if (!vitestConfig.coverage.enabled && Array.isArray(map.sources)) { - map.sources = (map.sources as string[]).map((source) => { - if (source.startsWith('angular:')) { - return source; - } - - // source is relative to the workspace root because the output file is at the root of the output. - const absoluteSource = path.join(workspaceRoot, source); - - return toPosixPath(path.relative(path.dirname(id), absoluteSource)); - }); - } + adjustSourcemapSources(map, !vitestConfig.coverage.enabled, workspaceRoot, id); } return { @@ -338,6 +322,40 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins ]; } +/** + * Adjusts the sources field in a sourcemap to ensure correct source mapping and coverage reporting. + * + * @param map The raw sourcemap to adjust. + * @param rebaseSources Whether to rebase the source paths relative to the test file. + * @param workspaceRoot The root directory of the workspace. + * @param id The ID (path) of the file being loaded. + */ +function adjustSourcemapSources( + map: ExistingRawSourceMap, + rebaseSources: boolean, + workspaceRoot: string, + id: string, +): void { + if (!map.sources?.length && !map.sourcesContent?.length && !map.mappings) { + // Vitest will include files in the coverage report if the sourcemap contains no sources. + // For builder-internal generated code chunks, which are typically helper functions, + // a virtual source is added to the sourcemap to prevent them from being incorrectly + // included in the final coverage report. + map.sources = ['virtual:builder']; + } else if (rebaseSources && map.sources) { + map.sources = map.sources.map((source) => { + if (!source || source.startsWith('angular:')) { + return source; + } + + // source is relative to the workspace root because the output file is at the root of the output. + const absoluteSource = path.join(workspaceRoot, source); + + return toPosixPath(path.relative(path.dirname(id), absoluteSource)); + }); + } +} + function createSourcemapSupportPlugin(): VitestPlugins[0] { return { name: 'angular:source-map-support', From 7c0b0f1f7131b9f43002d1c6ea6c834ffee86bc3 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:21:25 -0500 Subject: [PATCH 2/3] refactor(@angular/build): optimize Vitest plugin file loading and improve safety This commit improves the performance of the Vitest plugin by reusing a single `TextDecoder` instance for decoding memory files, avoiding repeated instantiation. It also adds safety checks when accessing the resolved Vitest configuration to prevent potential errors if the configuration is not yet fully initialized. --- .../build/src/builders/unit-test/runners/vitest/plugins.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index afe4589134e5..390c626afda9 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -179,9 +179,10 @@ export async function createVitestConfigPlugin( }; } +const textDecoder = new TextDecoder('utf-8'); async function loadResultFile(file: ResultFile): Promise { if (file.origin === 'memory') { - return new TextDecoder('utf-8').decode(file.contents); + return textDecoder.decode(file.contents); } return readFile(file.inputPath, 'utf-8'); @@ -266,7 +267,7 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins if (entryPoint) { outputPath = entryPoint + '.js'; - if (vitestConfig.coverage.enabled) { + if (vitestConfig?.coverage?.enabled) { // To support coverage exclusion of the actual test file, the virtual // test entry point only references the built and bundled intermediate file. // If vitest supported an "excludeOnlyAfterRemap" option, this could be removed completely. @@ -289,7 +290,7 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins const map = sourceMapText ? JSON.parse(sourceMapText) : undefined; if (map) { - adjustSourcemapSources(map, !vitestConfig.coverage.enabled, workspaceRoot, id); + adjustSourcemapSources(map, !vitestConfig?.coverage?.enabled, workspaceRoot, id); } return { From dc6022507f3a9186a72fb258a1eb3afb7d8431fa Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 27 Jan 2026 16:24:49 -0500 Subject: [PATCH 3/3] refactor(@angular/build): remove redundant path resolution logic in Vitest plugin This commit removes a redundant code block in the `resolveId` hook of the Vitest plugin. The logic for resolving relative imports is fully covered by the subsequent generic path resolution strategy, reducing code duplication and complexity. --- .../builders/unit-test/runners/vitest/plugins.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts index 390c626afda9..22c5993eabe2 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts @@ -217,20 +217,6 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins } } - if (importer && (id[0] === '.' || id[0] === '/')) { - let fullPath; - if (testFileToEntryPoint.has(importer)) { - fullPath = toPosixPath(path.join(workspaceRoot, id)); - } else { - fullPath = toPosixPath(path.join(path.dirname(importer), id)); - } - - const relativePath = path.relative(workspaceRoot, fullPath); - if (buildResultFiles.has(toPosixPath(relativePath))) { - return fullPath; - } - } - // Determine the base directory for resolution. let baseDir: string; if (importer) {