From afb258b658f55a1568b8d7c97b617b34f75c1aea Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 3 Jan 2024 15:18:27 -0600 Subject: [PATCH 1/3] Fix elements and attributes mapping for dx translate --- packages/html/Cargo.toml | 1 + packages/html/src/elements.rs | 48 +++++++++++++++++++-- packages/html/src/global_attributes.rs | 46 ++++++++++++++++++++ packages/html/src/lib.rs | 2 + packages/rsx-rosetta/Cargo.toml | 1 + packages/rsx-rosetta/src/lib.rs | 42 +++++++++++++----- packages/rsx-rosetta/tests/h-tags.rs | 33 ++++++++++++++ packages/rsx-rosetta/tests/raw.rs | 21 +++++++++ packages/rsx-rosetta/tests/simple.rs | 4 -- packages/rsx-rosetta/tests/web-component.rs | 21 +++++++++ 10 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 packages/rsx-rosetta/tests/h-tags.rs create mode 100644 packages/rsx-rosetta/tests/raw.rs create mode 100644 packages/rsx-rosetta/tests/web-component.rs diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 5644b440c..d12f7cf52 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -68,3 +68,4 @@ mounted = [ wasm-bind = ["web-sys", "wasm-bindgen"] native-bind = ["tokio"] hot-reload-context = ["dioxus-rsx"] +html-to-rsx = [] diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index a42c01950..03231c0e9 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -79,6 +79,25 @@ macro_rules! impl_attribute_match { }; } +#[cfg(feature = "html-to-rsx")] +macro_rules! impl_html_to_rsx_attribute_match { + ( + $attr:ident $fil:ident $name:literal + ) => { + if $attr == $name { + return Some(stringify!($fil)); + } + }; + + ( + $attr:ident $fil:ident $_:tt + ) => { + if $attr == stringify!($fil) { + return Some(stringify!($fil)); + } + }; +} + macro_rules! impl_element { ( $(#[$attr:meta])* @@ -316,6 +335,30 @@ macro_rules! builder_constructors { } } + #[cfg(feature = "html-to-rsx")] + pub fn map_html_attribute_to_rsx(html: &str) -> Option<&'static str> { + $( + $( + impl_html_to_rsx_attribute_match!( + html $fil $extra + ); + )* + )* + + None + } + + #[cfg(feature = "html-to-rsx")] + pub fn map_html_element_to_rsx(html: &str) -> Option<&'static str> { + $( + if html == stringify!($name) { + return Some(stringify!($name)); + } + )* + + None + } + $( impl_element!( $(#[$attr])* @@ -998,9 +1041,8 @@ builder_constructors! { src: Uri DEFAULT, text: String DEFAULT, - // r#async: Bool, - // r#type: String, // TODO could be an enum - r#type: String "type", + r#async: Bool "async", + r#type: String "type", // TODO could be an enum r#script: String "script", }; diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 02b0b32ed..639026b33 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -33,12 +33,44 @@ macro_rules! trait_method_mapping { }; } +#[cfg(feature = "html-to-rsx")] +macro_rules! html_to_rsx_attribute_mapping { + ( + $matching:ident; + $(#[$attr:meta])* + $name:ident; + ) => { + if $matching == stringify!($name) { + return Some(stringify!($name)); + } + }; + ( + $matching:ident; + $(#[$attr:meta])* + $name:ident: $lit:literal; + ) => { + if $matching == stringify!($lit) { + return Some(stringify!($name)); + } + }; + ( + $matching:ident; + $(#[$attr:meta])* + $name:ident: $lit:literal, $ns:literal; + ) => { + if $matching == stringify!($lit) { + return Some(stringify!($name)); + } + }; +} + macro_rules! trait_methods { ( @base $(#[$trait_attr:meta])* $trait:ident; $fn:ident; + $fn_html_to_rsx:ident; $( $(#[$attr:meta])* $name:ident $(: $($arg:literal),*)*; @@ -62,6 +94,18 @@ macro_rules! trait_methods { )* None } + + #[cfg(feature = "html-to-rsx")] + #[doc = "Converts an HTML attribute to an RSX attribute"] + pub(crate) fn $fn_html_to_rsx(html: &str) -> Option<&'static str> { + $( + html_to_rsx_attribute_mapping! { + html; + $name$(: $($arg),*)*; + } + )* + None + } }; // Rename the incoming ident and apply a custom namespace @@ -79,6 +123,7 @@ trait_methods! { GlobalAttributes; map_global_attributes; + map_html_global_attributes_to_rsx; /// Prevent the default action for this element. /// @@ -1593,6 +1638,7 @@ trait_methods! { @base SvgAttributes; map_svg_attributes; + map_html_svg_attributes_to_rsx; /// Prevent the default action for this element. /// diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 6dd7c42bd..c44777801 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -19,6 +19,8 @@ mod elements; #[cfg(feature = "hot-reload-context")] pub use elements::HtmlCtx; +#[cfg(feature = "html-to-rsx")] +pub use elements::{map_html_attribute_to_rsx, map_html_element_to_rsx}; pub mod events; pub mod geometry; mod global_attributes; diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index ab5d4e882..9cd53b02f 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-autofmt = { workspace = true } dioxus-rsx = { workspace = true } +dioxus-html = { workspace = true, features = ["html-to-rsx"]} html_parser.workspace = true proc-macro2 = "1.0.49" quote = "1.0.23" diff --git a/packages/rsx-rosetta/src/lib.rs b/packages/rsx-rosetta/src/lib.rs index 3c11f3a90..e12c6f021 100644 --- a/packages/rsx-rosetta/src/lib.rs +++ b/packages/rsx-rosetta/src/lib.rs @@ -2,6 +2,7 @@ #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] +use dioxus_html::{map_html_element_to_rsx, map_html_attribute_to_rsx}; use convert_case::{Case, Casing}; use dioxus_rsx::{ BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput, @@ -24,26 +25,47 @@ pub fn rsx_node_from_html(node: &Node) -> Option { match node { Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))), Node::Element(el) => { - let el_name = el.name.to_case(Case::Snake); - let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site())); + let el_name = if let Some(name) = map_html_element_to_rsx(&el.name) { + ElementName::Ident(Ident::new(name, Span::call_site())) + } else { + // if we don't recognize it and it has a dash, we assume it's a web component + if el.name.contains('-') { + ElementName::Custom(LitStr::new(&el.name, Span::call_site())) + } else { + // otherwise, it might be an element that isn't supported yet + ElementName::Ident(Ident::new( + &el.name.to_case(Case::Snake), + Span::call_site(), + )) + } + }; let mut attributes: Vec<_> = el .attributes .iter() .map(|(name, value)| { - let ident = if matches!(name.as_str(), "for" | "async" | "type" | "as") { - Ident::new_raw(name.as_str(), Span::call_site()) + let value = ifmt_from_text(value.as_deref().unwrap_or("false")); + let attr = if let Some(name) = map_html_attribute_to_rsx(name) { + let ident = if let Some(name) = name.strip_prefix("r#") { + Ident::new_raw(name, Span::call_site()) + } else { + Ident::new(name, Span::call_site()) + }; + ElementAttr::AttrText { + value, + name: ident, + } } else { - let new_name = name.to_case(Case::Snake); - Ident::new(new_name.as_str(), Span::call_site()) + // If we don't recognize the attribute, we assume it's a custom attribute + ElementAttr::CustomAttrText { + value, + name: LitStr::new(name, Span::call_site()), + } }; ElementAttrNamed { el_name: el_name.clone(), - attr: ElementAttr::AttrText { - value: ifmt_from_text(value.as_deref().unwrap_or("false")), - name: ident, - }, + attr } }) .collect(); diff --git a/packages/rsx-rosetta/tests/h-tags.rs b/packages/rsx-rosetta/tests/h-tags.rs new file mode 100644 index 000000000..eb179c57f --- /dev/null +++ b/packages/rsx-rosetta/tests/h-tags.rs @@ -0,0 +1,33 @@ +use html_parser::Dom; + +#[test] +fn h_tags_translate() { + let html = r#" +
+

hello world!

+

hello world!

+

hello world!

+

hello world!

+
hello world!
+
hello world!
+
+ "# + .trim(); + + let dom = Dom::parse(html).unwrap(); + + let body = rsx_rosetta::rsx_from_html(&dom); + + let out = dioxus_autofmt::write_block_out(body).unwrap(); + + let expected = r#" + div { + h1 { "hello world!" } + h2 { "hello world!" } + h3 { "hello world!" } + h4 { "hello world!" } + h5 { "hello world!" } + h6 { "hello world!" } + }"#; + pretty_assertions::assert_eq!(&out, &expected); +} diff --git a/packages/rsx-rosetta/tests/raw.rs b/packages/rsx-rosetta/tests/raw.rs new file mode 100644 index 000000000..a3ed1eef8 --- /dev/null +++ b/packages/rsx-rosetta/tests/raw.rs @@ -0,0 +1,21 @@ +use html_parser::Dom; + +#[test] +fn raw_attribute() { + let html = r#" +
+
hello world!
+
+ "# + .trim(); + + let dom = Dom::parse(html).unwrap(); + + let body = rsx_rosetta::rsx_from_html(&dom); + + let out = dioxus_autofmt::write_block_out(body).unwrap(); + + let expected = r#" + div { div { "unrecognizedattribute": "asd", "hello world!" } }"#; + pretty_assertions::assert_eq!(&out, &expected); +} diff --git a/packages/rsx-rosetta/tests/simple.rs b/packages/rsx-rosetta/tests/simple.rs index b8fd095e1..3f3f8230a 100644 --- a/packages/rsx-rosetta/tests/simple.rs +++ b/packages/rsx-rosetta/tests/simple.rs @@ -9,8 +9,6 @@ fn simple_elements() {
hello world!
hello world!
hello world!
-
hello world!
- hello world! "# .trim(); @@ -28,8 +26,6 @@ fn simple_elements() { div { id: "asd", "hello world!" } div { r#for: "asd", "hello world!" } div { r#async: "asd", "hello world!" } - div { large_thing: "asd", "hello world!" } - ai_is_awesome { "hello world!" } }"#; pretty_assertions::assert_eq!(&out, &expected); } diff --git a/packages/rsx-rosetta/tests/web-component.rs b/packages/rsx-rosetta/tests/web-component.rs new file mode 100644 index 000000000..c7309e1b7 --- /dev/null +++ b/packages/rsx-rosetta/tests/web-component.rs @@ -0,0 +1,21 @@ +use html_parser::Dom; + +#[test] +fn web_components_translate() { + let html = r#" +
+ +
+ "# + .trim(); + + let dom = Dom::parse(html).unwrap(); + + let body = rsx_rosetta::rsx_from_html(&dom); + + let out = dioxus_autofmt::write_block_out(body).unwrap(); + + let expected = r#" + div { my-component {} }"#; + pretty_assertions::assert_eq!(&out, &expected); +} From bc557906fe798e0bd1403fb609d7a5e6eb5b62be Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 3 Jan 2024 15:18:37 -0600 Subject: [PATCH 2/3] fix formatting --- packages/fullstack/src/hooks/server_cached.rs | 2 +- packages/html/src/events/mouse.rs | 5 ++++- packages/rsx-rosetta/src/lib.rs | 14 ++++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/fullstack/src/hooks/server_cached.rs b/packages/fullstack/src/hooks/server_cached.rs index 572efd2b7..b3f9113b5 100644 --- a/packages/fullstack/src/hooks/server_cached.rs +++ b/packages/fullstack/src/hooks/server_cached.rs @@ -15,7 +15,7 @@ use serde::{de::DeserializeOwned, Serialize}; /// let state1 = use_state(cx, || server_cached(|| { /// 1234 /// })); -/// +/// /// todo!() /// } /// ``` diff --git a/packages/html/src/events/mouse.rs b/packages/html/src/events/mouse.rs index ea5bf29d3..17128f4e4 100644 --- a/packages/html/src/events/mouse.rs +++ b/packages/html/src/events/mouse.rs @@ -148,7 +148,10 @@ impl_event! { /// ondoubleclick #[inline] -pub fn ondoubleclick<'a, E: crate::EventReturn, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event) -> E + 'a) -> ::dioxus_core::Attribute<'a> { +pub fn ondoubleclick<'a, E: crate::EventReturn, T>( + _cx: &'a ::dioxus_core::ScopeState, + mut _f: impl FnMut(::dioxus_core::Event) -> E + 'a, +) -> ::dioxus_core::Attribute<'a> { ::dioxus_core::Attribute::new( "ondblclick", _cx.listener(move |e: ::dioxus_core::Event| { diff --git a/packages/rsx-rosetta/src/lib.rs b/packages/rsx-rosetta/src/lib.rs index e12c6f021..49b6b6fc7 100644 --- a/packages/rsx-rosetta/src/lib.rs +++ b/packages/rsx-rosetta/src/lib.rs @@ -2,8 +2,8 @@ #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] -use dioxus_html::{map_html_element_to_rsx, map_html_attribute_to_rsx}; use convert_case::{Case, Casing}; +use dioxus_html::{map_html_attribute_to_rsx, map_html_element_to_rsx}; use dioxus_rsx::{ BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput, }; @@ -33,10 +33,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { ElementName::Custom(LitStr::new(&el.name, Span::call_site())) } else { // otherwise, it might be an element that isn't supported yet - ElementName::Ident(Ident::new( - &el.name.to_case(Case::Snake), - Span::call_site(), - )) + ElementName::Ident(Ident::new(&el.name.to_case(Case::Snake), Span::call_site())) } }; @@ -51,10 +48,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { } else { Ident::new(name, Span::call_site()) }; - ElementAttr::AttrText { - value, - name: ident, - } + ElementAttr::AttrText { value, name: ident } } else { // If we don't recognize the attribute, we assume it's a custom attribute ElementAttr::CustomAttrText { @@ -65,7 +59,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { ElementAttrNamed { el_name: el_name.clone(), - attr + attr, } }) .collect(); From 682beeccfd87ba1f6e06fd2d5b17bfcff9797253 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 3 Jan 2024 15:45:19 -0600 Subject: [PATCH 3/3] fallback to global attributes when mapping rsx rosetta html attribute names --- packages/html/src/elements.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 03231c0e9..ca3f4aee7 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -345,6 +345,14 @@ macro_rules! builder_constructors { )* )* + if let Some(name) = crate::map_html_global_attributes_to_rsx(html) { + return Some(name); + } + + if let Some(name) = crate::map_html_svg_attributes_to_rsx(html) { + return Some(name); + } + None }