From 209a55da694ae306b9216589431c5c62cf9bfc3b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Mar 2023 20:37:40 -0500 Subject: [PATCH 1/3] allow raw elements if they include a dash --- examples/web_component.rs | 15 +++++ packages/rsx-rosetta/src/lib.rs | 4 +- packages/rsx/src/element.rs | 107 ++++++++++++++++++++++++++++---- packages/rsx/src/lib.rs | 23 +++++-- packages/rsx/src/node.rs | 10 +++ 5 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 examples/web_component.rs diff --git a/examples/web_component.rs b/examples/web_component.rs new file mode 100644 index 000000000..7a719b41c --- /dev/null +++ b/examples/web_component.rs @@ -0,0 +1,15 @@ + + +use dioxus::prelude::*; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + web-component { + "my-prop": "5%", + } + }) +} diff --git a/packages/rsx-rosetta/src/lib.rs b/packages/rsx-rosetta/src/lib.rs index 64fed1e18..4f30c0081 100644 --- a/packages/rsx-rosetta/src/lib.rs +++ b/packages/rsx-rosetta/src/lib.rs @@ -1,6 +1,6 @@ use convert_case::{Case, Casing}; use dioxus_rsx::{ - BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, IfmtInput, + BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput, }; pub use html_parser::{Dom, Node}; use proc_macro2::{Ident, Span}; @@ -21,7 +21,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option { 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 = Ident::new(el_name.as_str(), Span::call_site()); + let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site())); let mut attributes: Vec<_> = el .attributes diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index e47c2e02a..5b0c12a98 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -1,9 +1,12 @@ +use std::fmt::{Display, Formatter}; + use super::*; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseBuffer, ParseStream}, + punctuated::Punctuated, Error, Expr, Ident, LitStr, Result, Token, }; @@ -12,7 +15,7 @@ use syn::{ // ======================================= #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub struct Element { - pub name: Ident, + pub name: ElementName, pub key: Option, pub attributes: Vec, pub children: Vec, @@ -22,7 +25,7 @@ pub struct Element { impl Parse for Element { fn parse(stream: ParseStream) -> Result { - let el_name = Ident::parse(stream)?; + let el_name = ElementName::parse(stream)?; // parse the guts let content: ParseBuffer; @@ -181,7 +184,7 @@ impl ToTokens for Element { tokens.append_all(quote! { __cx.element( - dioxus_elements::#name, + #name, __cx.bump().alloc([ #(#listeners),* ]), __cx.bump().alloc([ #(#attr),* ]), __cx.bump().alloc([ #(#children),* ]), @@ -191,6 +194,60 @@ impl ToTokens for Element { } } +#[derive(PartialEq, Eq, Clone, Debug, Hash)] +pub enum ElementName { + Ident(Ident), + Custom(String), +} + +impl ElementName { + pub(crate) fn tag_name(&self) -> TokenStream2 { + match self { + ElementName::Ident(i) => quote! { dioxus_elements::#i::TAG_NAME }, + ElementName::Custom(s) => quote! { #s }, + } + } +} + +impl PartialEq<&str> for ElementName { + fn eq(&self, other: &&str) -> bool { + match self { + ElementName::Ident(i) => i == *other, + ElementName::Custom(s) => s == *other, + } + } +} + +impl Display for ElementName { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ElementName::Ident(i) => write!(f, "{}", i), + ElementName::Custom(s) => write!(f, "{}", s), + } + } +} + +impl Parse for ElementName { + fn parse(stream: ParseStream) -> Result { + let raw = Punctuated::::parse_separated_nonempty(stream)?; + if raw.len() == 1 { + Ok(ElementName::Ident(raw.into_iter().next().unwrap())) + } else { + let tag = raw.into_iter().map(|ident| ident.to_string()).collect::>().join("-"); + Ok(ElementName::Custom(tag)) + } + } +} + +impl ToTokens for ElementName { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match self { + ElementName::Ident(i) => tokens.append_all(quote! { dioxus_elements::#i }), + ElementName::Custom(s) => tokens.append_all(quote! { #s }), + } + } +} + #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub enum ElementAttr { /// `attribute: "value"` @@ -234,7 +291,7 @@ impl ElementAttr { #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub struct ElementAttrNamed { - pub el_name: Ident, + pub el_name: ElementName, pub attr: ElementAttr, } @@ -242,24 +299,48 @@ impl ToTokens for ElementAttrNamed { fn to_tokens(&self, tokens: &mut TokenStream2) { let ElementAttrNamed { el_name, attr } = self; - tokens.append_all(match attr { + let ns = |name| match el_name { + ElementName::Ident(i) => quote! { dioxus_elements::#i::#name.1 }, + ElementName::Custom(_) => quote! { None }, + }; + let volitile = |name| match el_name { + ElementName::Ident(_) => quote! { #el_name::#name.2 }, + ElementName::Custom(_) => quote! { false }, + }; + let attribute=|name: &Ident| match el_name{ + ElementName::Ident(_) => quote! { #el_name::#name.0 }, + ElementName::Custom(_) => { + let as_string=name.to_string(); + quote!(#as_string) + } + }; + + let attribute= + + match attr { ElementAttr::AttrText { name, value } => { + let ns = ns(name); + let volitile = volitile(name); + let attribute=attribute(name); quote! { __cx.attr( - dioxus_elements::#el_name::#name.0, + #attribute, #value, - dioxus_elements::#el_name::#name.1, - dioxus_elements::#el_name::#name.2 + #ns, + #volitile ) } } ElementAttr::AttrExpression { name, value } => { + let ns = ns(name); + let volitile = volitile(name); + let attribute=attribute(name); quote! { __cx.attr( - dioxus_elements::#el_name::#name.0, + #attribute, #value, - dioxus_elements::#el_name::#name.1, - dioxus_elements::#el_name::#name.2 + #ns, + #volitile ) } } @@ -288,7 +369,9 @@ impl ToTokens for ElementAttrNamed { dioxus_elements::events::#name(__cx, #tokens) } } - }); + }; + + tokens.append_all(attribute); } } diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index d22b122e9..c54014527 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -426,13 +426,25 @@ impl<'a> DynamicContext<'a> { match root { BodyNode::Element(el) => { let el_name = &el.name; + let ns = |name| match el_name { + ElementName::Ident(i) => quote! { dioxus_elements::#i::#name }, + ElementName::Custom(_) => quote! { None }, + }; let static_attrs = el.attributes.iter().map(|attr| match &attr.attr { ElementAttr::AttrText { name, value } if value.is_static() => { let value = value.to_static().unwrap(); + let ns = ns(quote!(#name.1)); + let name=match el_name { + ElementName::Ident(_) => quote! { #el_name::#name.0 }, + ElementName::Custom(_) => { + let as_string=name.to_string(); + quote! { #as_string } + }, + }; quote! { ::dioxus::core::TemplateAttribute::Static { - name: dioxus_elements::#el_name::#name.0, - namespace: dioxus_elements::#el_name::#name.1, + name: #name, + namespace: #ns, value: #value, // todo: we don't diff these so we never apply the volatile flag @@ -479,10 +491,13 @@ impl<'a> DynamicContext<'a> { let _opt = el.children.len() == 1; let children = quote! { #(#children),* }; + let ns = ns(quote!(NAME_SPACE)); + let el_name = el_name.tag_name(); + quote! { ::dioxus::core::TemplateNode::Element { - tag: dioxus_elements::#el_name::TAG_NAME, - namespace: dioxus_elements::#el_name::NAME_SPACE, + tag: #el_name, + namespace: #ns, attrs: &[ #attrs ], children: &[ #children ], } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 0d5556552..a5f9e50e0 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -50,7 +50,17 @@ impl Parse for BodyNode { return Ok(BodyNode::Text(stream.parse()?)); } + // if this is a dash-separated path, it's a web component (custom element) let body_stream = stream.fork(); + if let Ok(ElementName::Custom(name)) = body_stream.parse::() { + println!("name: {}", name); + if name.contains('-') && body_stream.peek(token::Brace) { + return Ok(BodyNode::Element(stream.parse::()?)); + } + } + + let body_stream = stream.fork(); + if let Ok(path) = body_stream.parse::() { // this is an Element if path match of: // - one ident From 03413a68ab53b6ab1bb5c692820b208905bbf274 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 19 Mar 2023 20:47:16 -0500 Subject: [PATCH 2/3] fix formatting --- examples/web_component.rs | 2 -- packages/rsx/src/element.rs | 18 ++++++++++-------- packages/rsx/src/lib.rs | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/web_component.rs b/examples/web_component.rs index 7a719b41c..bd0f7eb15 100644 --- a/examples/web_component.rs +++ b/examples/web_component.rs @@ -1,5 +1,3 @@ - - use dioxus::prelude::*; fn main() { diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 5b0c12a98..1d74a08bc 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -233,7 +233,11 @@ impl Parse for ElementName { if raw.len() == 1 { Ok(ElementName::Ident(raw.into_iter().next().unwrap())) } else { - let tag = raw.into_iter().map(|ident| ident.to_string()).collect::>().join("-"); + let tag = raw + .into_iter() + .map(|ident| ident.to_string()) + .collect::>() + .join("-"); Ok(ElementName::Custom(tag)) } } @@ -307,21 +311,19 @@ impl ToTokens for ElementAttrNamed { ElementName::Ident(_) => quote! { #el_name::#name.2 }, ElementName::Custom(_) => quote! { false }, }; - let attribute=|name: &Ident| match el_name{ + let attribute = |name: &Ident| match el_name { ElementName::Ident(_) => quote! { #el_name::#name.0 }, ElementName::Custom(_) => { - let as_string=name.to_string(); + let as_string = name.to_string(); quote!(#as_string) } }; - let attribute= - - match attr { + let attribute = match attr { ElementAttr::AttrText { name, value } => { let ns = ns(name); let volitile = volitile(name); - let attribute=attribute(name); + let attribute = attribute(name); quote! { __cx.attr( #attribute, @@ -334,7 +336,7 @@ impl ToTokens for ElementAttrNamed { ElementAttr::AttrExpression { name, value } => { let ns = ns(name); let volitile = volitile(name); - let attribute=attribute(name); + let attribute = attribute(name); quote! { __cx.attr( #attribute, diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index c54014527..07c887e65 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -434,12 +434,12 @@ impl<'a> DynamicContext<'a> { ElementAttr::AttrText { name, value } if value.is_static() => { let value = value.to_static().unwrap(); let ns = ns(quote!(#name.1)); - let name=match el_name { + let name = match el_name { ElementName::Ident(_) => quote! { #el_name::#name.0 }, ElementName::Custom(_) => { - let as_string=name.to_string(); + let as_string = name.to_string(); quote! { #as_string } - }, + } }; quote! { ::dioxus::core::TemplateAttribute::Static { From f74854b38c5fddde6cb46af004778d3856df342f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 20 Mar 2023 07:21:31 -0500 Subject: [PATCH 3/3] fix formatting with raw elements --- packages/rsx/src/element.rs | 18 +++++++++++++++--- packages/rsx/src/node.rs | 3 +-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 1d74a08bc..466200eca 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -7,6 +7,7 @@ use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseBuffer, ParseStream}, punctuated::Punctuated, + spanned::Spanned, Error, Expr, Ident, LitStr, Result, Token, }; @@ -197,7 +198,7 @@ impl ToTokens for Element { #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub enum ElementName { Ident(Ident), - Custom(String), + Custom(LitStr), } impl ElementName { @@ -209,11 +210,20 @@ impl ElementName { } } +impl ElementName { + pub fn span(&self) -> Span { + match self { + ElementName::Ident(i) => i.span(), + ElementName::Custom(s) => s.span(), + } + } +} + impl PartialEq<&str> for ElementName { fn eq(&self, other: &&str) -> bool { match self { ElementName::Ident(i) => i == *other, - ElementName::Custom(s) => s == *other, + ElementName::Custom(s) => s.value() == *other, } } } @@ -222,7 +232,7 @@ impl Display for ElementName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ElementName::Ident(i) => write!(f, "{}", i), - ElementName::Custom(s) => write!(f, "{}", s), + ElementName::Custom(s) => write!(f, "{}", s.value()), } } } @@ -233,11 +243,13 @@ impl Parse for ElementName { if raw.len() == 1 { Ok(ElementName::Ident(raw.into_iter().next().unwrap())) } else { + let span = raw.span(); let tag = raw .into_iter() .map(|ident| ident.to_string()) .collect::>() .join("-"); + let tag = LitStr::new(&tag, span); Ok(ElementName::Custom(tag)) } } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index a5f9e50e0..54448c883 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -53,8 +53,7 @@ impl Parse for BodyNode { // if this is a dash-separated path, it's a web component (custom element) let body_stream = stream.fork(); if let Ok(ElementName::Custom(name)) = body_stream.parse::() { - println!("name: {}", name); - if name.contains('-') && body_stream.peek(token::Brace) { + if name.value().contains('-') && body_stream.peek(token::Brace) { return Ok(BodyNode::Element(stream.parse::()?)); } }