Skip to content

fix(vite-plugin-angular): repair CSS and Angular dep resolution regressions#2326

Merged
brandonroberts merged 1 commit intoanalogjs:alphafrom
benpsnyder:fix/regression-css
Apr 29, 2026
Merged

fix(vite-plugin-angular): repair CSS and Angular dep resolution regressions#2326
brandonroberts merged 1 commit intoanalogjs:alphafrom
benpsnyder:fix/regression-css

Conversation

@benpsnyder
Copy link
Copy Markdown
Contributor

@benpsnyder benpsnyder commented Apr 29, 2026

Summary

Fixes two Vite resolution regressions in the Angular plugin paths:

  • Tailwind CSS @plugin imports could resolve package style exports as JavaScript modules.
  • The Angular Compilation API path skipped Analog's dependency optimizer linker, so Vite could serve partially compiled Angular packages during dev.

Both failures were reproduced from snyder-apps using apps/meritos/edge-gui-portal with the local Analog packages rebuilt and linked.

Root Cause

Tailwind CSS plugin resolution

A consumer stylesheet reproduced the resolver failure:

@import 'tailwindcss' prefix(sa);
@plugin 'tailwindcss-primeui';

tailwindcss-primeui@0.6.1 exposes separate package exports:

  • style resolves to v4/index.css
  • import resolves to v3/index.js

Analog injected style into Vite's global resolve.conditions, so Tailwind's JavaScript plugin resolver selected the CSS export. Node then attempted to import the CSS file as ESM and failed with:

[plugin:@tailwindcss/vite:generate:build] Unknown file extension ".css" for .../tailwindcss-primeui/v4/index.css

Angular Compilation API dependency linking

apps/meritos/edge-gui-portal/vite.config.mts uses useAngularCompilationAPI: true. That path disabled Vite's normal app transforms, but it only configured a minimal optimizeDeps include/exclude list and did not install Analog's Angular dep optimizer linker plugin.

As a result, Vite's dev optimizer cached Angular packages in their partially compiled declaration form. The runtime then failed when PlatformLocation still contained ɵɵngDeclareFactory(...) and Angular tried to fall back to JIT without @angular/compiler loaded:

The injectable 'PlatformLocation' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.

What Changed

  • Removed global resolve.conditions: ['style', ...] from the Angular Vite plugin config paths.
  • Kept CSS handling out of global JavaScript package resolution.
  • Reused createDepOptimizerConfig() in the Angular Compilation API plugin path.
  • Included tslib in the shared dep optimizer include list.
  • Added tests asserting:
    • plugin configs do not add style to global resolution;
    • the Compilation API config installs the Angular dep optimizer linker plugin;
    • the shared optimizer include list contains rxjs/operators, rxjs, and tslib.

Before / After

Before this change, the portal build failed while processing apps/meritos/edge-gui-portal/src/app/styles/main.css because tailwindcss-primeui resolved to:

.../tailwindcss-primeui/v4/index.css

After this change, the consumer Vite config resolves JS plugin imports without the style condition:

{
  "conditions": ["module", "browser", "development|production"],
  "primeui": ".../tailwindcss-primeui/v3/index.js"
}

Before the Compilation API fix, the Vite dep cache could contain Angular partial declarations such as ɵɵngDeclareFactory for PlatformLocation.

After this change, forced optimization produces linked Angular deps. The checked cache files for @angular/common, @angular/common/http, @angular/platform-browser, @angular/router, and the generated platform-location chunk no longer contain ngDeclareFactory / ɵɵngDeclareFactory.

Validation

Passed in the Analog repo:

pnpm exec vitest run \
  packages/vite-plugin-angular/src/lib/angular-vite-plugin-live-reload.spec.ts \
  packages/vite-plugin-angular/src/lib/angular-vite-plugin.spec.ts \
  packages/vite-plugin-angular/src/lib/compilation-api/compilation-api-plugin.spec.ts \
  packages/vite-plugin-angular/src/lib/utils/plugin-config.spec.ts \
  --config packages/vite-plugin-angular/vite.config.ts

Result: 4 files / 131 tests passed.

Passed through the local Analog debug flow after rebuild/link refresh:

bun enterpriseOS/tools/debugging/analog-debug-src.ts \
  --scope analog:angular:compilation-api \
  --scope analog:angular:compiler \
  --analog-test-command "cd ../.. && pnpm exec vitest run packages/vite-plugin-angular/src/lib/compilation-api/compilation-api-plugin.spec.ts packages/vite-plugin-angular/src/lib/utils/plugin-config.spec.ts --config packages/vite-plugin-angular/vite.config.ts" \
  --verify-command "node --input-type=module -e \"import { resolveConfig } from 'vite'; const c = await resolveConfig({ configFile: 'apps/meritos/edge-gui-portal/vite.config.mts' }, 'serve', 'development', 'development'); console.log(JSON.stringify({ conditions: c.resolve.conditions, optimizeDeps: c.optimizeDeps }, null, 2));\""

Passed consumer optimization/linker sanity check:

bun x vite optimize apps/meritos/edge-gui-portal \
  --config apps/meritos/edge-gui-portal/vite.config.mts \
  --mode development \
  --force

Then verified no Angular partial declaration helpers remained in the relevant optimized Angular cache files.

Passed the original consumer build:

bun x nx run meritos-edge-gui-portal:build:development --skipNxCache

Result: meritos-edge-gui-portal and its 34 dependent build tasks completed successfully.

Additional check:

bun x nx typecheck meritos-edge-gui-portal --skipNxCache --skipSync

Result: still fails on existing dependent-library Vite config type overload / excessive stack depth errors, unrelated to these Vite plugin regressions.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 29, 2026

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit b199296
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/69f2386d908e99000898e581
😎 Deploy Preview https://deploy-preview-2326--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 29, 2026

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit 62acf77
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/69f1633405eaf500087cec24
😎 Deploy Preview https://deploy-preview-2326--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR centralizes dependency-optimizer configuration by routing dep-optimizer settings through createDepOptimizerConfig (removing per-plugin resolve.conditions and hardcoded optimizeDeps entries), adds tslib to optimizeDeps.include, and removes the custom 'style' resolve condition. It also improves stylesheet mapping and HMR handling: served/hashed stylesheet IDs can be mapped back to original source paths via getServedSourcePath, resolveId rewrites ngcomp stylesheet requests to the original source (preserving query), and registry behavior for inline/registered stylesheets now exposes stable synthetic source paths. Tests were extended to assert the new optimizer config and stylesheet-serving behavior, and workspace/dev deps were bumped.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commit style with the supported scope vite-plugin-angular and clearly describes the main fix: repairing CSS and Angular dependency resolution regressions.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the two resolution regressions, root causes, validation results, and before/after impacts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 29, 2026

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit b199296
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/69f2386d908e99000898e57c
😎 Deploy Preview https://deploy-preview-2326--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added scope:astro-angular Changes in @analogjs/astro-angular scope:ci GitHub workflow changes scope:content Changes in @analogjs/content scope:content-plugin Changes in @analogjs/content-plugin scope:create-analog Changes in create-analog scope:docs Documentation changes scope:nx-plugin Changes in @analogjs/nx-plugin scope:platform Changes in @analogjs/platform scope:repo Repository metadata and tooling labels Apr 29, 2026
@benpsnyder benpsnyder changed the base branch from beta to alpha April 29, 2026 01:47
@github-actions github-actions Bot added scope:router Changes in @analogjs/router scope:storybook-angular Changes in @analogjs/storybook-angular scope:trpc Changes in @analogjs/trpc scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vite-plugin-nitro Changes in @analogjs/vite-plugin-nitro scope:vitest-angular Changes in @analogjs/vitest-angular and removed scope:astro-angular Changes in @analogjs/astro-angular scope:content Changes in @analogjs/content scope:content-plugin Changes in @analogjs/content-plugin scope:create-analog Changes in create-analog scope:nx-plugin Changes in @analogjs/nx-plugin scope:platform Changes in @analogjs/platform scope:router Changes in @analogjs/router scope:storybook-angular Changes in @analogjs/storybook-angular scope:vite-plugin-nitro Changes in @analogjs/vite-plugin-nitro labels Apr 29, 2026
@github-actions github-actions Bot removed scope:vitest-angular Changes in @analogjs/vitest-angular scope:docs Documentation changes scope:ci GitHub workflow changes scope:repo Repository metadata and tooling labels Apr 29, 2026
@benpsnyder benpsnyder changed the title Fix/regression css fix(vite-plugin-angular): keep CSS style condition out of JS resolution Apr 29, 2026
@benpsnyder
Copy link
Copy Markdown
Contributor Author

Investigation notes on where this came from and what it was intended to fix:

The direct regression for the active useAngularCompilationAPI path appears to have been introduced in:

  • 10c466b4 - 2026-04-20 - refactor(vite-plugin-angular): extract compilation API into self-contained plugin

That commit created compilation-api/compilation-api-plugin.ts and copied this resolver config into the new standalone plugin path:

resolve: {
  conditions: ['style', ...(config.resolve?.conditions ?? [])],
}

So for apps using the Angular Compilation API, the style condition began leaking into global Vite module resolution from that refactor. The commit itself was not framed as a bug fix; it was intended to split the useAngularCompilationAPI branch out of the large angular-vite-plugin.ts implementation into a standalone plugin with its own Vite hooks.

The underlying style condition is older. It originally came from:

  • cee43a99 - 2022-11-15 - feat(vite-plugin-angular): support Angular Material custom package.json exports (#141)

That was intended to fix #112, where Angular Material theme/style imports needed custom package export conditions (sass / style) for Vite to resolve those stylesheet package exports.

A later related commit:

  • b79ab7ba - 2025-09-18 - fix(vite-plugin-angular): use configured resolve conditions then defaults (#1882)

kept the style condition but also preserved user/default Vite conditions so packages such as MSW resolved correctly in tests.

Relevant 10-day context:

  • 9bb7a9cf / #2305: fixed NgtscProgram external stylesheet resolution for AOT/Tailwind styleUrls.
  • eef84de7 / #2306: returned empty CSS instead of raw SCSS when Vitest test.css is disabled.
  • 10c466b4: extracted the Compilation API plugin and copied the global style condition into that new path.
  • ae803bb4 / #2311: let CSS ?inline imports flow through Vite's native CSS pipeline for Vitest Browser Mode / test.css behavior.

So the short version is: the problematic behavior was not newly introduced as a targeted fix in the last 10 days. A long-standing Angular Material workaround was copied into the new Compilation API plugin path on 2026-04-20, and that global condition then collided with Tailwind's JS @plugin resolver. In the failing consumer case, tailwindcss-primeui exposes style -> v4/index.css and import -> v3/index.js; because style was global, Tailwind selected the CSS export while trying to import a JS plugin, leading Node to throw Unknown file extension ".css".

@benpsnyder benpsnyder changed the title fix(vite-plugin-angular): keep CSS style condition out of JS resolution fix(vite-plugin-angular): repair CSS and Angular dep resolution regressions Apr 29, 2026
@benpsnyder
Copy link
Copy Markdown
Contributor Author

Follow-up debug finding for the PlatformLocation JIT runtime failure:

The failure was specific to the useAngularCompilationAPI: true path. That plugin config disabled Vite's normal app transforms, but only returned a minimal optimizeDeps block and did not install Analog's Angular dep optimizer linker plugin. In dev, Vite could then cache Angular packages in partial declaration form. I confirmed the failing consumer cache still had PlatformLocation with ɵɵngDeclareFactory(...), which triggers Angular's JIT fallback and fails because @angular/compiler is not loaded.

The second commit reuses createDepOptimizerConfig() from the Compilation API path, so the same esbuild/rolldown Angular linker plugin is installed there too. It also keeps tslib in the shared optimizer include list.

Validation after rebuilding/linking local Analog packages:

  • Analog focused tests: packages/vite-plugin-angular/src/lib/compilation-api/compilation-api-plugin.spec.ts and packages/vite-plugin-angular/src/lib/utils/plugin-config.spec.ts passed.
  • Broader Analog Vite plugin regression set passed: 4 files / 131 tests.
  • Consumer vite optimize --force for apps/meritos/edge-gui-portal completed successfully.
  • Checked optimized Angular cache files for @angular/common, @angular/common/http, @angular/platform-browser, @angular/router, and the generated platform-location chunk; no ngDeclareFactory / ɵɵngDeclareFactory helpers remained.
  • Consumer build passed: bun x nx run meritos-edge-gui-portal:build:development --skipNxCache.

If a running dev server still shows the old PlatformLocation error after this patch, it is likely serving stale optimized deps; restart the dev server or force Vite optimization so the Angular deps are linked again.

@github-actions github-actions Bot added the scope:repo Repository metadata and tooling label Apr 29, 2026
@benpsnyder
Copy link
Copy Markdown
Contributor Author

Follow-up on the builtin:vite-resolve / Option::unwrap() panic:

The remaining failure was not the root Tailwind stylesheet. main.css and main.css?direct resolved correctly. The panic happened on Angular component stylesheet wrapper URLs such as:

/65512024efaa4ff9597bc0a3a90fadfcf21ddeb92dd4049241d6e78639d9ef65.css?ngcomp=ng-c2085252809&e=0

Analog serves Angular component styles through hashed ids so Angular can load ?ngcomp wrappers. Those served styles can also include the injected Tailwind @reference "<root stylesheet>". Tailwind asks Vite to resolve that reference using the current stylesheet id as the importer. When the importer is the hashed virtual id, Vite 8's Rust builtin:vite-resolve path can hit the Option::unwrap() panic.

This works by keeping the public hashed ids, but resolving them back to their real source stylesheet path before Vite/Tailwind transform the CSS. The stylesheet registry now records a served source path for both external styleUrl files and inline styles, and the Angular plugin resolveId hooks map:

/hash.css?ngcomp=...

to:

<component-source>.css?ngcomp=...

Tailwind then resolves @reference from a normal file-system importer, so the Vite resolver no longer panics while Angular keeps the same component CSS wrapper behavior.

Validation:

  • Focused Analog tests passed: stylesheet-registry.spec.ts, compilation-api-plugin.spec.ts, and angular-vite-plugin.spec.ts (111 tests).
  • Rebuilt local Analog packages and refreshed the Snyder app link.
  • Re-ran the exact failing virtual component stylesheet URL in a fresh Vite server. It now returns a normal Vite CSS wrapper containing __vite__updateStyle instead of a 500 from builtin:vite-resolve.

brandonroberts pushed a commit that referenced this pull request Apr 29, 2026
…css imports

The Angular Vite plugin injected the `style` package-export condition
into Vite's *global* `resolve.conditions` (in `angular-vite-plugin`,
`fast-compile-plugin`, `compilation-api-plugin`, and the shared
`createDepOptimizerConfig`). That global injection was needed so JS-side
imports of CSS files whose package exports gate them only under `style`
— e.g. `import '@angular/material/prebuilt-themes/azure-blue.css'` —
could resolve at all.

But globalising `style` had a load-bearing side effect: Tailwind v4's
`@plugin` JS resolver inherits Vite's `resolve.conditions`. With `style`
in scope, Tailwind matched the `style` export of packages with mixed
exports (notably `tailwindcss-primeui`, whose `.` exports include both
`style: "./v4/index.css"` and `import: "./v3/index.js"`) and handed the
resolved `.css` file to Node's ESM loader, which crashed with:

    Internal server error: Unknown file extension ".css" for
      …/tailwindcss-primeui/v4/index.css

Removing the `style` injection outright (as PR #2326 proposes) trades
one regression for another: it breaks `@angular/material/prebuilt-themes`
and any other package that exposes its CSS only under `style`.

This commit scopes the `style` condition to `.css`-extension requests
via a new `cssExtensionStyleResolverPlugin`, registered once at the
`angular()` factory level. The plugin builds its own scoped resolver
(via Vite 8's `vite.createIdResolver` or Vite 7's
`ResolvedConfig.createResolver`) with `style` appended to the user's
configured conditions, and only fires when the requested id is a bare
specifier ending in `.css` (with optional `?inline` / `?module` query).

Net effect:

  - `import '@angular/material/prebuilt-themes/azure-blue.css'`     ✓
  - `@import '@angular/material/prebuilt-themes/azure-blue.css'`    ✓
  - `import '@angular/material/button'` (non-CSS)                   ✓
  - `@plugin 'tailwindcss-primeui'` (Tailwind JS resolver)          ✓

Tests:
  - `cssExtensionStyleResolverPlugin` (new, 6 cases) covers the
    fire/no-fire cases, condition appending, and both Vite version paths
    via a `vi.mock('vite', …)`-backed resolver factory.
  - `createDepOptimizerConfig` test asserts the helper no longer
    returns a global `resolve` block.
@github-actions github-actions Bot removed the scope:repo Repository metadata and tooling label Apr 29, 2026
@brandonroberts brandonroberts merged commit fe6c528 into analogjs:alpha Apr 29, 2026
21 of 23 checks passed
@brandonroberts brandonroberts deleted the fix/regression-css branch April 29, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:trpc Changes in @analogjs/trpc scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants