From dca74ba51daafbb7d1d64b50001e0814f44ba257 Mon Sep 17 00:00:00 2001 From: Ash Guy Date: Mon, 25 May 2020 17:30:29 +1000 Subject: [PATCH] [Fix] Elements Ordering & Struct Field Visibility (#17) > * As per #12 prior to this PR you couldn't use generated components across modules as the visibility modifier didn't flow down into each field of the struct. This PR fixes this issue including a test and some documentation about how and why it works. > * As per #16 prior to this PR when there were more than 2 children descending an element the order was being garbled due to the way it was being folded together. > > Tests included helping illustrate what was going on. --- .gitignore | 1 + README.md | 41 +++++++++--- render_macros/src/children.rs | 7 ++- render_macros/src/function_component.rs | 6 +- render_tests/src/lib.rs | 83 +++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 84a323e..ea56ff2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target **/*.rs.bk +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 8069ce7..d6cf677 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ XML rendering, but can work with other usages as well, like ReasonML's [`Pastel` A renderable component is a struct that implements the `Render` trait. There are multiple macros that provide a better experience implementing Renderable: -* `#[component]` for defining components using a function -* `rsx!` for composing elements with JSX ergonomics -* `html!` for composing elements and render them to a string +- `#[component]` for defining components using a function +- `rsx!` for composing elements with JSX ergonomics +- `html!` for composing elements and render them to a string ## Why is this different from... @@ -113,10 +113,37 @@ assert_eq!(rendered_html, r#"

Hello world!

"#); If you pay close attention, you see that the function `Heading` is: -* declared with an uppercase. Underneath, it generates a struct with the same name, and -implements the `Render` trait on it. -* does not have a return type. This is because everything is written to a writer, for -performance reasons. +- declared with an uppercase. Underneath, it generates a struct with the same name, and + implements the `Render` trait on it. +- does not have a return type. This is because everything is written to a writer, for + performance reasons. + +### Visibility & Component Libraries + +Often you're going to want to store your components somewhere else in your +project tree other than the module you're working on (if not in a different +module entirely!). In these cases, the visibility applied top the function that +defines your component will flow down into all fields of that struct. + +For example, if we add "pub" to the front of our Heading component above: + +```rust +#[component] +pub fn Heading<'title>(title: &'title str) { + rsx! {

{title}

} +} +``` + +...the struct that is generated would look something like... + +```rust +pub struct Heading { + pub title: &'title str +} +``` + +This is important to understand from a safety point of view when structuring +your libraries. #### Full example diff --git a/render_macros/src/children.rs b/render_macros/src/children.rs index 8d9feeb..7de0a43 100644 --- a/render_macros/src/children.rs +++ b/render_macros/src/children.rs @@ -24,16 +24,19 @@ impl Children { quote! { #child } }) .collect(); + match children_quotes.len() { 0 => quote! { Option::<()>::None }, - 1 => quote! { Some(#(#children_quotes)*) }, + 1 => quote! { Some(#(#children_quotes),*) }, _ => { let mut iter = children_quotes.iter(); + let first = iter.next().unwrap(); let second = iter.next().unwrap(); + let tuple_of_tuples = iter.fold( quote!((#first, #second)), - |renderable, current| quote!((#current, #renderable)), + |renderable, current| quote!((#renderable, #current)), ); quote! { Some(#tuple_of_tuples) } diff --git a/render_macros/src/function_component.rs b/render_macros/src/function_component.rs index 261528b..b4d3511 100644 --- a/render_macros/src/function_component.rs +++ b/render_macros/src/function_component.rs @@ -10,7 +10,11 @@ pub fn create_function_component(f: syn::ItemFn) -> TokenStream { let vis = f.vis; let inputs_block = if inputs.len() > 0 { - quote!({ #inputs }) + let input_names: Vec<_> = inputs + .iter() + .collect(); + + quote!({ #(#vis #input_names),* }) } else { quote!(;) }; diff --git a/render_tests/src/lib.rs b/render_tests/src/lib.rs index 63070e0..bf1ced5 100644 --- a/render_tests/src/lib.rs +++ b/render_tests/src/lib.rs @@ -20,6 +20,36 @@ pub fn works_with_raw() { assert_eq!(actual, "
"); } +#[test] +pub fn element_ordering() { + use pretty_assertions::assert_eq; + use render::{html, raw}; + + let actual = html! { + + }; + + assert_eq!(actual, ""); + + let deep = html! { +
+

{"A list"}

+
+ +
+ }; + + assert_eq!(deep, "

A list


"); +} + mod kaki { // A simple HTML 5 doctype declaration use render::html::HTML5Doctype; @@ -67,4 +97,57 @@ mod kaki { ); assert_eq!(actual, expected); } + + #[test] + fn externals_test() { + use pretty_assertions::assert_eq; + use crate::other::ExternalPage; + + let actual = render::html! { + + {format!("Welcome, {}", "Gal")} + + }; + + let expected = concat!( + "", + "", + "Home", + "", + "

Foo

", + "Welcome, Gal", + "", + "" + ); + assert_eq!(actual, expected); + } } + +/// ## Other +/// +/// Module for testing component visibility when imported from other modules. + +mod other { + use render::html::HTML5Doctype; + use render::{ component, rsx, Render }; + + #[component] + pub fn ExternalPage<'title, 'subtitle, Children: Render>( + title: &'title str, + subtitle: &'subtitle str, + children: Children + ) { + rsx! { + <> + + + {title} + +

{subtitle}

+ {children} + + + + } + } +} \ No newline at end of file