From aaded7981fbad2a9e11438fb3d61ec56c80cebc6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 8 Aug 2023 10:56:41 -0700 Subject: [PATCH] fix boolean attribute rendering in SSR --- packages/ssr/src/cache.rs | 6 ++ packages/ssr/src/renderer.rs | 100 +++++++++++++++++++++++++------- packages/ssr/tests/bool_attr.rs | 38 ++++++++++++ 3 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 packages/ssr/tests/bool_attr.rs diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index a67fd5f22..564be68a2 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -1,6 +1,8 @@ use dioxus_core::prelude::*; use std::fmt::Write; +use crate::renderer::{str_truthy, BOOL_ATTRS}; + #[derive(Debug)] pub struct StringCache { pub segments: Vec, @@ -86,6 +88,10 @@ impl StringCache { inner_html = Some(value); } else if let Some("style") = namespace { styles.push((name, value)); + } else if BOOL_ATTRS.contains(&name) { + if str_truthy(value) { + write!(chain, " {name}=\"{value}\"",)?; + } } else { write!(chain, " {name}=\"{value}\"")?; } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index f23e654fb..873d0a036 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -1,5 +1,6 @@ use super::cache::Segment; use crate::cache::StringCache; + use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn}; use std::collections::HashMap; use std::fmt::Write; @@ -83,18 +84,14 @@ impl Renderer { inner_html = Some(attr); } else if attr.namespace == Some("style") { accumulated_dynamic_styles.push(attr); + } else if BOOL_ATTRS.contains(&attr.name) { + if truthy(&attr.value) { + write!(buf, " {}=", attr.name)?; + write_value(buf, &attr.value)?; + } } else { - match attr.value { - AttributeValue::Text(value) => { - write!(buf, " {}=\"{}\"", attr.name, value)? - } - AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, - AttributeValue::Int(value) => write!(buf, " {}={}", attr.name, value)?, - AttributeValue::Float(value) => { - write!(buf, " {}={}", attr.name, value)? - } - _ => {} - }; + write!(buf, " {}=", attr.name)?; + write_value(buf, &attr.value)?; } } Segment::Node(idx) => match &template.dynamic_nodes[*idx] { @@ -153,17 +150,9 @@ impl Renderer { 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)?, - _ => {} - }; + write!(buf, "{}:", attr.name)?; + write_value_unquoted(buf, &attr.value)?; + write!(buf, ";")?; } if !*inside_style_tag { write!(buf, "\"")?; @@ -248,3 +237,70 @@ fn to_string_works() { assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
</diiiiiiiiv>
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); } + +pub(crate) const BOOL_ATTRS: &[&str] = &[ + "allowfullscreen", + "allowpaymentrequest", + "async", + "autofocus", + "autoplay", + "checked", + "controls", + "default", + "defer", + "disabled", + "formnovalidate", + "hidden", + "ismap", + "itemscope", + "loop", + "multiple", + "muted", + "nomodule", + "novalidate", + "open", + "playsinline", + "readonly", + "required", + "reversed", + "selected", + "truespeed", + "webkitdirectory", +]; + +pub(crate) fn str_truthy(value: &str) -> bool { + !value.is_empty() && value != "0" && value.to_lowercase() != "false" +} + +pub(crate) fn truthy(value: &AttributeValue) -> bool { + match value { + AttributeValue::Text(value) => str_truthy(value), + AttributeValue::Bool(value) => *value, + AttributeValue::Int(value) => *value != 0, + AttributeValue::Float(value) => *value != 0.0, + _ => false, + } +} + +pub(crate) fn write_value(buf: &mut impl Write, value: &AttributeValue) -> std::fmt::Result { + match value { + AttributeValue::Text(value) => write!(buf, "\"{}\"", value), + AttributeValue::Bool(value) => write!(buf, "{}", value), + AttributeValue::Int(value) => write!(buf, "{}", value), + AttributeValue::Float(value) => write!(buf, "{}", value), + _ => Ok(()), + } +} + +pub(crate) fn write_value_unquoted( + buf: &mut impl Write, + value: &AttributeValue, +) -> std::fmt::Result { + match value { + AttributeValue::Text(value) => write!(buf, "{}", value), + AttributeValue::Bool(value) => write!(buf, "{}", value), + AttributeValue::Int(value) => write!(buf, "{}", value), + AttributeValue::Float(value) => write!(buf, "{}", value), + _ => Ok(()), + } +} diff --git a/packages/ssr/tests/bool_attr.rs b/packages/ssr/tests/bool_attr.rs new file mode 100644 index 000000000..b7720532d --- /dev/null +++ b/packages/ssr/tests/bool_attr.rs @@ -0,0 +1,38 @@ +use dioxus::prelude::*; + +#[test] +fn static_boolean_attributs() { + fn app(cx: Scope) -> Element { + render! { + div { hidden: "false" } + div { hidden: "true" } + } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!( + dioxus_ssr::render(&dom), + r#"
"# + ); +} + +#[test] +fn dynamic_boolean_attributs() { + fn app(cx: Scope) -> Element { + let inner_html = "
1234
"; + render! { + div { hidden: false } + div { hidden: true } + } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!( + dioxus_ssr::render(&dom), + r#"
"# + ); +}