Skip to content

fix(vite): rebuild component styles when a transitive preprocessor dependency changes - #425

Open
Alexays wants to merge 2 commits into
voidzero-dev:mainfrom
Alexays:fix/transitive-style-dep-hmr
Open

fix(vite): rebuild component styles when a transitive preprocessor dependency changes#425
Alexays wants to merge 2 commits into
voidzero-dev:mainfrom
Alexays:fix/transitive-style-dep-hmr

Conversation

@Alexays

@Alexays Alexays commented Aug 3, 2026

Copy link
Copy Markdown

Problem

Editing a stylesheet that a component style depends on transitively (a Sass partial pulled in through @use / @import / meta.load-css, a Less import, ...) does nothing in dev mode: the compiled component style is cached and no HMR update is dispatched. The stale CSS is served until the styleUrl file itself is touched (or the dev server restarted).

The Angular CLI's builder handles this case (it watches the Sass loadedUrls), so projects migrating to this plugin lose partial watching silently, which is easy to misdiagnose as an application bug. In our app the shared partial is included by a dozen component stylesheets, and edits to it were invisible.

Root cause

resolveResources runs each styleUrl through Vite's preprocessCSS, which already reports every file the preprocessor loaded in processed.deps, but the plugin discarded that field. Transitive deps were therefore never registered in resourceToComponent, so handleHotUpdate fell through to Vite's default pipeline (which has no module for them either), and the resourceCache entry for the compiled style was never invalidated.

Fix

  • Capture processed.deps (normalized) per compiled style, cached alongside the compiled CSS so cache hits re-register them and the transform's prune loop does not drop them.
  • Keep a reverse map from each dep to every style compiled on top of it. This has to be multi-owner: resourceToComponent keeps a single owner per resource, and a shared partial belongs to many components; with a single owner only the last transformed component would receive the update.
  • In handleHotUpdate, when a changed file is a known style dep: drop the compiled-CSS cache of every owning style and dispatch component HMR for each owner (the @ng/component endpoint already re-reads and re-preprocesses styles from disk, so the served update is fresh).

Tests

New style-deps-hmr.test.ts, reusing the harness style of hmr-hot-update.test.ts with a real resolved config so Sass actually runs and reports its deps:

  • a change to a partial shared by two components dispatches angular:component-update for both and returns [];
  • an untracked stylesheet still falls through to Vite (ctx.modules).

Adds sass as a devDependency for that test. pnpm test: 202 passed (12 files). oxfmt --check clean; the oxlint --type-aware failure on e2e/app/vite.config.ts is pre-existing on main.

Also validated end-to-end against a real application (large Angular admin, Sass partial shared by ~10 components): before the patch edits to the partial were never picked up; with it, each edit recompiles the owning styles and hot-updates every owning component within a couple of seconds.

…pendency changes

resolveResources runs each styleUrl through Vite's preprocessCSS but
discarded the deps it reports (Sass partials pulled in through @use,
@import or meta.load-css, Less imports, ...). Editing such a shared
partial therefore never invalidated the compiled style nor dispatched
component HMR: the dev server kept serving the stale CSS until the
styleUrl file itself was touched.

Track the reported deps per compiled style, map each dep back to every
style built on top of it, and on a hot update of a dep invalidate those
styles and dispatch HMR for each owning component. A dep shared by
several components (a design-system partial, typically) updates all of
them, not just the last registered owner.
Comment thread napi/angular-compiler/package.json
@Brooooooklyn
Brooooooklyn force-pushed the fix/transitive-style-dep-hmr branch from 02e6a57 to 0850aea Compare August 10, 2026 14:11
@Brooooooklyn

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0850aeaff7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread napi/angular-compiler/vite-plugin/index.ts Outdated
When a component stylesheet was edited so its `@use`/`@import` list
changed, handleHotUpdate dropped the style's cached dependency list but
nothing re-recorded the fresh processed.deps: the `@ng/component` HMR
endpoint recompiles the CSS without touching the dep maps, and returning
[] skips the component transform. The newly imported partial was
therefore never registered in styleDepOwners, so subsequent edits to it
produced no component update until a full reload or another transform.

Refactor dep registration into registerStyleDeps (idempotent: prunes
stale owner entries and records fresh ones) and use it from both the
initial transform and a new refreshStyleDeps in handleHotUpdate's style
branch, which re-reads and re-preprocesses the edited style before
dispatching HMR.

Adds a regression test that switches a style's Sass `@use` import via HMR
and asserts the newly imported partial then dispatches component updates.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fbb8bb5a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let handled = false
for (const stylePath of styleDepOwners.get(normalizedFile)!) {
resourceCache.delete(stylePath)
styleDepsCache.delete(stylePath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve dependency registration when rebuilding transitive styles

When a tracked partial itself adds or switches a nested @use (rather than the component stylesheet changing), this branch deletes the owning style's cached dependency list, but the HMR endpoint's preprocessing does not call registerStyleDeps; consequently the new nested partial is absent from styleDepOwners, and its next edit falls through without updating the component. Fresh evidence in the current revision is that refreshStyleDeps is invoked only by the later direct-resource branch, so it does not cover changes to transitive partials; refresh or register each owning style's dependencies during this rebuild.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants