Skip to content

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

Open
Alexays wants to merge 1 commit 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 1 commit 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.
"@playwright/test": "^1.58.0",
"@types/node": "catalog:",
"oxfmt": "catalog:",
"sass": "^1.93.2",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really need this?

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