From d68654e6858b00b0e18ea81fee335a0da7512b9b Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:20:15 -0500 Subject: [PATCH 1/2] refactor(@angular/build): optimize Vitest in-memory plugin resolveId Refactors the `resolveId` hook in the `angular:test-in-memory-provider` Vite plugin to improve clarity, efficiency, and cross-platform consistency. Key improvements include: - Consolidating path resolution logic into a single flow. - Ensuring all resolved paths are absolute and consistently POSIX-formatted. - Prioritizing direct test entry point lookups. - Removing redundant checks and assertions. --- .../unit-test/runners/vitest/plugins.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 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 96d3337e0149..05a9f8470253 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 @@ -171,29 +171,33 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins name: 'angular:test-in-memory-provider', enforce: 'pre', resolveId: (id, importer) => { - 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; - } - } - + // Fast path for test entry points. if (testFileToEntryPoint.has(id)) { return id; } - assert(buildResultFiles.size > 0, 'buildResult must be available for resolving.'); - const relativePath = path.relative(workspaceRoot, id); + // Determine the base directory for resolution. + let baseDir: string; + if (importer) { + // If the importer is a test entry point, resolve relative to the workspace root. + // Otherwise, resolve relative to the importer's directory. + baseDir = testFileToEntryPoint.has(importer) ? workspaceRoot : path.dirname(importer); + } else { + // If there's no importer, assume the id is relative to the workspace root. + baseDir = workspaceRoot; + } + + // Construct the full, absolute path and normalize it to POSIX format. + const fullPath = toPosixPath(path.join(baseDir, id)); + + // Check if the resolved path corresponds to a known build artifact. + const relativePath = path.relative(workspaceRoot, fullPath); if (buildResultFiles.has(toPosixPath(relativePath))) { - return id; + return fullPath; } + + // If the module cannot be resolved from the build artifacts, let other plugins handle it. + return undefined; }, load: async (id) => { assert(buildResultFiles.size > 0, 'buildResult must be available for in-memory loading.'); From beaa8e33fb87982d1da3b58faa3c1c841028a36d Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:35:34 -0500 Subject: [PATCH 2/2] refactor(@angular/build): optimize vitest in-memory file loading This commit optimizes the 'angular:test-in-memory-provider' Vitest plugin by introducing a 'loadResultFile' helper. This helper utilizes 'TextDecoder' to decode memory files, avoiding unnecessary buffer copies associated with 'Buffer.from().toString()'. It also removes duplicate file reading logic for source maps. --- .../unit-test/runners/vitest/plugins.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 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 05a9f8470253..d614d16f97b8 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 @@ -163,6 +163,14 @@ export async function createVitestConfigPlugin( }; } +async function loadResultFile(file: ResultFile): Promise { + if (file.origin === 'memory') { + return new TextDecoder('utf-8').decode(file.contents); + } + + return readFile(file.inputPath, 'utf-8'); +} + export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins { const { workspaceRoot, buildResultFiles, testFileToEntryPoint } = pluginOptions; @@ -221,17 +229,10 @@ export function createVitestPlugins(pluginOptions: PluginOptions): VitestPlugins const outputFile = buildResultFiles.get(outputPath); if (outputFile) { + const code = await loadResultFile(outputFile); const sourceMapPath = outputPath + '.map'; const sourceMapFile = buildResultFiles.get(sourceMapPath); - const code = - outputFile.origin === 'memory' - ? Buffer.from(outputFile.contents).toString('utf-8') - : await readFile(outputFile.inputPath, 'utf-8'); - const sourceMapText = sourceMapFile - ? sourceMapFile.origin === 'memory' - ? Buffer.from(sourceMapFile.contents).toString('utf-8') - : await readFile(sourceMapFile.inputPath, 'utf-8') - : undefined; + 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,