~repos /tide-jsx

#rust#proc-macro#jsx

git clone https://pyrossh.dev/repos/tide-jsx.git

Tide + JSX


0c772702 Gal Schlezinger

6 years ago
Add `#[component]` macro (#4)
README.md CHANGED
@@ -1,8 +1,18 @@
1
- # Render
1
+ # render
2
2
 
3
3
  > 🔏 A safe and simple template engine with the ergonomics of JSX
4
4
 
5
- The `Renderable` trait contains a simple function that returns `String`. This is very handy for type-safe HTML templates, but can also work for writing tree-like terminal coloring mechanism like ReasonML's [Pastel](https://reason-native.com/docs/pastel/).
5
+ `render` itself is a combination of traits, structs and macros that together unify and
6
+ boost the experience of composing tree-shaped data structures. This works best with HTML and
7
+ XML rendering, but can work with other usages as well, like ReasonML's [`Pastel`](https://reason-native.com/docs/pastel/) library for terminal colors.
8
+
9
+ ## How?
10
+
11
+ A renderable component is a struct that implements the `Renderable` trait. There
12
+ are multiple macros that provide a better experience implementing Renderable:
13
+
14
+ * `html!` for the JSX ergonomics
15
+ * `#[component]` for the syntactic-sugar of function components
6
16
 
7
17
  ## Why this is different from `typed-html`?
8
18
 
@@ -18,6 +28,8 @@ The `Renderable` trait contains a simple function that returns `String`. This is
18
28
  // A simple HTML 5 doctype declaration
19
29
  use render::html::HTML5Doctype;
20
30
  use render::{
31
+ // A macro to create components
32
+ component,
21
33
  // A macro to compose components in JSX fashion
22
34
  html,
23
35
  // A component that just render its children
@@ -27,28 +39,19 @@ use render::{
27
39
  };
28
40
 
29
41
  // This can be any layout we want
30
- #[derive(Debug)]
42
+ #[component]
31
- struct Page<'a, T: Renderable> {
43
+ fn Page<'a, Children: Renderable>(title: &'a str, children: Children) -> String {
44
+ html! {
45
+ <Fragment>
32
- title: &'a str,
46
+ <HTML5Doctype />
47
+ <html>
48
+ <head><title>{title}</title></head>
49
+ <body>
33
- children: T,
50
+ {children}
51
+ </body>
52
+ </html>
53
+ </Fragment>
34
- }
54
+ }
35
-
36
- // Implementing `Renderable` gives the ability to compose
37
- // components
38
- impl<'a, T: Renderable> Renderable for Page<'a, T> {
39
- fn render(self) -> String {
40
- html! {
41
- <Fragment>
42
- <HTML5Doctype />
43
- <html>
44
- <head><title>{self.title}</title></head>
45
- <body>
46
- {self.children}
47
- </body>
48
- </html>
49
- </Fragment>
50
- }
51
- }
52
55
  }
53
56
 
54
57
  // This can be a route in Rocket, the web framework,
@@ -60,4 +63,7 @@ pub fn some_page(user_name: &str) -> String {
60
63
  </Page>
61
64
  }
62
65
  }
66
+
63
67
  ```
68
+
69
+ License: MIT
render/src/lib.rs CHANGED
@@ -1,14 +1,24 @@
1
1
  //! > 🔏 A safe and simple template engine with the ergonomics of JSX
2
+ //!
3
+ //! `render` itself is a combination of traits, structs and macros that together unify and
4
+ //! boost the experience of composing tree-shaped data structures. This works best with HTML and
5
+ //! XML rendering, but can work with other usages as well, like ReasonML's [`Pastel`](https://reason-native.com/docs/pastel/) library for terminal colors.
2
6
  //!
3
- //! The `Renderable` trait contains a simple function that returns `String`. This is very handy for type-safe HTML templates, but can also work for writing tree-like terminal coloring mechanism like ReasonML's [Pastel](https://reason-native.com/docs/pastel/).
7
+ //! # How?
4
8
  //!
9
+ //! A renderable component is a struct that implements the `Renderable` trait. There
10
+ //! are multiple macros that provide a better experience implementing Renderable:
11
+ //!
12
+ //! * `html!` for the JSX ergonomics
13
+ //! * `#[component]` for the syntactic-sugar of function components
14
+ //!
5
- //! ## Why this is different from `typed-html`?
15
+ //! # Why this is different from `typed-html`?
6
16
  //!
7
17
  //! `typed-html` is a wonderful library. Unfortunately, it focused its power in strictness of the HTML spec itself, and doesn't allow arbitrary compositions of custom elements.
8
18
  //!
9
19
  //! `render` takes a different approach. For now, HTML is not typed at all. It can get any key and get any string value. The main focus is custom components, so you can create a composable and declarative template with no runtime errors.
10
20
  //!
11
- //! ## Usage
21
+ //! # Usage
12
22
  //!
13
23
  //! ```rust
14
24
  //! #![feature(proc_macro_hygiene)]
@@ -16,6 +26,8 @@
16
26
  //! // A simple HTML 5 doctype declaration
17
27
  //! use render::html::HTML5Doctype;
18
28
  //! use render::{
29
+ //! // A macro to create components
30
+ //! component,
19
31
  //! // A macro to compose components in JSX fashion
20
32
  //! html,
21
33
  //! // A component that just render its children
@@ -25,28 +37,19 @@
25
37
  //! };
26
38
  //!
27
39
  //! // This can be any layout we want
28
- //! #[derive(Debug)]
29
- //! struct Page<'a, T: Renderable> {
30
- //! title: &'a str,
31
- //! children: T,
32
- //! }
33
- //!
34
- //! // Implementing `Renderable` gives the ability to compose
35
- //! // components
40
+ //! #[component]
36
- //! impl<'a, T: Renderable> Renderable for Page<'a, T> {
41
+ //! fn Page<'a, Children: Renderable>(title: &'a str, children: Children) -> String {
37
- //! fn render(self) -> String {
38
- //! html! {
42
+ //! html! {
39
- //! <Fragment>
43
+ //! <Fragment>
40
- //! <HTML5Doctype />
44
+ //! <HTML5Doctype />
41
- //! <html>
45
+ //! <html>
42
- //! <head><title>{self.title}</title></head>
46
+ //! <head><title>{title}</title></head>
43
- //! <body>
47
+ //! <body>
44
- //! {self.children}
48
+ //! {children}
45
- //! </body>
49
+ //! </body>
46
- //! </html>
50
+ //! </html>
47
- //! </Fragment>
51
+ //! </Fragment>
48
- //! }
49
- //! }
52
+ //! }
50
53
  //! }
51
54
  //!
52
55
  //! // This can be a route in Rocket, the web framework,
@@ -80,7 +83,7 @@ mod simple_element;
80
83
  mod text_element;
81
84
 
82
85
  pub use fragment::Fragment;
83
- pub use render_macros::{html, rsx};
86
+ pub use render_macros::{component, html, rsx};
84
87
  pub use renderable::Renderable;
85
88
  pub use simple_element::SimpleElement;
86
89
  pub use text_element::Raw;
render/src/renderable.rs CHANGED
@@ -88,3 +88,9 @@ impl<O: Renderable, E: Renderable> Renderable for Result<O, E> {
88
88
  }
89
89
  }
90
90
  }
91
+
92
+ impl Renderable for usize {
93
+ fn render(self) -> String {
94
+ self.to_string()
95
+ }
96
+ }
render_macros/src/function_component.rs ADDED
@@ -0,0 +1,51 @@
1
+ use proc_macro::TokenStream;
2
+ use quote::quote;
3
+ use syn::spanned::Spanned;
4
+
5
+ pub fn to_component(f: syn::ItemFn) -> TokenStream {
6
+ let struct_name = f.sig.ident;
7
+ let (impl_generics, ty_generics, where_clause) = f.sig.generics.split_for_impl();
8
+ let inputs = f.sig.inputs;
9
+ let block = f.block;
10
+ let vis = f.vis;
11
+
12
+ let inputs_block = if inputs.len() > 0 {
13
+ quote!({ #inputs })
14
+ } else {
15
+ quote!(;)
16
+ };
17
+
18
+ let inputs_reading = if inputs.len() == 0 {
19
+ quote!()
20
+ } else {
21
+ let input_names: Vec<_> = inputs
22
+ .iter()
23
+ .filter_map(|argument| match argument {
24
+ syn::FnArg::Typed(typed) => Some(typed),
25
+ syn::FnArg::Receiver(rec) => {
26
+ rec.span().unwrap().error("Don't use `self` on components");
27
+ None
28
+ }
29
+ })
30
+ .map(|value| {
31
+ let pat = &value.pat;
32
+ quote!(#pat)
33
+ })
34
+ .collect();
35
+ quote!(
36
+ let #struct_name { #(#input_names),* } = self;
37
+ )
38
+ };
39
+
40
+ TokenStream::from(quote! {
41
+ #[derive(Debug)]
42
+ #vis struct #struct_name#impl_generics #inputs_block
43
+
44
+ impl#impl_generics ::render::Renderable for #struct_name #ty_generics #where_clause {
45
+ fn render(self) -> String {
46
+ #inputs_reading
47
+ #block
48
+ }
49
+ }
50
+ })
51
+ }
render_macros/src/lib.rs CHANGED
@@ -7,6 +7,7 @@ mod children;
7
7
  mod element;
8
8
  mod element_attribute;
9
9
  mod element_attributes;
10
+ mod function_component;
10
11
  mod tags;
11
12
 
12
13
  use element::Element;
@@ -93,3 +94,57 @@ pub fn rsx(input: TokenStream) -> TokenStream {
93
94
  let result = quote! { #el };
94
95
  TokenStream::from(result)
95
96
  }
97
+
98
+ /// A syntactic sugar for implementing [`Renderable`](../render/trait.Renderable.html) conveniently
99
+ /// using functions.
100
+ ///
101
+ /// This attribute should be above a stand-alone function definition that returns a
102
+ /// [`String`](std::string::String):
103
+ ///
104
+ /// ```rust
105
+ /// # #![feature(proc_macro_hygiene)]
106
+ /// # use render_macros::{component, html};
107
+ /// #
108
+ /// #[component]
109
+ /// fn UserFn(name: String) -> String {
110
+ /// html! { <div>{format!("Hello, {}", name)}</div> }
111
+ /// }
112
+ /// ```
113
+ ///
114
+ /// Practically, this is exactly the same as using the [Renderable](../render/trait.Renderable.html) trait:
115
+ ///
116
+ /// ```rust
117
+ /// # #![feature(proc_macro_hygiene)]
118
+ /// # use render_macros::{component, html};
119
+ /// # use render::Renderable;
120
+ /// # use pretty_assertions::assert_eq;
121
+ /// #
122
+ /// #[derive(Debug)]
123
+ /// struct User { name: String }
124
+ ///
125
+ /// impl render::Renderable for User {
126
+ /// fn render(self) -> String {
127
+ /// html! { <div>{format!("Hello, {}", self.name)}</div> }
128
+ /// }
129
+ /// }
130
+ ///
131
+ /// # #[component]
132
+ /// # fn UserFn(name: String) -> String {
133
+ /// # html! { <div>{format!("Hello, {}", name)}</div> }
134
+ /// # }
135
+ /// #
136
+ /// # let from_fn = html! {
137
+ /// # <UserFn name={String::from("Schniz")} />
138
+ /// # };
139
+ /// #
140
+ /// # let from_struct = html! {
141
+ /// # <User name={String::from("Schniz")} />
142
+ /// # };
143
+ /// #
144
+ /// # assert_eq!(from_fn, from_struct);
145
+ /// ```
146
+ #[proc_macro_attribute]
147
+ pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
148
+ let f = parse_macro_input!(item as syn::ItemFn);
149
+ function_component::to_component(f)
150
+ }
render_tests/src/lib.rs CHANGED
@@ -1,7 +1,7 @@
1
1
  #![feature(proc_macro_hygiene)]
2
2
 
3
3
  use render::html::HTML5Doctype;
4
- use render::{html, rsx, Fragment, Renderable};
4
+ use render::{component, html, rsx, Fragment, Renderable};
5
5
 
6
6
  #[derive(Debug)]
7
7
  struct Hello<'a, T: Renderable> {
@@ -40,6 +40,25 @@ pub fn it_works() -> String {
40
40
  value
41
41
  }
42
42
 
43
+ #[component]
44
+ pub fn Layout<'a, Children: Renderable>(title: &'a str, children: Children) -> String {
45
+ html! {
46
+ <html>
47
+ <head><title>{title}</title></head>
48
+ <body>
49
+ {children}
50
+ </body>
51
+ </html>
52
+ }
53
+ }
54
+
55
+ #[component]
56
+ pub fn SomeComponent(name: String) -> String {
57
+ html! {
58
+ <div>{format!("Hello, {}", name)}</div>
59
+ }
60
+ }
61
+
43
62
  #[test]
44
63
  pub fn verify_works() {
45
64
  println!("{}", it_works());