You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: DESIGN.md
+39-11Lines changed: 39 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -933,6 +933,7 @@ pub struct HtmlParser {
933
933
condition_parser:ConditionParser,
934
934
handlebars_parser:HandlebarsParser,
935
935
css_strategy:CssStrategy,
936
+
legal_comments:LegalComments,
936
937
// Other fields...
937
938
}
938
939
```
@@ -957,7 +958,28 @@ pub enum CssStrategy {
957
958
-**Style**: Embeds the full CSS content in `<style>` tags inside the shadow DOM template. Used when all files are needed in-memory.
958
959
- **Module**: Registers each component's CSS as a CSS Module via an [Import Map](https://html.spec.whatwg.org/multipage/webappapis.html#import-maps) entry whose value is a `data:text/css,...` URI. During SSR, the handler emits a `<script type="importmap">{"imports":{"component-name":"data:text/css,..."}}</script>` in each component's light DOM on first render (e.g., `<my-comp><script type="importmap">...</script><template ...>`) and adds `shadowrootadoptedstylesheets="component-name"` to each shadow root `<template>`. When the developer supplies their own `<template>` wrapper (e.g., to attach `@event` handlers), they MUST declare `shadowrootadoptedstylesheets="component-name"` on it — the parser returns `ParserError::MissingAdoptedStylesheets` at build time if the attribute is absent, so adoption can never silently fail. Multi-specifier values (`shadowrootadoptedstylesheets="component-name other-sheet"`) are honored verbatim. Components inside false `<if>` blocks or empty `<for>` loops that were not rendered during SSR get their importmap definitions emitted at `body_end`, so client-side activation can adopt them. CSS bytes are percent-encoded as needed to survive the `data:` URI parser (`%`, `#`, `"`, whitespace, and non-ASCII / control bytes); the importmap JSON object is built via `serde_json` so the specifier and URI value are correctly JSON-escaped. **Requires browser support for [Multiple Import Maps](https://github.com/WICG/import-maps/blob/main/proposals/multiple-import-maps.md) (Chrome 133+)** so each component's importmap can be emitted independently and merged into the document-level resolution table by the browser. When a CSP nonce is configured (via `RenderOptions::with_nonce` / `webui_handler_set_nonce`), the SSR-emitted `<script type="importmap">` tags include `nonce="VALUE"` (in `type`, `nonce` order) so strict `script-src 'nonce-...'` policies allow them, matching the existing nonce treatment of inline `<script>` tags. The browser registers the CSS module globally and shares a single `CSSStyleSheet` across all shadow roots that adopt it. No external CSS files are produced. During SPA partial navigation, definitions for newly needed components are sent in the `templateStyles` array as `<script type="importmap">{"imports":{...}}</script>` strings (without a `nonce` attribute - the router materializes each tag client-side and applies the per-request nonce when appending to `<head>` before executing template scripts). WebUI Framework compiled metadata carries the adopted stylesheet specifier (`sa`) so client-created components can adopt the registered stylesheet on their shadow root.
959
960
960
-
Set at construction time with `HtmlParser::with_options(ParserOptions::try_new(...))`.
/// Strategy for preserving legal comments in generated output.
967
+
pubenumLegalComments {
968
+
/// Strip every HTML and CSS comment.
969
+
None,
970
+
/// Preserve legal CSS comments inline and strip all other comments.
971
+
Inline,
972
+
}
973
+
```
974
+
975
+
The default is `LegalComments::Inline`, which preserves CSS comments that match
976
+
esbuild's legal-comment convention: comments containing `@license` or
977
+
`@preserve`, or comments starting with `/*!` or `//!`. WebUI supports only
978
+
`none` and `inline` modes. HTML comments are always stripped, and bindings or
979
+
directives inside HTML comments never produce fragments or plugin metadata.
980
+
CSS comments are stripped from external component CSS, inline `<style>` content,
981
+
component template CSS, and plugin-captured component templates unless they are
982
+
legal comments and `inline` preservation is active.
961
983
962
984
#### Primary Method
963
985
```rust
@@ -989,7 +1011,7 @@ pub trait ParserPlugin {
989
1011
-**Fragment start**: `start_fragment` runs before each `HtmlParser::parse(...)` call so plugins can reset fragment-local counters
990
1012
-**Attribute loop**: `classify_attribute` decides whether framework-owned attrs are kept, skipped, or skipped-and-counted as bindings
991
1013
-**Element completion**: `finish_element` runs with the final binding count after all attrs are processed; returned bytes are emitted as a `Plugin` fragment
992
-
-**Component registration**: `register_component_template` receives the final processed component template HTML
1014
+
-**Component registration**: `register_component_template` receives the final processed component template HTML after HTML/CSS comment stripping
993
1015
-**Artifact extraction**: `into_artifacts` returns post-parse outputs such as client component templates without `Any` downcasts
994
1016
995
1017
**Selecting parser plugins**
@@ -1043,6 +1065,7 @@ actionable message so stale dev processes can be stopped explicitly.
1043
1065
- Handle attributes and special elements
1044
1066
- Omit closing tags when the HTML parser produces no end tag (void elements, etc.)
1045
1067
- Handle self-closing tags (`/>` syntax) for SVG and other elements
1068
+
- Strip HTML comment nodes before output; comment contents are never parsed for signals, directives, attributes, or plugin metadata
1046
1069
1047
1070
#### Buffer Management
1048
1071
-**Buffer Isolation:** Isolate directive content from parent context
<!--{{tokens}}--> → Signal { value:"tokens", raw:false }
1144
-
<!--{{{tokens}}}--> → Signal { value:"tokens", raw:true }
1145
-
<!--{{tokens.light}}--> → Signal { value:"tokens.light", raw:false }
1146
-
<!--regularcomment--> → Raw (preservedas-is)
1171
+
```css
1172
+
/*{{tokens}}*/ → Signal { value:"tokens", raw:false }
1173
+
/*{{{tokens.light}}}*/ → Signal { value:"tokens.light", raw:true }
1147
1174
```
1148
1175
1149
-
This mechanism is general-purpose (not limited to `tokens`) and enables comment-based placeholders for runtime value injection in HTML files. The existing handlebars parser is reused for expression parsing within comment delimiters.
1176
+
Bare handlebars expressions in CSS are raw text. Dynamic CSS fragments must use
1177
+
the comment wrapper so the CSS parser can distinguish them from invalid CSS.
0 commit comments