fix(@angular/build): forward tsconfig paths as Vite aliases for Vitest coverage#33024
fix(@angular/build): forward tsconfig paths as Vite aliases for Vitest coverage#33024tomeelog wants to merge 1 commit intoangular:mainfrom
Conversation
…t coverage When using tsconfig.json 'paths' (e.g. "#/util": ["./src/util"]) with coverage enabled, Vitest's vite:import-analysis plugin fails to resolve path-alias imports from original source files during coverage processing because the Angular CLI's Vitest integration did not expose those aliases to the Vite resolve configuration. The fix reads the tsconfig file, converts every paths entry to a Vite resolve.alias entry (supporting both exact and wildcard patterns), and injects them into the projectDefaults resolve config used by the project workspace. This makes path aliases available during both test execution and coverage instrumentation. Fixes angular#32891
There was a problem hiding this comment.
Code Review
This pull request introduces support for resolving tsconfig path aliases during Vitest unit testing, specifically addressing issues where coverage instrumentation failed to resolve these aliases. It includes logic to parse compilerOptions.paths from the tsconfig file and map them to Vite-compatible resolve aliases. Feedback was provided regarding the robustness of the JSON comment-stripping regex to avoid breaking valid strings and the need to normalize resolved paths to POSIX format for better cross-platform compatibility.
| try { | ||
| const raw = await readFile(tsConfigPath, 'utf-8'); | ||
| // tsconfig files may contain C-style comments – strip them before parsing. | ||
| const json = JSON.parse(raw.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, '')); |
There was a problem hiding this comment.
The current regex for stripping comments is prone to breaking valid JSON if it contains strings with // or /* (for example, URLs like https://angular.io or path patterns). A safer approach is to use a regex that accounts for strings to avoid accidental matches within them.
| const json = JSON.parse(raw.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, '')); | |
| const json = JSON.parse( | |
| raw.replace(/("(?:\\.|[^\\"])*")|\/\*[\s\S]*?\*\/|\/\/.*/g, (m, g1) => g1 ?? ''), | |
| ); |
| const targetDir = path.join(baseDir, target.replace(/\/\*$/, '')); | ||
| return [{ | ||
| find: new RegExp(`^${escapeRegExp(prefix)}\/(.*)$`), | ||
| replacement: `${targetDir}/$1`, | ||
| }]; | ||
| } | ||
| // Exact alias: "#/util" -> "./src/util" | ||
| return [{ find: pattern, replacement: path.join(baseDir, target) }]; |
There was a problem hiding this comment.
To ensure consistent behavior across different operating systems, especially on Windows, it is recommended to normalize the resolved alias paths to POSIX format using toPosixPath. Vite and Vitest generally prefer POSIX-style paths or correctly formatted absolute paths for aliases, and mixing slashes (e.g., C:\path/to/file) can lead to resolution issues.
| const targetDir = path.join(baseDir, target.replace(/\/\*$/, '')); | |
| return [{ | |
| find: new RegExp(`^${escapeRegExp(prefix)}\/(.*)$`), | |
| replacement: `${targetDir}/$1`, | |
| }]; | |
| } | |
| // Exact alias: "#/util" -> "./src/util" | |
| return [{ find: pattern, replacement: path.join(baseDir, target) }]; | |
| const targetDir = toPosixPath(path.join(baseDir, target.replace(/\/\*$/, ''))); | |
| return [{ | |
| find: new RegExp('^' + escapeRegExp(prefix) + '\\/(.*)$'), | |
| replacement: targetDir + '/$1', | |
| }]; | |
| } | |
| // Exact alias: "#/util" -> "./src/util" | |
| return [{ find: pattern, replacement: toPosixPath(path.join(baseDir, target)) }]; |
PR Checklist
Please check to confirm your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
When a project configures
compilerOptions.pathsintsconfig.json(e.g."#/*": ["./src/app/*"]) and runsng testwithcoverage: true, Vitest'svite:import-analysisplugin fails during the coverage phase with:This happens because the Angular CLI's Vitest integration does not forward
tsconfig path aliases to Vite's
resolve.aliasconfiguration. They areresolved during compilation (by esbuild) but not during Vitest's own
Vite-based coverage instrumentation pass.
Issue Number: #32891
What is the new behavior?
The tsconfig file is read at Vitest initialisation time. Its
compilerOptions.pathsentries are converted to Vite-compatibleresolve.aliasobjects (supporting both exact and wildcard patterns) andinjected into the
projectDefaults.resolve.aliasconfig that the AngularCLI passes to the Vitest project workspace.
Path aliases are now resolved correctly during both test execution and
coverage instrumentation.
Does this PR introduce a breaking change?
Other information
The fix is intentionally narrow — it only reads
pathsandbaseUrlfromthe tsconfig, leaving all other resolution behaviour unchanged. Comment
syntax in tsconfig files (C-style
/* */and//) is stripped beforeJSON parsing to handle real-world configs.
A new behavior spec (
vitest-coverage-tsconfig-paths_spec.ts) verifies thefix end-to-end using the builder test harness.