Skip to content

fix(@angular/build): respect tsconfig customConditions in unit-test builder#33246

Open
RobbyRabbitman wants to merge 1 commit into
angular:mainfrom
RobbyRabbitman:fix/unit-test-custom-conditions
Open

fix(@angular/build): respect tsconfig customConditions in unit-test builder#33246
RobbyRabbitman wants to merge 1 commit into
angular:mainfrom
RobbyRabbitman:fix/unit-test-custom-conditions

Conversation

@RobbyRabbitman
Copy link
Copy Markdown

@RobbyRabbitman RobbyRabbitman commented May 23, 2026

PR Checklist

Please check to confirm your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

When @angular/build:unit-test is configured with an @angular/build:ng-packagr build target, the synthesized application-builder build never receives the test tsconfig's compilerOptions.customConditions. Module resolution therefore diverges from ng build, which honors customConditions natively through ng-packagr:

  • transformNgPackagrOptions() only forwards stylePreprocessorOptions, assets and inlineStyleLanguage from ng-package.json; ng-packagr's schema has no conditions field, so nothing is carried over.
  • The synthesized ApplicationBuilderInternalOptions is then handed to buildApplicationInternal() without conditions, so esbuild resolves library imports with only the default conditions (module, development/production, plus browser/node).
  • The Vitest runner's resolve.conditions was hardcoded to ['es2015', 'es2020', 'module', (browser)], also ignoring custom conditions.

Concrete impact: monorepo setups that use customConditions (e.g. a source condition) to redirect workspace library imports to local TypeScript sources during development work as expected for ng build, but ng test silently falls back to the published entry points — defeating the purpose of source‑mapped local development and making library tests harder to debug.

Issue Number: N/A

What is the new behavior?

The unit-test builder now reads compilerOptions.customConditions from the test tsconfig (following the extends chain via the TypeScript config parser) and:

  • forwards them as conditions into the synthesized application-builder options, but only when the build target did not already set conditions. This preserves the existing application-builder behavior, including explicit opt‑outs (conditions: []) and user-supplied lists.
  • appends them to Vite's resolve.conditions in the Vitest plugin, so the runner's own resolver matches the build-time resolution.

No schema changes and no public API surface changes; the new behavior is opt‑in via the existing compilerOptions.customConditions tsconfig field (TS 5.0+).

A new integration spec at packages/angular/build/src/builders/unit-test/tests/options/conditions_spec.ts locks the behavior in with two cases:

  1. Positive – with customConditions: ['staging'] in tsconfig.spec.json and a #target imports map that exposes the good source only under the staging condition, the spec resolves to the good source and passes.
  2. Negative regression guard – same target mapping but no customConditions declared; the spec must fall back to the default (bad) target, proving the new behavior is opt‑in and does not inject unrelated conditions.

Does this PR introduce a breaking change?

  • Yes
  • No

The new behavior only activates when compilerOptions.customConditions is present in the test tsconfig — a field most projects do not set today. For @angular/build:application build targets that explicitly set conditions, that value is preserved (the backfill is guarded by conditions === undefined). For ng-packagr targets, this brings ng test into parity with ng build, which is a convergence rather than a divergence.

Other information

Files changed:

  • packages/angular/build/src/builders/unit-test/options.ts — read customConditions from the test tsconfig via ts.parseJsonConfigFileContent (extends‑aware) and expose it on the normalized options.
  • packages/angular/build/src/builders/unit-test/builder.ts — backfill conditions into the application-builder options only when the build target did not set it.
  • packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts — thread customConditions through to the Vitest config plugin.
  • packages/angular/build/src/builders/unit-test/runners/vitest/plugins.ts — append customConditions to Vite's resolve.conditions.
  • packages/angular/build/src/builders/unit-test/tests/options/conditions_spec.ts — new integration spec.

The Karma runner intentionally was not touched: it does not share the Vite resolver code path, and forwarding conditions through the application build it consumes is already covered by the same builder.ts change.](#33246)

@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 23, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@RobbyRabbitman RobbyRabbitman force-pushed the fix/unit-test-custom-conditions branch from 320be5f to 7c4e758 Compare May 23, 2026 11:58
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for customConditions in the unit-test builder, ensuring parity with ng build when using @angular/build:ng-packagr. It adds logic to read compilerOptions.customConditions from the test tsconfig and forwards these conditions to the application build and Vitest runner. Feedback suggests simplifying the tsconfig parsing logic by using the higher-level ts.getParsedCommandLineOfConfigFile API.

Comment on lines +34 to +51
function readCustomConditionsFromTsConfig(tsConfigPath: string): string[] | undefined {
const { config, error } = ts.readConfigFile(tsConfigPath, (p) => ts.sys.readFile(p));
if (error || !config) {
return undefined;
}

const parsed = ts.parseJsonConfigFileContent(
config,
ts.sys,
path.dirname(tsConfigPath),
/* existingOptions */ undefined,
tsConfigPath,
);

const conditions = parsed.options.customConditions;

return Array.isArray(conditions) && conditions.length > 0 ? [...conditions] : undefined;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for reading and parsing the TypeScript configuration can be simplified by using ts.getParsedCommandLineOfConfigFile. This high-level API handles reading the file and resolving the extends chain in a single call, making the code more concise. Additionally, while ignoring parsing errors (available in parsed.errors) is acceptable for this optional feature, logging them could help users debug issues with their tsconfig configuration.

function readCustomConditionsFromTsConfig(tsConfigPath: string): string[] | undefined {
  const parsed = ts.getParsedCommandLineOfConfigFile(tsConfigPath, {}, ts.sys);
  const conditions = parsed?.options.customConditions;

  return Array.isArray(conditions) && conditions.length > 0 ? [...conditions] : undefined;
}
References
  1. When refactoring code that handles configuration properties, ensure that any implicit type validation previously performed is still maintained, especially if the configuration is guaranteed to be type-safe by an upstream process.

…uilder

The unit-test builder synthesizes an application-builder build from the
configured buildTarget. The application builder forwards `conditions` into
esbuild's `build.initialOptions.conditions`, and the Angular compiler plugin
in turn assigns that array onto the in-plugin TypeScript program's
`compilerOptions.customConditions`. When the buildTarget is
`@angular/build:ng-packagr`, the synthesized application options never set
`conditions` (ng-packagr has no such schema field; it honors
`compilerOptions.customConditions` natively at the tsconfig level instead).
As a result both esbuild and the TypeScript program resolve with default
conditions only, diverging from `ng build` and silently breaking monorepo
setups that use `customConditions` to redirect workspace library imports to
local sources during development.

Read `compilerOptions.customConditions` from the test tsconfig (following
the `extends` chain via `ts.parseJsonConfigFileContent`) and:

- forward them as `conditions` to the synthesized application build, but
  only when the buildTarget did not already set `conditions` (preserves
  application-builder behavior including explicit `conditions: []` and
  user-supplied lists), and
- append them to Vite's `resolve.conditions` so the Vitest runner's own
  resolver matches the build-time resolution.

This aligns esbuild, the compiler plugin's TypeScript program, and Vitest
on the same condition set without introducing new options or schema fields;
the new behavior is opt-in via the existing tsconfig field.
@RobbyRabbitman RobbyRabbitman force-pushed the fix/unit-test-custom-conditions branch from 7c4e758 to 34a4902 Compare May 23, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant