wasm_react/
macros.rs

1/// A convenience macro to [`create_element()`](crate::create_element()) for
2/// creating HTML element nodes.
3///
4/// Returns an [`H<HtmlTag>`](crate::props::H) struct that provides
5/// auto-completion for HTML attributes and events.
6///
7/// # Example
8///
9/// ```
10/// # use wasm_react::*;
11/// # fn f() -> VNode {
12/// h!(div)
13///   .attr("id", &"app".into())
14///   .build(
15///     h!(h1).build("Hello World!")
16///   )
17/// # }
18///
19/// // <div id="app"><h1>Hello World!</h1></div>
20///
21/// # fn g() -> VNode {
22/// h!("web-component")
23///   .build("Hello World!")
24/// # }
25///
26/// // <web-component>Hello World!</web-component>
27/// ```
28///
29/// It is also possible to add an id and/or classes to the element using a terse
30/// notation. You can use the same syntax as [`classnames!`](crate::classnames!).
31///
32/// ```
33/// # use wasm_react::*;
34/// # fn f() -> VNode {
35/// h!(div[#"app"."some-class"."warning"])
36///   .build("This is a warning!")
37/// # }
38///
39/// // <div id="app" class="some-class warning">This is a warning!</div>
40/// ```
41#[macro_export]
42macro_rules! h {
43  ($tag:literal $( [$( #$id:literal )? $( .$( $classnames:tt )+ )?] )?) => {
44    $crate::props::H::new($crate::props::HtmlTag($tag)) $(
45      $( .id($id) )?
46      $( .class_name(&$crate::classnames![.$( $classnames )+]) )?
47    )?
48  };
49  ($tag:ident $( [$( #$id:literal )? $( .$( $classnames:tt )+ )?] )?) => {
50    $crate::props::H::new($crate::props::HtmlTag(stringify!($tag))) $(
51      $( .id($id) )?
52      $( .class_name(&$crate::classnames![.$( $classnames )+]) )?
53    )?
54  };
55}
56
57/// A helper macro which can be used to clone a list of variables. Helpful for
58/// creating a closure which clone-captures the environment.
59///
60/// # Example
61///
62/// ```
63/// # use wasm_react::{*, hooks::*};
64/// # struct C { message: &'static str }
65/// # impl C { fn f(&self) {
66/// let switch = use_state(|| true);
67/// let counter = use_state(|| 0);
68///
69/// let cb = Callback::new({
70///   clones!(self.message, switch, mut counter);
71///
72///   move |delta: i32| {
73///     if (*switch.value()) {
74///       println!("{}", message);
75///     }
76///
77///     counter.set(|c| c + delta);
78///   }
79/// });
80/// # }}
81/// ```
82///
83/// This is equivalent to the following:
84///
85/// ```
86/// # use wasm_react::{*, hooks::*};
87/// # struct C { message: &'static str }
88/// # impl C { fn f(&self) {
89/// let switch = use_state(|| true);
90/// let counter = use_state(|| 0);
91///
92/// let cb = Callback::new({
93///   let message = self.message.clone();
94///   let switch = switch.clone();
95///   let mut counter = counter.clone();
96///
97///   move |delta: i32| {
98///     if (*switch.value()) {
99///       println!("{}", message);
100///     }
101///
102///     counter.set(|c| c + delta);
103///   }
104/// });
105/// # }}
106/// ```
107#[macro_export]
108macro_rules! clones {
109  (@clones $(,)? mut $obj:ident.$id:ident $( $tail:tt )*) => {
110    let mut $id = $obj.$id.clone();
111    $crate::clones!(@clones $( $tail )*);
112  };
113  (@clones $(,)? $obj:ident.$id:ident $( $tail:tt )*) => {
114    let $id = $obj.$id.clone();
115    $crate::clones!(@clones $( $tail )*);
116  };
117  (@clones $(,)? mut $id:ident $( $tail:tt )*) => {
118    let mut $id = $id.clone();
119    $crate::clones!(@clones $( $tail )*);
120  };
121  (@clones $(,)? $id:ident $( $tail:tt )*) => {
122    let $id = $id.clone();
123    $crate::clones!(@clones $( $tail )*);
124  };
125  (@clones) => {};
126
127  ($( $tt:tt )*) => {
128    $crate::clones!(@clones $( $tt )*);
129  };
130}
131
132/// Constructs a [`String`] based on various types that implement
133/// [`Classnames`](crate::props::Classnames).
134///
135/// # Example
136///
137/// ```
138/// # use wasm_react::*;
139/// assert_eq!(
140///   classnames![."button"."blue"],
141///   "button blue ".to_string(),
142/// );
143///
144/// let blue = false;
145/// let disabled = true;
146///
147/// assert_eq!(
148///   classnames![."button".blue.disabled],
149///   "button disabled ".to_string(),
150/// );
151///
152/// let is_blue = Some("blue");
153/// let disabled = "disabled".to_string();
154///
155/// assert_eq!(
156///   classnames![."button".{is_blue}.{disabled}],
157///   "button blue disabled ",
158/// );
159/// ```
160#[macro_export]
161macro_rules! classnames {
162  [@single $result:ident <<] => {};
163
164  // Handle string literals
165  [@single $result:ident << .$str:literal $( $tail:tt )*] => {
166    $crate::props::Classnames::append_to(&$str, &mut $result);
167    $crate::classnames![@single $result << $( $tail ) *];
168  };
169
170  // Handle boolean variables
171  [@single $result:ident << .$bool:ident $( $tail:tt )*] => {
172    $crate::props::Classnames::append_to(
173      &$bool.then(|| stringify!($bool)),
174      &mut $result
175    );
176    $crate::classnames![@single $result << $( $tail ) *];
177  };
178
179  // Handle block expressions
180  [@single $result:ident << .$block:block $( $tail:tt )*] => {
181    $crate::props::Classnames::append_to(&$block, &mut $result);
182    $crate::classnames![@single $result << $( $tail ) *];
183  };
184
185  [] => {
186    ::std::string::String::new()
187  };
188  [$( $tt:tt )*] => {
189    {
190      let mut result = ::std::string::String::new();
191      $crate::classnames![@single result << $( $tt )*];
192      result
193    }
194  };
195}
196
197/// This macro can be used to expose your [`Component`](crate::Component) for JS
198/// consumption via `wasm-bindgen`.
199///
200/// Requirement is that you implement the [`TryFrom<JsValue, Error = JsValue>`](core::convert::TryFrom)
201/// trait on your component and that you do not export anything else that has
202/// the same name as your component.
203///
204/// Therefore, it is only recommended to use this macro if you're writing a
205/// library for JS consumption only, or if you're writing a standalone
206/// application, since this will pollute the export namespace, which isn't
207/// desirable if you're writing a library for Rust consumption only.
208///
209/// # Example
210///
211/// Implement [`TryFrom<JsValue, Error = JsValue>`](core::convert::TryFrom) on
212/// your component and export it:
213///
214/// ```
215/// # use wasm_react::*;
216/// # use wasm_bindgen::prelude::*;
217/// # use js_sys::Reflect;
218/// #
219/// pub struct Counter {
220///   counter: i32,
221/// }
222///
223/// impl Component for Counter {
224///   # fn render(&self) -> VNode { VNode::new() }
225///   /* … */
226/// }
227///
228/// impl TryFrom<JsValue> for Counter {
229///   type Error = JsValue;
230///
231///   fn try_from(value: JsValue) -> Result<Self, Self::Error> {
232///     let diff = Reflect::get(&value, &"counter".into())?
233///       .as_f64()
234///       .ok_or(JsError::new("`counter` property not found"))?;
235///
236///     Ok(Counter { counter: diff as i32 })
237///   }
238/// }
239///
240/// export_components! { Counter }
241/// ```
242///
243/// In JS, you can use it like any other component:
244///
245/// ```js
246/// import React from "react";
247/// import init, { Counter } from "./path/to/pkg/project.js";
248///
249/// function SomeOtherJsComponent(props) {
250///   return (
251///     <div>
252///       <Counter counter={0} />
253///     </div>
254///   );
255/// }
256/// ```
257///
258/// You can export multiple components and also rename them:
259///
260/// ```
261/// # use wasm_react::*;
262/// # use wasm_bindgen::prelude::*;
263/// # pub struct App; pub struct Counter;
264/// # impl Component for App { fn render(&self) -> VNode { VNode::new() } }
265/// # impl TryFrom<JsValue> for App {
266/// #   type Error = JsValue;
267/// #   fn try_from(_: JsValue) -> Result<Self, Self::Error> { todo!() }
268/// # }
269/// # impl Component for Counter { fn render(&self) -> VNode { VNode::new() } }
270/// # impl TryFrom<JsValue> for Counter {
271/// #   type Error = JsValue;
272/// #   fn try_from(_: JsValue) -> Result<Self, Self::Error> { todo!() }
273/// # }
274/// export_components! {
275///   /// Some doc comment for the exported component.
276///   App as CounterApp,
277///   Counter
278/// }
279/// ```
280#[macro_export]
281macro_rules! export_components {
282  {} => {};
283  {
284    $( #[$meta:meta] )*
285    $Component:ident $( , $( $tail:tt )* )?
286  } => {
287    $crate::export_components! {
288      $( #[$meta] )*
289      $Component as $Component $( , $( $tail )* )?
290    }
291  };
292  {
293    $( #[$meta:meta] )*
294    $Component:ty as $Name:ident $( , $( $tail:tt )* )?
295  } => {
296    $crate::paste! {
297      $( #[$meta] )*
298      #[allow(non_snake_case)]
299      #[allow(dead_code)]
300      #[doc(hidden)]
301      #[::wasm_bindgen::prelude::wasm_bindgen(js_name = $Name)]
302      pub fn [<__WasmReact_Export_ $Name>](
303        props: ::wasm_bindgen::JsValue,
304      ) -> ::wasm_bindgen::JsValue
305      where
306        $Component: $crate::Component
307          + TryFrom<::wasm_bindgen::JsValue, Error = ::wasm_bindgen::JsValue>
308      {
309        let component_ref = $crate::hooks::use_memo({
310          let props = props.clone();
311
312          move || $Component::try_from(props).unwrap()
313        }, $crate::hooks::Deps::some(props));
314
315        $crate::react_bindings::use_rust_tmp_refs();
316
317        let component = component_ref.value();
318        $crate::Component::render(&*component).into()
319      }
320    }
321
322    $( $crate::export_components! { $( $tail )* } )?
323  };
324}
325
326/// This macro can be used to import JS React components for Rust consumption
327/// via `wasm-bindgen`.
328///
329/// Make sure that the components you import use the same React runtime as
330/// specified for `wasm-react`.
331///
332/// # Example
333///
334/// Assume the JS components are defined and exported in `/.dummy/myComponents.js`:
335///
336/// ```js
337/// import "https://unpkg.com/react/umd/react.production.min.js";
338///
339/// export function MyComponent(props) { /* … */ }
340/// export function PublicComponent(props) { /* … */ }
341/// export function RenamedComponent(props) { /* … */ }
342/// ```
343///
344/// Then you can import them using `import_components!`:
345///
346/// ```
347/// # use wasm_react::*;
348/// # use wasm_bindgen::prelude::*;
349/// import_components! {
350///   #[wasm_bindgen(module = "/.dummy/myComponents.js")]
351///
352///   /// Some doc comment for the imported component.
353///   MyComponent,
354///   /// This imported component will be made public.
355///   pub PublicComponent,
356///   /// You can rename imported components.
357///   RenamedComponent as pub OtherComponent,
358/// }
359/// ```
360///
361/// Now you can include the imported components in your render function:
362///
363/// ```
364/// # use wasm_react::{*, props::*};
365/// # use wasm_bindgen::prelude::*;
366/// # import_components! { #[wasm_bindgen(inline_js = "")] MyComponent }
367/// # struct App;
368/// # impl Component for App {
369/// fn render(&self) -> VNode {
370///   h!(div).build(
371///     MyComponent::new()
372///       .attr("prop", &"Hello World!".into())
373///       .build(())
374///   )
375/// }
376/// # }
377/// ```
378///
379/// # Defining Custom Convenience Methods
380///
381/// `MyComponent::new()` returns an [`H<MyComponent>`](crate::props::H) which
382/// can be used to define convenience methods by using a new extension trait:
383///
384/// ```
385/// # use wasm_react::{*, props::*};
386/// # use wasm_bindgen::prelude::*;
387/// # import_components! { #[wasm_bindgen(inline_js = "")] MyComponent }
388/// trait HMyComponentExt {
389///   fn prop(self, value: &str) -> Self;
390/// }
391///
392/// impl HMyComponentExt for H<MyComponent> {
393///   fn prop(self, value: &str) -> Self {
394///     self.attr("prop", &value.into())
395///   }
396/// }
397///
398/// /* … */
399///
400/// # struct App;
401/// # impl Component for App {
402/// fn render(&self) -> VNode {
403///   h!(div).build(
404///     MyComponent::new()
405///       .prop("Hello World!")
406///       .build(())
407///   )
408/// }
409/// # }
410/// ```
411#[macro_export]
412macro_rules! import_components {
413  { #[$from:meta] } => {};
414  {
415    #[$from:meta]
416    $( #[$meta:meta] )*
417    $vis:vis $Component:ident $( , $( $tail:tt )* )?
418  } => {
419    $crate::import_components! {
420      #[$from]
421      $( #[$meta] )*
422      $Component as $vis $Component $( , $( $tail )* )?
423    }
424  };
425  {
426    #[$from:meta]
427    $( #[$meta:meta] )*
428    $Component:ident as $vis:vis $Name:ident $( , $( $tail:tt )* )?
429  } => {
430    $crate::paste! {
431      #[$from]
432      extern "C" {
433        #[wasm_bindgen::prelude::wasm_bindgen(js_name = $Component)]
434        static [<__WASMREACT_IMPORT_ $Name:upper>]: wasm_bindgen::JsValue;
435      }
436
437      $( #[$meta] )*
438      #[derive(Debug, Clone, Copy)]
439      $vis struct $Name;
440
441      impl $Name {
442        #[doc = "Returns an `H<" $Name ">` struct that provides convenience "
443                "methods for adding props."]
444        pub fn new() -> $crate::props::H<$Name> {
445          $crate::props::H::new($Name)
446        }
447      }
448
449      impl $crate::props::HType for $Name {
450        fn as_js(&self) -> std::borrow::Cow<'_, JsValue> {
451          std::borrow::Cow::Borrowed(&[<__WASMREACT_IMPORT_ $Name:upper>])
452        }
453      }
454    }
455
456    $( $crate::import_components! { #[$from] $( $tail )* } )?
457  };
458}