fix(vite): rebuild component styles when a transitive preprocessor dependency changes - #425
fix(vite): rebuild component styles when a transitive preprocessor dependency changes#425Alexays wants to merge 2 commits into
Conversation
…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.
02e6a57 to
0850aea
Compare
|
@codex review |
There was a problem hiding this comment.
💡 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".
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.
There was a problem hiding this comment.
💡 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) |
There was a problem hiding this comment.
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 👍 / 👎.
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 thestyleUrlfile 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
resolveResourcesruns eachstyleUrlthrough Vite'spreprocessCSS, which already reports every file the preprocessor loaded inprocessed.deps, but the plugin discarded that field. Transitive deps were therefore never registered inresourceToComponent, sohandleHotUpdatefell through to Vite's default pipeline (which has no module for them either), and theresourceCacheentry for the compiled style was never invalidated.Fix
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.resourceToComponentkeeps 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.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/componentendpoint 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 ofhmr-hot-update.test.tswith a real resolved config so Sass actually runs and reports its deps:angular:component-updatefor both and returns[];ctx.modules).Adds
sassas a devDependency for that test.pnpm test: 202 passed (12 files).oxfmt --checkclean; theoxlint --type-awarefailure one2e/app/vite.config.tsis pre-existing onmain.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.