fix: only emit components that are needed for css modules at the time of emission#214
Merged
mohamedmansour merged 2 commits intomainfrom Apr 10, 2026
Merged
Conversation
…ered components The previous PR (#212) moved <style type="module"> emission from inline (inside each component's light DOM) to a blanket emission in <head> for all inventoried components, and used document.querySelectorAll + manual CSSStyleSheet construction for SPA adoption. This had two problems: 1. Bloated SSR HTML with style definitions for every route-reachable component, including those not rendered on the current page. 2. document.querySelectorAll cannot pierce shadow DOM boundaries, so inline <style type="module"> tags were invisible to the framework. ## What this changes ### SSR: restore inline CSS module emission (handler) emit_css_module() places the <style type="module" specifier="X"> tag inside the component's light DOM on first render: <my-comp><style type="module" specifier="my-comp">CSS</style><template ...> The browser registers the CSS module globally from this tag and automatically adopts it via shadowrootadoptedstylesheets on the declarative shadow root. No JS needed during SSR hydration. ### SSR: emit templates and inventory for rendered components only Previously, body_end expanded rendered_components into a "reachable" set that included components inside unrendered <if>/<for> blocks. This caused inventory to cover components whose <style type="module"> definitions were never emitted (because emit_css_module only fires for actually-rendered components), creating a mismatch. Now body_end emits template IIFEs and builds the inventory from rendered_components only. Components inside unrendered conditional blocks are excluded — they arrive via templateStyles[] + templates[] during SPA partial navigation. This ensures: if a component is in the inventory, both its template IIFE and its CSS module definition are in the document. ### SPA: use import() with { type: "css" } for style adoption (framework) For SPA-created components (no declarative shadow root), the framework uses the browser's CSS module registry directly: import(specifier, { with: { type: "css" } }) This returns the browser-registered CSSStyleSheet — no DOM queries, no manual CSSStyleSheet construction, no application-level cache. The browser's module registry is the cache (O(1) hash map lookup). SSR-hydrated components skip this entirely — the browser already adopted the sheet from shadowrootadoptedstylesheets. ## Files changed - **crates/webui-handler/src/lib.rs:** Restore emit_css_module() inline emission. Emit templates + inventory from rendered_components only (not expanded reachable set). Move inventory meta tag to body_end. Remove collect_reachable_components (dead code). - **packages/webui-framework/src/element/styles.ts:** Replace querySelectorAll + new CSSStyleSheet() + sheetCache with import(specifier, { with: { type: "css" } }) for SPA path. SSR path is a no-op (browser handles adoption). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bed1d85 to
a544e57
Compare
KurtCattiSchmidt
previously approved these changes
Apr 10, 2026
KurtCattiSchmidt
approved these changes
Apr 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The previous PR (#212) moved
<style type="module">emission from inline (inside each component's light DOM) to a blanket emission in for all inventoried components, and useddocument.querySelectorAll+ manual CSSStyleSheet construction for SPA adoption. This had two problems:What this changes
SSR: restore inline CSS module emission (handler)
emit_css_module()places the<style type="module" specifier="X">tag inside the component's light DOM on first render:The browser registers the CSS module globally from this tag and automatically adopts it via shadowrootadoptedstylesheets on the declarative shadow root. No JS needed during SSR hydration.
SSR: emit templates and inventory for rendered components only
Previously, body_end expanded rendered_components into a "reachable" set that included components inside unrendered / blocks. This caused inventory to cover components whose <style type="module"> definitions were never emitted (because emit_css_module only fires for actually-rendered components), creating a mismatch.
Now body_end emits template IIFEs and builds the inventory from rendered_components only. Components inside unrendered conditional blocks are excluded — they arrive via templateStyles[] + templates[] during SPA partial navigation. This ensures: if a component is in the inventory, both its template IIFE and its CSS module definition are in the document.
SPA: use
import() with { type: "css" }for style adoption (framework)For SPA-created components (no declarative shadow root), the framework uses the browser's CSS module registry directly:
This returns the browser-registered CSSStyleSheet — no DOM queries, no manual CSSStyleSheet construction, no application-level cache. The browser's module registry is the cache (O(1) hash map lookup).
SSR-hydrated components skip this entirely — the browser already adopted the sheet from shadowrootadoptedstylesheets.