diff --git a/examples/spread.rs b/examples/spread.rs new file mode 100644 index 000000000..eb09e5dd9 --- /dev/null +++ b/examples/spread.rs @@ -0,0 +1,209 @@ +//! Example: SSR +//! +//! This example shows how we can render the Dioxus Virtualdom using SSR. + +use dioxus::core::{Attribute, HasAttributesBox}; +use dioxus::html::{AudioExtension, ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; +use dioxus::prelude::*; + +fn main() { + // We can render VirtualDoms + let mut vdom = VirtualDom::new(app); + let _ = vdom.rebuild(); + println!("{}", dioxus_ssr::render(&vdom)); + + // Or we can render rsx! calls themselves + println!( + "{}", + dioxus_ssr::render_lazy(rsx! { + div { + h1 { "Hello, world!" } + } + }) + ); + + // We can configure the SSR rendering to add ids for rehydration + println!("{}", dioxus_ssr::pre_render(&vdom)); + + // We can render to a buf directly too + let mut file = String::new(); + let mut renderer = dioxus_ssr::Renderer::default(); + renderer.render_to(&mut file, &vdom).unwrap(); + println!("{file}"); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx!(Foo { + autoplay: true, + controls: true, + })) +} + +pub struct FooProps<'a> { + pub open: Option<&'a str>, + attributes: Vec>, +} + +// ----- +impl<'a> FooProps<'a> { + #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] + #[allow(dead_code)] + pub fn builder(cx: &'a ScopeState) -> FooPropsBuilder<'a, ((),)> { + FooPropsBuilder { + bump: cx.bump(), + fields: ((),), + attributes: Vec::new(), + _phantom: core::default::Default::default(), + } + } +} +#[must_use] +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, non_snake_case)] +pub struct FooPropsBuilder<'a, TypedBuilderFields> { + bump: &'a ::dioxus::core::exports::bumpalo::Bump, + fields: TypedBuilderFields, + attributes: Vec>, + _phantom: (core::marker::PhantomData<&'a ()>), +} +//impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } +impl<'a> dioxus::prelude::Properties<'a> for FooProps<'a> { + type Builder = FooPropsBuilder<'a, ((),)>; + const IS_STATIC: bool = false; + fn builder(cx: &'a ScopeState) -> Self::Builder { + FooProps::builder(cx) + } + unsafe fn memoize(&self, other: &Self) -> bool { + false + } +} +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, non_snake_case)] +pub trait FooPropsBuilder_Optional { + fn into_value T>(self, default: F) -> T; +} +impl FooPropsBuilder_Optional for () { + fn into_value T>(self, default: F) -> T { + default() + } +} +impl FooPropsBuilder_Optional for (T,) { + fn into_value T>(self, _: F) -> T { + self.0 + } +} +#[allow(dead_code, non_camel_case_types, missing_docs)] +impl<'a> FooPropsBuilder<'a, ((),)> { + pub fn open( + self, + open: &'a str, + ) -> FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > { + let open = (Some(open),); + let (_,) = self.fields; + FooPropsBuilder { + bump: self.bump, + fields: (open,), + attributes: self.attributes, + _phantom: self._phantom, + } + } +} +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, non_snake_case)] +pub enum FooPropsBuilder_Error_Repeated_field_open {} +#[doc(hidden)] +#[allow(dead_code, non_camel_case_types, missing_docs)] +impl<'a> + FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > +{ + #[deprecated(note = "Repeated field open")] + pub fn open( + self, + _: FooPropsBuilder_Error_Repeated_field_open, + ) -> FooPropsBuilder< + 'a, + (( + Option<&'a str>, + // pub attributes: Vec>, + ),), + > { + self + } +} +#[allow(dead_code, non_camel_case_types, missing_docs)] +impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open,)> { + pub fn build(self) -> FooProps<'a> { + let (open,) = self.fields; + let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); + FooProps { + open, + attributes: self.attributes, + } + } +} +// ----- + +impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A,)>> for FooPropsBuilder<'a, (A,)> { + fn push_attribute( + self, + name: &'a str, + ns: Option<&'static str>, + attr: impl IntoAttributeValue<'a>, + volatile: bool, + ) -> Self { + let mut attrs = self.attributes; + // We insert attributes so that the list is binary-searchable + if let Err(index) = attrs.binary_search_by(|probe| probe.name.cmp(name)) { + attrs.insert( + index, + Attribute::new(name, attr.into_value(self.bump), ns, volatile), + ); + } + FooPropsBuilder { + bump: self.bump, + fields: self.fields, + attributes: attrs, + _phantom: self._phantom, + } + } +} +impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} +impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} + +#[allow(non_snake_case)] +pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { + let muted = false; + let attributes = &cx.props.attributes; + render! { + // rsx! { + // audio { + // muted: muted, + // } + // } + ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { + static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "123" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; + let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; + attrs.push((&**attributes).into()); + ::dioxus::core::VNode { + parent: None, + key: None, + template: std::cell::Cell::new(TEMPLATE), + root_ids: Default::default(), + dynamic_nodes: __cx.bump().alloc([]), + dynamic_attrs: __cx.bump().alloc(attrs), + } + }) + } +} diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index e24b8327a..c7824db85 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -656,12 +656,12 @@ Finally, call `.build()` to create the instance of `{name}`. } } - impl #impl_generics dioxus::prelude::Properties for #name #ty_generics + impl #impl_generics ::dioxus::prelude::Properties<'_> for #name #ty_generics #b_generics_where_extras_predicates { type Builder = #builder_name #generics_with_empty; const IS_STATIC: bool = #is_static; - fn builder(_cx: &::dioxus_core::prelude::ScopeState) -> Self::Builder { + fn builder(_cx: &::dioxus::prelude::ScopeState) -> Self::Builder { #name::builder() } unsafe fn memoize(&self, other: &Self) -> bool { diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index fa8c7e4bd..0c282a930 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -91,7 +91,7 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> { /// }) /// } /// ``` -impl<'a> Properties for FragmentProps<'a> { +impl<'a> Properties<'_> for FragmentProps<'a> { type Builder = FragmentBuilder<'a, false>; const IS_STATIC: bool = false; fn builder(_cx: &ScopeState) -> Self::Builder { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index e34fb7d42..2d3f7fcb0 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -896,8 +896,8 @@ impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { pub trait HasAttributesBox<'a, T> { fn push_attribute( self, - name: &str, - ns: Option<&str>, + name: &'a str, + ns: Option<&'static str>, attr: impl IntoAttributeValue<'a>, volatile: bool, ) -> T; diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index d3f9f6989..d05fbf932 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -32,7 +32,7 @@ use crate::innerlude::*; /// data: &'a str /// } /// ``` -pub trait Properties: Sized { +pub trait Properties<'a>: Sized { /// The type of the builder for this component. /// Used to create "in-progress" versions of the props. type Builder; @@ -41,7 +41,7 @@ pub trait Properties: Sized { const IS_STATIC: bool; /// Create a builder for this component. - fn builder(cx: &ScopeState) -> Self::Builder; + fn builder(cx: &'a ScopeState) -> Self::Builder; /// Memoization can only happen if the props are valid for the 'static lifetime /// @@ -51,7 +51,7 @@ pub trait Properties: Sized { unsafe fn memoize(&self, other: &Self) -> bool; } -impl Properties for () { +impl Properties<'_> for () { type Builder = EmptyBuilder; const IS_STATIC: bool = true; fn builder(_cx: &ScopeState) -> Self::Builder { @@ -70,8 +70,8 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>( - cx: &ScopeState, +pub fn fc_to_builder<'a, T: Properties<'a> + 'a>( + cx: &'a ScopeState, _: fn(Scope<'a, T>) -> Element<'a>, ) -> T::Builder { T::builder(cx) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 5d279f86a..62d640037 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -423,7 +423,7 @@ impl<'src> ScopeState { fn_name: &'static str, ) -> DynamicNode<'src> where - P: Properties + 'child, + P: Properties<'child> + 'child, 'src: 'child, { let vcomp = VProps::new(component, P::memoize, props); diff --git a/packages/dioxus/tests/props_spread.rs b/packages/dioxus/tests/props_spread.rs deleted file mode 100644 index 01423ea80..000000000 --- a/packages/dioxus/tests/props_spread.rs +++ /dev/null @@ -1,187 +0,0 @@ -use dioxus::core_macro::render; -use dioxus::prelude::rsx; -use dioxus_core::{Attribute, Element, HasAttributesBox, Scope}; -use dioxus_html::{ExtendedAudioMarker, ExtendedGlobalAttributesMarker}; - -#[test] -fn props_spread() { - pub struct FooProps<'a> { - pub open: Option<&'a str>, - attributes: Vec>, - } - - // ----- - impl<'a> FooProps<'a> { - #[doc = "\nCreate a builder for building `FooProps`.\nOn the builder, call `.open(...)`(optional) to set the values of the fields.\nFinally, call `.build()` to create the instance of `FooProps`.\n "] - #[allow(dead_code)] - pub fn builder(cx: &ScopeState) -> FooPropsBuilder<'a, ((),)> { - FooPropsBuilder { - bump: cx.bump(), - fields: ((),), - attributes: Vec::new(), - _phantom: core::default::Default::default(), - } - } - } - #[must_use] - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub struct FooPropsBuilder<'a, TypedBuilderFields> { - bump: &'a ::dioxus::core::exports::bumpalo::Bump, - fields: TypedBuilderFields, - attributes: Vec>, - _phantom: (core::marker::PhantomData<&'a ()>), - } - //impl<'a, TypedBuilderFields, > Clone for FooPropsBuilder<'a, TypedBuilderFields, > where TypedBuilderFields: Clone { fn clone(&self) -> Self { Self { fields: self.fields.clone(), attributes: self.attributes, _phantom: Default::default() } } } - impl<'a> dioxus::prelude::Properties for FooProps<'a> { - type Builder = FooPropsBuilder<'a, ((),)>; - const IS_STATIC: bool = false; - fn builder(cx: &ScopeState) -> Self::Builder { - FooProps::builder(cx) - } - unsafe fn memoize(&self, other: &Self) -> bool { - false - } - } - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub trait FooPropsBuilder_Optional { - fn into_value T>(self, default: F) -> T; - } - impl FooPropsBuilder_Optional for () { - fn into_value T>(self, default: F) -> T { - default() - } - } - impl FooPropsBuilder_Optional for (T,) { - fn into_value T>(self, _: F) -> T { - self.0 - } - } - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a> FooPropsBuilder<'a, ((),)> { - pub fn open( - self, - open: &'a str, - ) -> FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > { - let open = (Some(open),); - let (_,) = self.fields; - FooPropsBuilder { - bump: self.bump, - fields: (open,), - attributes: self.attributes, - _phantom: self._phantom, - } - } - } - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub enum FooPropsBuilder_Error_Repeated_field_open {} - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a> - FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > - { - #[deprecated(note = "Repeated field open")] - pub fn open( - self, - _: FooPropsBuilder_Error_Repeated_field_open, - ) -> FooPropsBuilder< - 'a, - (( - Option<&'a str>, - // pub attributes: Vec>, - ),), - > { - self - } - } - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl<'a, __open: FooPropsBuilder_Optional>> FooPropsBuilder<'a, (__open,)> { - pub fn build(self) -> FooProps<'a> { - let (open,) = self.fields; - let open = FooPropsBuilder_Optional::into_value(open, || Default::default()); - FooProps { - open, - attributes: self.attributes, - } - } - } - // ----- - - impl<'a, A> HasAttributesBox<'a, FooPropsBuilder<'a, (A,)>> for FooPropsBuilder<'a, (A,)> { - fn push_attribute( - self, - name: &str, - ns: Option<&str>, - attr: impl IntoAttributeValue<'a>, - volatile: bool, - ) -> Self { - let mut attrs = Vec::from(self.attributes); - attrs.push(Attribute::new( - name, - attr.into_value(self.bump), - ns, - volatile, - )); - FooPropsBuilder { - bump: self.bump, - fields: self.fields, - attributes: attrs, - _phantom: self._phantom, - } - } - } - impl ExtendedGlobalAttributesMarker for FooPropsBuilder<'_, (A,)> {} - impl ExtendedAudioMarker for FooPropsBuilder<'_, (A,)> {} - - use dioxus::prelude::*; - use dioxus_html::AudioExtension; - - #[allow(non_snake_case)] - pub fn Foo<'a>(cx: Scope<'a, FooProps<'a>>) -> Element<'a> { - let muted = false; - let attributes = &cx.props.attributes; - render! { - // rsx! { - // audio { - // muted: muted, - // } - // } - ::dioxus::core::LazyNodes::new(move |__cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode { - static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template { name: concat!(file!(), ":", line!(), ":", column!(), ":", "" ), roots: &[::dioxus::core::TemplateNode::Element { tag: dioxus_elements::audio::TAG_NAME, namespace: dioxus_elements::audio::NAME_SPACE, attrs: &[::dioxus::core::TemplateAttribute::Dynamic { id: 0usize }], children: &[] }], node_paths: &[], attr_paths: &[&[0u8]] }; - let mut attrs = vec![__cx.attr(dioxus_elements::audio::muted.0, muted, dioxus_elements::audio::muted.1, dioxus_elements::audio::muted.2)]; - for attr in attributes { - attrs.push(attr); - } - ::dioxus::core::VNode { - parent: None, - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Default::default(), - dynamic_nodes: __cx.bump().alloc([]), - dynamic_attrs: __cx.bump().alloc(attrs), - } - }) - } - } - - rsx! { - Foo { - autoplay: true, - controls: true, - } - }; -}