Просмотр исходного кода

Merge pull request #904 from Demonthos/fix-style-attributes-ssr

Fix style attributes in SSR
Jon Kelley 2 лет назад
Родитель
Сommit
b09207e75d
3 измененных файлов с 123 добавлено и 8 удалено
  1. 38 3
      packages/ssr/src/cache.rs
  2. 45 5
      packages/ssr/src/renderer.rs
  3. 40 0
      packages/ssr/tests/styles.rs

+ 38 - 3
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 {

+ 45 - 5
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("<div class=\"asdasdasd\" class=\"asdasdasd\"".into(),),
                     Attr(0,),
+                    StyleMarker {
+                        inside_style_tag: false,
+                    },
                     PreRendered(">Hello world 1 --&gt;".into(),),
                     Node(0,),
                     PreRendered(

+ 40 - 0
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#"<div style="width:100px;"></div>"#
+    );
+}
+
+#[test]
+fn partially_dynamic_styles() {
+    let dynamic = 123;
+
+    assert_eq!(
+        dioxus_ssr::render_lazy(rsx! {
+            div { width: "100px", height: "{dynamic}px" }
+        }),
+        r#"<div style="width:100px;height:123px;"></div>"#
+    );
+}
+
+#[test]
+fn dynamic_styles() {
+    let dynamic = 123;
+
+    assert_eq!(
+        dioxus_ssr::render_lazy(rsx! {
+            div { width: "{dynamic}px" }
+        }),
+        r#"<div style="width:123px;"></div>"#
+    );
+}