From 468f684d7d8496fd20066f9839ff2a84c8289849 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 22 Mar 2023 10:10:18 -0500 Subject: [PATCH] fix style attributes in SSR --- packages/ssr/src/cache.rs | 41 ++++++++++++++++++++++++++--- packages/ssr/src/renderer.rs | 50 ++++++++++++++++++++++++++++++++---- packages/ssr/tests/styles.rs | 40 +++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 packages/ssr/tests/styles.rs diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index 8556682f1..e261d3b65 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -17,6 +17,12 @@ pub enum Segment { Attr(usize), Node(usize), PreRendered(String), + /// A marker for where to insert a dynamic styles + StyleMarker { + // If the marker is inside a style tag or not + // This will be true if there are static styles + inside_style_tag: bool, + }, } impl std::fmt::Write for StringChain { @@ -61,16 +67,45 @@ impl StringCache { } => { cur_path.push(root_idx); write!(chain, "<{tag}")?; + // we need to collect the styles and write them at the end + let mut styles = Vec::new(); + let mut has_dynamic_attrs = false; for attr in *attrs { match attr { - TemplateAttribute::Static { name, value, .. } => { - write!(chain, " {name}=\"{value}\"")?; + TemplateAttribute::Static { + name, + value, + namespace, + } => { + if let Some("style") = namespace { + styles.push((name, value)); + } else { + write!(chain, " {name}=\"{value}\"")?; + } } TemplateAttribute::Dynamic { id: index } => { - chain.segments.push(Segment::Attr(*index)) + chain.segments.push(Segment::Attr(*index)); + has_dynamic_attrs = true; } } } + + // write the styles + if !styles.is_empty() { + write!(chain, " style=\"")?; + for (name, value) in styles { + write!(chain, "{name}:{value};")?; + } + chain.segments.push(Segment::StyleMarker { + inside_style_tag: true, + }); + write!(chain, "\"")?; + } else if has_dynamic_attrs { + chain.segments.push(Segment::StyleMarker { + inside_style_tag: false, + }); + } + if children.is_empty() && tag_is_self_closing(tag) { write!(chain, "/>")?; } else { diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 5b6fa8fb2..be4025dba 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -70,15 +70,24 @@ impl Renderer { .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap())) .clone(); + // We need to keep track of the dynamic styles so we can insert them into the right place + let mut accumulated_dynamic_styles = Vec::new(); + for segment in entry.segments.iter() { match segment { Segment::Attr(idx) => { let attr = &template.dynamic_attrs[*idx]; - match attr.value { - AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?, - AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, - _ => {} - }; + if attr.namespace == Some("style") { + accumulated_dynamic_styles.push(attr); + } else { + match attr.value { + AttributeValue::Text(value) => { + write!(buf, " {}=\"{}\"", attr.name, value)? + } + AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, + _ => {} + }; + } } Segment::Node(idx) => match &template.dynamic_nodes[*idx] { DynamicNode::Component(node) => { @@ -128,6 +137,34 @@ impl Renderer { }, Segment::PreRendered(contents) => write!(buf, "{contents}")?, + + Segment::StyleMarker { inside_style_tag } => { + if !accumulated_dynamic_styles.is_empty() { + // if we are inside a style tag, we don't need to write the style attribute + if !*inside_style_tag { + write!(buf, " style=\"")?; + } + for attr in &accumulated_dynamic_styles { + match attr.value { + AttributeValue::Text(value) => { + write!(buf, "{}:{};", attr.name, value)? + } + AttributeValue::Bool(value) => { + write!(buf, "{}:{};", attr.name, value)? + } + AttributeValue::Float(f) => write!(buf, "{}:{};", attr.name, f)?, + AttributeValue::Int(i) => write!(buf, "{}:{};", attr.name, i)?, + _ => {} + }; + } + if !*inside_style_tag { + write!(buf, "\"")?; + } + + // clear the accumulated styles + accumulated_dynamic_styles.clear(); + } + } } } @@ -168,6 +205,9 @@ fn to_string_works() { vec![ PreRendered("
Hello world 1 -->".into(),), Node(0,), PreRendered( diff --git a/packages/ssr/tests/styles.rs b/packages/ssr/tests/styles.rs new file mode 100644 index 000000000..de77e715c --- /dev/null +++ b/packages/ssr/tests/styles.rs @@ -0,0 +1,40 @@ +use dioxus::prelude::*; + +#[test] +fn static_styles() { + fn app(cx: Scope) -> Element { + render! { div { width: "100px" } } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!( + dioxus_ssr::render(&dom), + r#"
"# + ); +} + +#[test] +fn partially_dynamic_styles() { + let dynamic = 123; + + assert_eq!( + dioxus_ssr::render_lazy(rsx! { + div { width: "100px", height: "{dynamic}px" } + }), + r#"
"# + ); +} + +#[test] +fn dynamic_styles() { + let dynamic = 123; + + assert_eq!( + dioxus_ssr::render_lazy(rsx! { + div { width: "{dynamic}px" } + }), + r#"
"# + ); +}