implement spreading props in the rsx macro

This commit is contained in:
Evan Almloff committed 2023-09-22 10:04:34 -05:00
1 parent 9e167dfdb7
commit 5b65c4cfb4
7 files changed
+208 -102

No files matched your search

+1 -1
View File
@@ -73,7 +73,7 @@ impl<'a> HasAttributesBox<'a, Props<'a>> for Props<'a> {
impl ExtendedGlobalAttributesMarker for Props<'_> {} impl ExtendedGlobalAttributesMarker for Props<'_> {}
fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { fn Component<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
let attributes = &cx.props.attributes; let attributes = &*cx.props.attributes;
render! { render! {
audio { audio {
..attributes, ..attributes,
+17 -4
View File
@@ -49,7 +49,6 @@ impl Writer<'_> {
attributes, attributes,
children, children,
brace, brace,
extra_attributes,
.. ..
} = el; } = el;
@@ -167,7 +166,7 @@ impl Writer<'_> {
fn write_attributes( fn write_attributes(
&mut self, &mut self,
attributes: &[ElementAttrNamed], attributes: &[AttributeType],
key: &Option<IfmtInput>, key: &Option<IfmtInput>,
sameline: bool, sameline: bool,
) -> Result { ) -> Result {
@@ -189,7 +188,7 @@ impl Writer<'_> {
while let Some(attr) = attr_iter.next() { while let Some(attr) = attr_iter.next() {
self.out.indent += 1; self.out.indent += 1;
if !sameline { if !sameline {
self.write_comments(attr.attr.start())?; self.write_comments(attr.start())?;
} }
self.out.indent -= 1; self.out.indent -= 1;
@@ -290,7 +289,14 @@ impl Writer<'_> {
Ok(()) Ok(())
} }
fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result { fn write_attribute(&mut self, attr: &AttributeType) -> Result {
match attr {
AttributeType::Named(attr) => self.write_named_attribute(attr),
AttributeType::Spread(attr) => self.write_spread_attribute(attr),
}
}
fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
self.write_attribute_name(&attr.attr.name)?; self.write_attribute_name(&attr.attr.name)?;
write!(self.out, ": ")?; write!(self.out, ": ")?;
self.write_attribute_value(&attr.attr.value)?; self.write_attribute_value(&attr.attr.value)?;
@@ -298,6 +304,13 @@ impl Writer<'_> {
Ok(()) Ok(())
} }
fn write_spread_attribute(&mut self, attr: &Expr) -> Result {
write!(self.out, "..")?;
write!(self.out, "{}", prettyplease::unparse_expr(attr))?;
Ok(())
}
// make sure the comments are actually relevant to this element. // make sure the comments are actually relevant to this element.
// test by making sure this element is the primary element on this line // test by making sure this element is the primary element on this line
pub fn current_span_is_primary(&self, location: Span) -> bool { pub fn current_span_is_primary(&self, location: Span) -> bool {
+19 -11
View File
@@ -1,4 +1,4 @@
use dioxus_rsx::{BodyNode, ElementAttrNamed, ElementAttrValue, ForLoop}; use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop};
use proc_macro2::{LineColumn, Span}; use proc_macro2::{LineColumn, Span};
use quote::ToTokens; use quote::ToTokens;
use std::{ use std::{
@@ -165,12 +165,12 @@ impl<'a> Writer<'a> {
} }
} }
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize { pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize {
let mut total = 0; let mut total = 0;
for attr in attributes { for attr in attributes {
if self.current_span_is_primary(attr.attr.start()) { if self.current_span_is_primary(attr.start()) {
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() { 'line: for line in self.src[..attr.start().start().line - 1].iter().rev() {
match (line.trim().starts_with("//"), line.is_empty()) { match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return 100000, (true, _) => return 100000,
(_, true) => continue 'line, (_, true) => continue 'line,
@@ -179,16 +179,24 @@ impl<'a> Writer<'a> {
} }
} }
total += match &attr.attr.name { match attr {
dioxus_rsx::ElementAttrName::BuiltIn(name) => { AttributeType::Named(attr) => {
let name = name.to_string(); let name_len = match &attr.attr.name {
name.len() dioxus_rsx::ElementAttrName::BuiltIn(name) => {
let name = name.to_string();
name.len()
}
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
};
total += name_len;
total += self.attr_value_len(&attr.attr.value);
}
AttributeType::Spread(expr) => {
let expr_len = self.retrieve_formatted_expr(expr).len();
total += expr_len + 3;
} }
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
}; };
total += self.attr_value_len(&attr.attr.value);
total += 6; total += 6;
} }
+8 -8
View File
@@ -1,6 +1,7 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use dioxus_rsx::{ use dioxus_rsx::{
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput, AttributeType, BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed,
ElementName, IfmtInput,
}; };
pub use html_parser::{Dom, Node}; pub use html_parser::{Dom, Node};
use proc_macro2::{Ident, Span}; use proc_macro2::{Ident, Span};
@@ -34,7 +35,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
Ident::new(new_name.as_str(), Span::call_site()) Ident::new(new_name.as_str(), Span::call_site())
}; };
ElementAttrNamed { AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
attr: ElementAttr { attr: ElementAttr {
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text( value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(
@@ -42,13 +43,13 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
)), )),
name: dioxus_rsx::ElementAttrName::BuiltIn(ident), name: dioxus_rsx::ElementAttrName::BuiltIn(ident),
}, },
} })
}) })
.collect(); .collect();
let class = el.classes.join(" "); let class = el.classes.join(" ");
if !class.is_empty() { if !class.is_empty() {
attributes.push(ElementAttrNamed { attributes.push(AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
attr: ElementAttr { attr: ElementAttr {
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new( name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
@@ -57,11 +58,11 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
)), )),
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)), value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(&class)),
}, },
}); }));
} }
if let Some(id) = &el.id { if let Some(id) = &el.id {
attributes.push(ElementAttrNamed { attributes.push(AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
attr: ElementAttr { attr: ElementAttr {
name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new( name: dioxus_rsx::ElementAttrName::BuiltIn(Ident::new(
@@ -70,7 +71,7 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
)), )),
value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)), value: dioxus_rsx::ElementAttrValue::AttrLiteral(ifmt_from_text(id)),
}, },
}); }));
} }
let children = el.children.iter().filter_map(rsx_node_from_html).collect(); let children = el.children.iter().filter_map(rsx_node_from_html).collect();
@@ -82,7 +83,6 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
merged_attributes: Default::default(), merged_attributes: Default::default(),
key: None, key: None,
brace: Default::default(), brace: Default::default(),
extra_attributes: None,
})) }))
} }
+32 -1
View File
@@ -4,7 +4,38 @@ use super::*;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{parse_quote, Expr, ExprIf, Ident, LitStr}; use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr};
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum AttributeType {
Named(ElementAttrNamed),
Spread(Expr),
}
impl AttributeType {
pub fn start(&self) -> Span {
match self {
AttributeType::Named(n) => n.attr.start(),
AttributeType::Spread(e) => e.span(),
}
}
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
match (self, other) {
(Self::Named(a), Self::Named(b)) => a.try_combine(b).map(Self::Named),
_ => None,
}
}
}
impl ToTokens for AttributeType {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
AttributeType::Named(n) => tokens.append_all(quote! { #n }),
AttributeType::Spread(e) => tokens.append_all(quote! { #e.into() }),
}
}
}
#[derive(PartialEq, Eq, Clone, Debug, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct ElementAttrNamed { pub struct ElementAttrNamed {
+67 -29
View File
@@ -8,7 +8,7 @@ use syn::{
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
punctuated::Punctuated, punctuated::Punctuated,
spanned::Spanned, spanned::Spanned,
Ident, LitStr, Result, Token, Expr, Ident, LitStr, Result, Token,
}; };
// ======================================= // =======================================
@@ -18,11 +18,10 @@ use syn::{
pub struct Element { pub struct Element {
pub name: ElementName, pub name: ElementName,
pub key: Option<IfmtInput>, pub key: Option<IfmtInput>,
pub attributes: Vec<ElementAttrNamed>, pub attributes: Vec<AttributeType>,
pub merged_attributes: Vec<ElementAttrNamed>, pub merged_attributes: Vec<AttributeType>,
pub children: Vec<BodyNode>, pub children: Vec<BodyNode>,
pub brace: syn::token::Brace, pub brace: syn::token::Brace,
pub extra_attributes: Option<Expr>,
} }
impl Parse for Element { impl Parse for Element {
@@ -33,7 +32,7 @@ impl Parse for Element {
let content: ParseBuffer; let content: ParseBuffer;
let brace = syn::braced!(content in stream); let brace = syn::braced!(content in stream);
let mut attributes: Vec<ElementAttrNamed> = vec![]; let mut attributes: Vec<AttributeType> = vec![];
let mut children: Vec<BodyNode> = vec![]; let mut children: Vec<BodyNode> = vec![];
let mut key = None; let mut key = None;
@@ -43,9 +42,20 @@ impl Parse for Element {
// "def": 456, // "def": 456,
// abc: 123, // abc: 123,
loop { loop {
if content.peek(Token![...]) { if content.peek(Token![..]) {
content.parse::<Token![...]>()?; content.parse::<Token![..]>()?;
extra_attributes = Some(content.parse::<Expr>()?); let expr = content.parse::<Expr>()?;
let span = expr.span();
attributes.push(attribute::AttributeType::Spread(expr));
if content.is_empty() {
break;
}
if content.parse::<Token![,]>().is_err() {
missing_trailing_comma!(span);
}
continue;
} }
// Parse the raw literal fields // Parse the raw literal fields
@@ -56,13 +66,13 @@ impl Parse for Element {
content.parse::<Token![:]>()?; content.parse::<Token![:]>()?;
let value = content.parse::<ElementAttrValue>()?; let value = content.parse::<ElementAttrValue>()?;
attributes.push(ElementAttrNamed { attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
attr: ElementAttr { attr: ElementAttr {
name: ElementAttrName::Custom(name), name: ElementAttrName::Custom(name),
value, value,
}, },
}); }));
if content.is_empty() { if content.is_empty() {
break; break;
@@ -85,13 +95,13 @@ impl Parse for Element {
let span = content.span(); let span = content.span();
if name_str.starts_with("on") { if name_str.starts_with("on") {
attributes.push(ElementAttrNamed { attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
attr: ElementAttr { attr: ElementAttr {
name: ElementAttrName::BuiltIn(name), name: ElementAttrName::BuiltIn(name),
value: ElementAttrValue::EventTokens(content.parse()?), value: ElementAttrValue::EventTokens(content.parse()?),
}, },
}); }));
} else { } else {
match name_str.as_str() { match name_str.as_str() {
"key" => { "key" => {
@@ -99,13 +109,13 @@ impl Parse for Element {
} }
_ => { _ => {
let value = content.parse::<ElementAttrValue>()?; let value = content.parse::<ElementAttrValue>()?;
attributes.push(ElementAttrNamed { attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
el_name: el_name.clone(), el_name: el_name.clone(),
attr: ElementAttr { attr: ElementAttr {
name: ElementAttrName::BuiltIn(name), name: ElementAttrName::BuiltIn(name),
value, value,
}, },
}); }));
} }
} }
} }
@@ -114,7 +124,6 @@ impl Parse for Element {
break; break;
} }
// todo: add a message saying you need to include commas between fields
if content.parse::<Token![,]>().is_err() { if content.parse::<Token![,]>().is_err() {
missing_trailing_comma!(span); missing_trailing_comma!(span);
} }
@@ -126,12 +135,26 @@ impl Parse for Element {
// Deduplicate any attributes that can be combined // Deduplicate any attributes that can be combined
// For example, if there are two `class` attributes, combine them into one // For example, if there are two `class` attributes, combine them into one
let mut merged_attributes: Vec<ElementAttrNamed> = Vec::new(); let mut merged_attributes: Vec<AttributeType> = Vec::new();
for attr in &attributes { for attr in &attributes {
if let Some(old_attr_index) = merged_attributes if let Some(old_attr_index) = merged_attributes.iter().position(|a| {
.iter() matches!((a, attr), (
.position(|a| a.attr.name == attr.attr.name) AttributeType::Named(ElementAttrNamed {
{ attr: ElementAttr {
name: ElementAttrName::BuiltIn(old_name),
..
},
..
}),
AttributeType::Named(ElementAttrNamed {
attr: ElementAttr {
name: ElementAttrName::BuiltIn(new_name),
..
},
..
}),
) if old_name == new_name)
}) {
let old_attr = &mut merged_attributes[old_attr_index]; let old_attr = &mut merged_attributes[old_attr_index];
if let Some(combined) = old_attr.try_combine(attr) { if let Some(combined) = old_attr.try_combine(attr) {
*old_attr = combined; *old_attr = combined;
@@ -165,7 +188,6 @@ impl Parse for Element {
merged_attributes, merged_attributes,
children, children,
brace, brace,
extra_attributes,
}) })
} }
} }
@@ -180,15 +202,31 @@ impl ToTokens for Element {
None => quote! { None }, None => quote! { None },
}; };
let listeners = self let listeners = self.merged_attributes.iter().filter(|f| {
.merged_attributes matches!(
.iter() f,
.filter(|f| matches!(f.attr.value, ElementAttrValue::EventTokens { .. })); AttributeType::Named(ElementAttrNamed {
attr: ElementAttr {
value: ElementAttrValue::EventTokens { .. },
..
},
..
})
)
});
let attr = self let attr = self.merged_attributes.iter().filter(|f| {
.merged_attributes !matches!(
.iter() f,
.filter(|f| !matches!(f.attr.value, ElementAttrValue::EventTokens { .. })); AttributeType::Named(ElementAttrNamed {
attr: ElementAttr {
value: ElementAttrValue::EventTokens { .. },
..
},
..
})
)
});
tokens.append_all(quote! { tokens.append_all(quote! {
__cx.element( __cx.element(
+64 -48
View File
@@ -261,7 +261,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
#[cfg(feature = "hot_reload")] #[cfg(feature = "hot_reload")]
#[derive(Default, Debug)] #[derive(Default, Debug)]
struct DynamicMapping { struct DynamicMapping {
attribute_to_idx: std::collections::HashMap<ElementAttr, Vec<usize>>, attribute_to_idx: std::collections::HashMap<AttributeType, Vec<usize>>,
last_attribute_idx: usize, last_attribute_idx: usize,
node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>, node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
last_element_idx: usize, last_element_idx: usize,
@@ -277,7 +277,7 @@ impl DynamicMapping {
new new
} }
fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> { fn get_attribute_idx(&mut self, attr: &AttributeType) -> Option<usize> {
self.attribute_to_idx self.attribute_to_idx
.get_mut(attr) .get_mut(attr)
.and_then(|idxs| idxs.pop()) .and_then(|idxs| idxs.pop())
@@ -287,7 +287,7 @@ impl DynamicMapping {
self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop()) self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
} }
fn insert_attribute(&mut self, attr: ElementAttr) -> usize { fn insert_attribute(&mut self, attr: AttributeType) -> usize {
let idx = self.last_attribute_idx; let idx = self.last_attribute_idx;
self.last_attribute_idx += 1; self.last_attribute_idx += 1;
@@ -309,10 +309,17 @@ impl DynamicMapping {
match node { match node {
BodyNode::Element(el) => { BodyNode::Element(el) => {
for attr in el.merged_attributes { for attr in el.merged_attributes {
match &attr.attr.value { match &attr {
ElementAttrValue::AttrLiteral(input) if input.is_static() => {} AttributeType::Named(ElementAttrNamed {
attr:
ElementAttr {
value: ElementAttrValue::AttrLiteral(input),
..
},
..
}) if input.is_static() => {}
_ => { _ => {
self.insert_attribute(attr.attr); self.insert_attribute(attr);
} }
} }
} }
@@ -340,7 +347,7 @@ impl DynamicMapping {
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct DynamicContext<'a> { pub struct DynamicContext<'a> {
dynamic_nodes: Vec<&'a BodyNode>, dynamic_nodes: Vec<&'a BodyNode>,
dynamic_attributes: Vec<&'a ElementAttrNamed>, dynamic_attributes: Vec<&'a AttributeType>,
current_path: Vec<u8>, current_path: Vec<u8>,
node_paths: Vec<Vec<u8>>, node_paths: Vec<Vec<u8>>,
@@ -360,10 +367,16 @@ impl<'a> DynamicContext<'a> {
let mut static_attrs = Vec::new(); let mut static_attrs = Vec::new();
for attr in &el.merged_attributes { for attr in &el.merged_attributes {
match &attr.attr.value { match &attr {
ElementAttrValue::AttrLiteral(value) if value.is_static() => { AttributeType::Named(ElementAttrNamed {
attr:
ElementAttr {
value: ElementAttrValue::AttrLiteral(value),
name,
},
..
}) if value.is_static() => {
let value = value.source.as_ref().unwrap(); let value = value.source.as_ref().unwrap();
let name = &attr.attr.name;
let attribute_name_rust = name.to_string(); let attribute_name_rust = name.to_string();
let (name, namespace) = let (name, namespace) =
Ctx::map_attribute(&element_name_rust, &attribute_name_rust) Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
@@ -377,7 +390,7 @@ impl<'a> DynamicContext<'a> {
_ => { _ => {
let idx = match mapping { let idx = match mapping {
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?, Some(mapping) => mapping.get_attribute_idx(&attr)?,
None => self.dynamic_attributes.len(), None => self.dynamic_attributes.len(),
}; };
self.dynamic_attributes.push(attr); self.dynamic_attributes.push(attr);
@@ -447,47 +460,50 @@ impl<'a> DynamicContext<'a> {
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name }, ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
ElementName::Custom(_) => quote! { None }, ElementName::Custom(_) => quote! { None },
}; };
let static_attrs = el let static_attrs = el.merged_attributes.iter().map(|attr| match attr {
.merged_attributes AttributeType::Named(ElementAttrNamed {
.iter() attr:
.map(|attr| match &attr.attr.value { ElementAttr {
ElementAttrValue::AttrLiteral(value) if value.is_static() => { value: ElementAttrValue::AttrLiteral(value),
let value = value.to_static().unwrap(); name,
let ns = { },
match &attr.attr.name { ..
ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)), }) if value.is_static() => {
ElementAttrName::Custom(_) => quote!(None), let value = value.to_static().unwrap();
} let ns = {
}; match name {
let name = &attr.attr.name; ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
let name = match (el_name, name) { ElementAttrName::Custom(_) => quote!(None),
(ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => { }
quote! { #el_name::#name.0 } };
} let name = match (el_name, name) {
_ => { (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
let as_string = name.to_string(); quote! { #el_name::#name.0 }
quote! { #as_string } }
} _ => {
}; let as_string = name.to_string();
quote! { quote! { #as_string }
::dioxus::core::TemplateAttribute::Static { }
name: #name, };
namespace: #ns, quote! {
value: #value, ::dioxus::core::TemplateAttribute::Static {
name: #name,
namespace: #ns,
value: #value,
// todo: we don't diff these so we never apply the volatile flag // todo: we don't diff these so we never apply the volatile flag
// volatile: dioxus_elements::#el_name::#name.2, // volatile: dioxus_elements::#el_name::#name.2,
}
} }
} }
}
_ => { _ => {
let ct = self.dynamic_attributes.len(); let ct = self.dynamic_attributes.len();
self.dynamic_attributes.push(attr); self.dynamic_attributes.push(attr);
self.attr_paths.push(self.current_path.clone()); self.attr_paths.push(self.current_path.clone());
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } } quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
} }
}); });
let attrs = quote! { #(#static_attrs),*}; let attrs = quote! { #(#static_attrs),*};