Skip to content

Give each .rx module its own stylesheet; find a cold dependency's program - #66

Open
brenelz wants to merge 1 commit into
solidjs:mainfrom
brenelz:fix/rx-module-css
Open

brenelz wants to merge 1 commit into
solidjs:mainfrom
brenelz:fix/rx-module-css

Conversation

@brenelz

@brenelz brenelz commented Nov 14, 2026

Copy link
Copy Markdown

Two dev-time bugs in the Vite front end (compiler/vite.mjs), both found while building a ReactiveScript template on @solidjs/vite-plugin 3.0.0-next.57, Vite 8.3.8 and Vitest 5.1.

  1. Under vite dev, editing a <style> block in a non-entry .rx module never reaches the page: not on the hot update, not after a reload, not in a new tab. Only a server restart shows the new CSS. Production builds are correct.
  2. With Vitest's fsModuleCache on (the default in Vitest 5), the first vitest run after editing a non-entry .rx file fails with …/Guestbook.rx is imported as an .rx dependency, but no program built so far contains it.

Root cause

The front end returned the whole program's stylesheet only from the entry's compile (css: !dep && file === entry ? out.css : ""). The plugin's hotUpdate recompiles CSS only for the file that changed, by its plain path with no ?rx-dep. So the front end built the changed dependency as an entry of its own, and the stylesheet the page actually loads (App.rx?solid-tsrx-css) was never refreshed.

Vitest keys each cached transform on that module's id and source alone. When it serves the entry from the cache, compile never runs for the entry, so builds is empty, and the next dependency request that misses the cache has no program to come from.

What changed

  • The crate's compile_linked records each module's own share of the stylesheet (Compiled::module_css), and app() returns it as modules[path].css. The program-wide css that rxc compile writes is unchanged.
  • compile returns m.css for every module, entry or dependency. Solid's plugin then gives each .rx module its own CSS module, as it does for .tsrx, so a style edit hot-updates. A module shared by two programs now links its stylesheet once instead of once per program.
  • When a ?rx-dep request finds no program in memory, containing() scans the .rx files under the plugin's root for line-start import … from "./x.rx" statements. It then builds the files that reach the module through imports (top-level files first) and uses the first program that holds it. If those programs hold it but don't compile, it reports their diagnostics instead of the "no program" error. The scan runs only on that miss.

Carrying the entry in the dependency id (?rx-dep&entry=…) was the other option. It would give a module shared by two programs two ids and two instances, which breaks the one-module rule that the shared test checks.

Verification

With SOLID_VITE_PLUGIN set to an installed @solidjs/vite-plugin 3.0.0-next.57:

  • node compiler/test/vite.mjs: 16 pass, 0 fail. Two new blocks:
    • styles (dev): a dependency's <style> color, checked after the edit, after a reload and in a new page. It fails on main (after the edit: color rgb(0, 0, 0)).
    • cold dependency: a fresh front end compiles shop/catalog.rx?rx-dep with no entry compiled first, with a commented-out import that would otherwise look like a cycle. The code must match a warm compile, and a broken dependency must report its diagnostics. It fails on main with the "no program built so far" error.
  • node compiler/test/node.mjs and cargo test --release pass.
  • In the template, a Playwright probe of a Counter.rx style edit shows the new color after the edit, after a reload and in a new tab. Before, the color stayed black in all three.
  • In the template, with fsModuleCache on, vitest run, then an edit to Guestbook.rx, then vitest run passes both runs. Before, the second run failed.

The two existing dev blocks now open server.resolvedUrls.local[0]. The hard-coded 127.0.0.1 failed on macOS, where Vite listens on localhost (::1).

Left open

  • spec/11 CP-7 still says the front end returns "on the entry the stylesheet (CSS-4)". The code now follows spec/07 CSS-4's "the module's stylesheet is returned beside its JavaScript". The spec/11 wording is yours to change. Stylesheet order now follows module import order (dependencies before importers) rather than set.order.
  • The Vitest cache is still not sound for .rx. Usage is tracked by bare name across the program, so one module's edit can change another module's compiled code: adding guess products := [] in shop/ui.rx changes catalog.rx's output. A cached catalog.rx can then be stale. The front end can't add to Vitest's cache key. That needs @solidjs/vite-plugin to register a defineCacheKeyGenerator from configureVitest with a program-wide key for .rx modules. Until then, fsModuleCache: false stays the safe setting for .rx projects.
  • Picking the program from the files is a guess when several programs hold a module: the first top-level file in sorted order wins. The scan only covers root, so an entry outside it (a monorepo package) is not found.

🤖 Generated with Claude Code

…gram

Under vite dev, a <style> edit in a non-entry module never reached the page, because the program's whole stylesheet came only on the entry and the plugin recompiles CSS per file. Under Vitest's fsModuleCache, a cached entry left builds empty, so the next changed dependency threw "no program built so far contains it".

This branch has not been deployed

No deployments
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.

1 participant