Merge pull request #1324 from ealmloff/bool-attrs-ssr

Fix boolean attribute rendering in SSR
This commit is contained in:
Jonathan Kelley 2023-09-16 12:01:52 -07:00 committed by GitHub
commit 9c300ff266
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 26 deletions

View file

@ -88,15 +88,18 @@ pub trait HistoryProvider<R: Routable> {
/// # use dioxus::prelude::*;
/// # #[component]
/// # fn Index(cx: Scope) -> Element { todo!() }
/// # fn Other(cx: Scope) -> Element { todo!() }
/// #[derive(Clone, Routable, Debug, PartialEq)]
/// enum Route {
/// #[route("/")]
/// Index {},
/// #[route("/other")]
/// Other {},
/// }
/// let mut history = MemoryHistory::<Route>::default();
/// assert_eq!(history.can_go_back(), false);
///
/// history.push(Route::Index {});
/// history.push(Route::Other {});
/// assert_eq!(history.can_go_back(), true);
/// ```
#[must_use]

View file

@ -35,7 +35,7 @@ use crate::utils::use_router_internal::use_router_internal;
///
/// #[component]
/// fn Index(cx: Scope) -> Element {
/// let path = use_route(&cx).unwrap();
/// let path: Route = use_route(&cx).unwrap();
/// render! {
/// h2 { "Current Path" }
/// p { "{path}" }

View file

@ -226,9 +226,9 @@ pub trait Routable: FromStr + Display + Clone + 'static {
///
/// #[derive(Routable, Clone, PartialEq, Debug)]
/// enum Route {
/// #[route("/")]
/// #[route("/home")]
/// Home {},
/// #[route("/about")]
/// #[route("/home/about")]
/// About {},
/// }
///

View file

@ -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<Segment>,
@ -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}\"")?;
}

View file

@ -1,5 +1,7 @@
use super::cache::Segment;
use crate::cache::StringCache;
use dioxus_core::Attribute;
use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn};
use std::collections::HashMap;
use std::fmt::Write;
@ -83,18 +85,13 @@ 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_attribute(buf, attr)?;
}
}
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,81 @@ fn to_string_works() {
assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --&gt;123&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>&lt;/diiiiiiiiv&gt;<div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
}
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_attribute(buf: &mut impl Write, attr: &Attribute) -> std::fmt::Result {
let name = &attr.name;
match attr.value {
AttributeValue::Text(value) => write!(buf, " {name}=\"{value}\""),
AttributeValue::Bool(value) => write!(buf, " {name}={value}"),
AttributeValue::Int(value) => write!(buf, " {name}={value}"),
AttributeValue::Float(value) => write!(buf, " {name}={value}"),
_ => Ok(()),
}
}
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(()),
}
}

View file

@ -0,0 +1,37 @@
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#"<div></div><div hidden="true"></div>"#
);
}
#[test]
fn dynamic_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#"<div></div><div hidden=true></div>"#
);
}