diff --git a/DESIGN.md b/DESIGN.md index 6d8bd166..52031636 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -331,9 +331,27 @@ pub struct HtmlParser { css_parser: CssParser, condition_parser: ConditionParser, handlebars_parser: HandlebarsParser, + css_strategy: CssStrategy, // Other fields... } ``` + +#### CSS Strategy +```rust +/// Strategy for how component CSS is delivered in rendered output. +pub enum CssStrategy { + /// Emit `` tags (default). + External, + /// Embed CSS content inline in `", css.trim())), }; - let processed = self.process_component_template(&html_content, css_path.as_deref()); + let processed = + self.process_component_template(&html_content, css_injection.as_deref()); self.parse(tag_name, &processed)?; } @@ -972,32 +1002,27 @@ impl HtmlParser { } /// Process component template HTML: wrap in shadow DOM template if needed, - /// inject stylesheet link, and strip runtime-only attributes. - fn process_component_template(&mut self, html: &str, styles: Option<&str>) -> String { + /// inject CSS snippet (link or inline style), and strip runtime-only attributes. + fn process_component_template(&mut self, html: &str, css_snippet: Option<&str>) -> String { let trimmed = html.trim(); let has_template = trimmed.starts_with(" in the template tag + if let Some(snippet) = css_snippet { + // Inject CSS snippet after the first > in the template tag if let Some(pos) = stripped.find('>') { - let mut result = String::with_capacity(stripped.len() + style_path.len() + 50); + let mut result = String::with_capacity(stripped.len() + snippet.len() + 16); result.push_str(&stripped[..=pos]); - result.push_str(&format!( - "", - style_path - )); + result.push_str(snippet); result.push_str(&stripped[pos + 1..]); return result; } } stripped - } else if let Some(style_path) = styles { - format!( - "" - ) + } else if let Some(snippet) = css_snippet { + format!("") } else { format!("") } @@ -2257,4 +2282,58 @@ mod tests { raw.value.contains("") && raw.value.contains("")) ); } + + #[test] + fn test_css_strategy_external_emits_link_tag() { + let mut parser = HtmlParser::new(); + parser + .component_registry_mut() + .register_component("my-card", "

", Some("p { color: red; }")) + .ok(); + parser.parse("index.html", "Hello").ok(); + let records = parser.into_fragment_records(); + let my_card = &records["my-card"].fragments; + let raw_text: String = my_card + .iter() + .filter_map(|f| match &f.fragment { + Some(Fragment::Raw(r)) => Some(r.value.as_str()), + _ => None, + }) + .collect(); + assert!( + raw_text.contains(r#""#), + "Expected external tag in: {}", + raw_text + ); + } + + #[test] + fn test_css_strategy_inline_emits_style_tag() { + let mut parser = HtmlParser::new(); + parser.set_css_strategy(CssStrategy::Inline); + parser + .component_registry_mut() + .register_component("my-card", "

", Some("p { color: red; }")) + .ok(); + parser.parse("index.html", "Hello").ok(); + let records = parser.into_fragment_records(); + let my_card = &records["my-card"].fragments; + let raw_text: String = my_card + .iter() + .filter_map(|f| match &f.fragment { + Some(Fragment::Raw(r)) => Some(r.value.as_str()), + _ => None, + }) + .collect(); + assert!( + raw_text.contains(""), + "Expected inline