leptos_macro/lib.rs
1//! Macros for use with the Leptos framework.
2
3#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
4#![forbid(unsafe_code)]
5// to prevent warnings from popping up when a nightly feature is stabilized
6#![allow(stable_features)]
7// FIXME? every use of quote! {} is warning here -- false positive?
8#![allow(unknown_lints)]
9#![allow(private_macro_use)]
10#![deny(missing_docs)]
11
12#[macro_use]
13extern crate proc_macro_error2;
14
15use component::DummyModel;
16use proc_macro::TokenStream;
17use proc_macro2::{Span, TokenTree};
18use quote::{quote, ToTokens};
19use std::str::FromStr;
20use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility};
21
22mod params;
23mod view;
24use crate::component::unmodified_fn_name_from_fn_name;
25mod component;
26mod lazy;
27mod memo;
28mod slice;
29mod slot;
30
31/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
32/// same rules as HTML, with the following differences:
33///
34/// 1. Text content should be provided as a Rust string, i.e., double-quoted:
35/// ```rust
36/// # use leptos::prelude::*;
37/// # fn test() -> impl IntoView {
38/// view! { <p>"Here’s some text"</p> }
39/// # }
40/// ```
41///
42/// 2. Self-closing tags need an explicit `/` as in XML/XHTML
43/// ```rust,compile_fail
44/// # use leptos::prelude::*;
45///
46/// # fn test() -> impl IntoView {
47/// // ❌ not like this
48/// view! { <input type="text" name="name"> }
49/// # ;
50/// # }
51/// ```
52/// ```rust
53/// # use leptos::prelude::*;
54/// # fn test() -> impl IntoView {
55/// // ✅ add that slash
56/// view! { <input type="text" name="name" /> }
57/// # }
58/// ```
59///
60/// 3. Components (functions annotated with `#[component]`) can be inserted as camel-cased tags. (Generics
61/// on components are specified as `<Component<T>/>`, not the turbofish `<Component::<T>/>`.)
62/// ```rust
63/// # use leptos::prelude::*;
64///
65/// # #[component]
66/// # fn Counter(initial_value: i32) -> impl IntoView { view! { <p></p>} }
67/// # fn test() -> impl IntoView {
68/// view! { <div><Counter initial_value=3 /></div> }
69/// # ;
70/// # }
71/// ```
72///
73/// 4. Dynamic content can be wrapped in curly braces (`{ }`) to insert text nodes, elements, or set attributes.
74/// If you insert a signal here, Leptos will create an effect to update the DOM whenever the value changes.
75/// *(“Signal” here means `Fn() -> T` where `T` is the appropriate type for that node: a `String` in case
76/// of text nodes, a `bool` for `class:` attributes, etc.)*
77///
78/// Attributes can take a wide variety of primitive types that can be converted to strings. They can also
79/// take an `Option`, in which case `Some` sets the attribute and `None` removes the attribute.
80///
81/// Note that in some cases, rust-analyzer support may be better if attribute values are surrounded with braces (`{}`).
82/// Unlike in JSX, attribute values are not required to be in braces, but braces can be used and may improve this LSP support.
83///
84/// ```rust,ignore
85/// # use leptos::prelude::*;
86///
87/// # fn test() -> impl IntoView {
88/// let (count, set_count) = create_signal(0);
89///
90/// view! {
91/// // ❌ not like this: `count.get()` returns an `i32`, not a function
92/// <p>{count.get()}</p>
93/// // ✅ this is good: Leptos sees the function and knows it's a dynamic value
94/// <p>{move || count.get()}</p>
95/// // 🔥 with the `nightly` feature, `count` is a function, so `count` itself can be passed directly into the view
96/// <p>{count}</p>
97/// }
98/// # ;
99/// # };
100/// ```
101///
102/// 5. Event handlers can be added with `on:` attributes. In most cases, the events are given the correct type
103/// based on the event name.
104/// ```rust
105/// # use leptos::prelude::*;
106/// # fn test() -> impl IntoView {
107/// view! {
108/// <button on:click=|ev| {
109/// log::debug!("click event: {ev:#?}");
110/// }>
111/// "Click me"
112/// </button>
113/// }
114/// # }
115/// ```
116///
117/// 6. DOM properties can be set with `prop:` attributes, which take any primitive type or `JsValue` (or a signal
118/// that returns a primitive or JsValue). They can also take an `Option`, in which case `Some` sets the property
119/// and `None` deletes the property.
120/// ```rust
121/// # use leptos::prelude::*;
122/// # fn test() -> impl IntoView {
123/// let (name, set_name) = create_signal("Alice".to_string());
124///
125/// view! {
126/// <input
127/// type="text"
128/// name="user_name"
129/// value={move || name.get()} // this only sets the default value!
130/// prop:value={move || name.get()} // here's how you update values. Sorry, I didn’t invent the DOM.
131/// on:click=move |ev| set_name.set(event_target_value(&ev)) // `event_target_value` is a useful little Leptos helper
132/// />
133/// }
134/// # }
135/// ```
136///
137/// 7. Classes can be toggled with `class:` attributes, which take a `bool` (or a signal that returns a `bool`).
138/// ```rust
139/// # use leptos::prelude::*;
140/// # fn test() -> impl IntoView {
141/// let (count, set_count) = create_signal(2);
142/// view! { <div class:hidden-div={move || count.get() < 3}>"Now you see me, now you don’t."</div> }
143/// # }
144/// ```
145///
146/// Class names can include dashes, and since v0.5.0 can include a dash-separated segment of only numbers.
147/// ```rust
148/// # use leptos::prelude::*;
149/// # fn test() -> impl IntoView {
150/// let (count, set_count) = create_signal(2);
151/// view! { <div class:hidden-div-25={move || count.get() < 3}>"Now you see me, now you don’t."</div> }
152/// # }
153/// ```
154///
155/// Class names cannot include special symbols.
156/// ```rust,compile_fail
157/// # use leptos::prelude::*;
158/// # fn test() -> impl IntoView {
159/// let (count, set_count) = create_signal(2);
160/// // class:hidden-[div]-25 is invalid attribute name
161/// view! { <div class:hidden-[div]-25={move || count.get() < 3}>"Now you see me, now you don’t."</div> }
162/// # }
163/// ```
164///
165/// However, you can pass arbitrary class names using the syntax `class=("name", value)`.
166/// ```rust
167/// # use leptos::prelude::*;
168/// # fn test() -> impl IntoView {
169/// let (count, set_count) = create_signal(2);
170/// // this allows you to use CSS frameworks that include complex class names
171/// view! {
172/// <div
173/// class=("is-[this_-_really]-necessary-42", move || count.get() < 3)
174/// >
175/// "Now you see me, now you don’t."
176/// </div>
177/// }
178/// # }
179/// ```
180///
181/// 8. Individual styles can also be set with `style:` or `style=("property-name", value)` syntax.
182/// ```rust
183/// # use leptos::prelude::*;
184///
185/// # fn test() -> impl IntoView {
186/// let (x, set_x) = create_signal(0);
187/// let (y, set_y) = create_signal(0);
188/// view! {
189/// <div
190/// style="position: absolute"
191/// style:left=move || format!("{}px", x.get())
192/// style:top=move || format!("{}px", y.get())
193/// style=("background-color", move || format!("rgb({}, {}, 100)", x.get(), y.get()))
194/// >
195/// "Moves when coordinates change"
196/// </div>
197/// }
198/// # }
199/// ```
200///
201/// 9. You can use the `node_ref` or `_ref` attribute to store a reference to its DOM element in a
202/// [NodeRef](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html) to use later.
203/// ```rust
204/// # use leptos::prelude::*;
205///
206/// # fn test() -> impl IntoView {
207/// use leptos::html::Input;
208///
209/// let (value, set_value) = signal(0);
210/// let my_input = NodeRef::<Input>::new();
211/// view! { <input type="text" node_ref=my_input/> }
212/// // `my_input` now contains an `Element` that we can use anywhere
213/// # ;
214/// # };
215/// ```
216///
217/// 10. You can add the same class to every element in the view by passing in a special
218/// `class = {/* ... */},` argument after ``. This is useful for injecting a class
219/// provided by a scoped styling library.
220/// ```rust
221/// # use leptos::prelude::*;
222///
223/// # fn test() -> impl IntoView {
224/// let class = "mycustomclass";
225/// view! { class = class,
226/// <div> // will have class="mycustomclass"
227/// <p>"Some text"</p> // will also have class "mycustomclass"
228/// </div>
229/// }
230/// # }
231/// ```
232///
233/// 11. You can set any HTML element’s `innerHTML` with the `inner_html` attribute on an
234/// element. Be careful: this HTML will not be escaped, so you should ensure that it
235/// only contains trusted input.
236/// ```rust
237/// # use leptos::prelude::*;
238/// # fn test() -> impl IntoView {
239/// let html = "<p>This HTML will be injected.</p>";
240/// view! {
241/// <div inner_html=html/>
242/// }
243/// # }
244/// ```
245///
246/// Here’s a simple example that shows off several of these features, put together
247/// ```rust
248/// # use leptos::prelude::*;
249/// pub fn SimpleCounter() -> impl IntoView {
250/// // create a reactive signal with the initial value
251/// let (value, set_value) = create_signal(0);
252///
253/// // create event handlers for our buttons
254/// // note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
255/// let clear = move |_ev| set_value.set(0);
256/// let decrement = move |_ev| set_value.update(|value| *value -= 1);
257/// let increment = move |_ev| set_value.update(|value| *value += 1);
258///
259/// view! {
260/// <div>
261/// <button on:click=clear>"Clear"</button>
262/// <button on:click=decrement>"-1"</button>
263/// <span>"Value: " {move || value.get().to_string()} "!"</span>
264/// <button on:click=increment>"+1"</button>
265/// </div>
266/// }
267/// }
268/// ```
269#[proc_macro_error2::proc_macro_error]
270#[proc_macro]
271#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
272pub fn view(tokens: TokenStream) -> TokenStream {
273 view_macro_impl(tokens, false)
274}
275
276/// The `template` macro behaves like [`view`](view!), except that it wraps the entire tree in a
277/// [`ViewTemplate`](https://docs.rs/leptos/0.7.0-gamma3/leptos/prelude/struct.ViewTemplate.html). This optimizes creation speed by rendering
278/// most of the view into a `<template>` tag with HTML rendered at compile time, then hydrating it.
279/// In exchange, there is a small binary size overhead.
280#[proc_macro_error2::proc_macro_error]
281#[proc_macro]
282#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
283pub fn template(tokens: TokenStream) -> TokenStream {
284 view_macro_impl(tokens, true)
285}
286
287fn view_macro_impl(tokens: TokenStream, template: bool) -> TokenStream {
288 let tokens: proc_macro2::TokenStream = tokens.into();
289 let mut tokens = tokens.into_iter();
290
291 let first = tokens.next();
292 let second = tokens.next();
293 let third = tokens.next();
294 let fourth = tokens.next();
295 let global_class = match (&first, &second) {
296 (Some(TokenTree::Ident(first)), Some(TokenTree::Punct(eq)))
297 if *first == "class" && eq.as_char() == '=' =>
298 {
299 match &fourth {
300 Some(TokenTree::Punct(comma)) if comma.as_char() == ',' => {
301 third.clone()
302 }
303 _ => {
304 abort!(
305 second, "To create a scope class with the view! macro you must put a comma `,` after the value";
306 help = r#"e.g., view!{ class="my-class", <div>...</div>}"#
307 )
308 }
309 }
310 }
311 _ => None,
312 };
313 let tokens = if global_class.is_some() {
314 tokens.collect::<proc_macro2::TokenStream>()
315 } else {
316 [first, second, third, fourth]
317 .into_iter()
318 .flatten()
319 .chain(tokens)
320 .collect()
321 };
322 let config = rstml::ParserConfig::default().recover_block(true);
323 let parser = rstml::Parser::new(config);
324 let (mut nodes, errors) = parser.parse_recoverable(tokens).split_vec();
325 let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
326 let nodes_output = view::render_view(
327 &mut nodes,
328 global_class.as_ref(),
329 normalized_call_site(proc_macro::Span::call_site()),
330 template,
331 );
332
333 // The allow lint needs to be put here instead of at the expansion of
334 // view::attribute_value(). Adding this next to the expanded expression
335 // seems to break rust-analyzer, but it works when the allow is put here.
336 let output = quote! {
337 {
338 #[allow(unused_braces)]
339 {
340 #(#errors;)*
341 #nodes_output
342 }
343 }
344 };
345
346 if template {
347 quote! {
348 ::leptos::prelude::ViewTemplate::new(#output)
349 }
350 } else {
351 output
352 }
353 .into()
354}
355
356fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
357 cfg_if::cfg_if! {
358 if #[cfg(all(debug_assertions, feature = "nightly"))] {
359 Some(leptos_hot_reload::span_to_stable_id(
360 site.file(),
361 site.start().line()
362 ))
363 } else {
364 _ = site;
365 None
366 }
367 }
368}
369
370/// This behaves like the [`view`](view!) macro, but loads the view from an external file instead of
371/// parsing it inline.
372///
373/// This is designed to allow editing views in a separate file, if this improves a user's workflow.
374///
375/// The file is loaded and parsed during proc-macro execution, and its path is resolved relative to
376/// the crate root rather than relative to the file from which it is called.
377#[proc_macro_error2::proc_macro_error]
378#[proc_macro]
379pub fn include_view(tokens: TokenStream) -> TokenStream {
380 let file_name = syn::parse::<syn::LitStr>(tokens).unwrap_or_else(|_| {
381 abort!(
382 Span::call_site(),
383 "the only supported argument is a string literal"
384 );
385 });
386 let file =
387 std::fs::read_to_string(file_name.value()).unwrap_or_else(|_| {
388 abort!(Span::call_site(), "could not open file");
389 });
390 let tokens = proc_macro2::TokenStream::from_str(&file)
391 .unwrap_or_else(|e| abort!(Span::call_site(), e));
392 view(tokens.into())
393}
394
395/// Annotates a function so that it can be used with your template as a Leptos `<Component/>`.
396///
397/// The `#[component]` macro allows you to annotate plain Rust functions as components
398/// and use them within your Leptos [view](crate::view!) as if they were custom HTML elements. The
399/// component function takes any number of other arguments. When you use the component somewhere else,
400/// the names of its arguments are the names of the properties you use in the [view](crate::view!) macro.
401///
402/// Every component function should have the return type `-> impl IntoView`.
403///
404/// You can add Rust doc comments to component function arguments and the macro will use them to
405/// generate documentation for the component.
406///
407/// Here’s how you would define and use a simple Leptos component which can accept custom properties for a name and age:
408/// ```rust
409/// # use leptos::prelude::*;
410/// use std::time::Duration;
411///
412/// #[component]
413/// fn HelloComponent(
414/// /// The user's name.
415/// name: String,
416/// /// The user's age.
417/// age: u8,
418/// ) -> impl IntoView {
419/// // create the signals (reactive values) that will update the UI
420/// let (age, set_age) = create_signal(age);
421/// // increase `age` by 1 every second
422/// set_interval(
423/// move || set_age.update(|age| *age += 1),
424/// Duration::from_secs(1),
425/// );
426///
427/// // return the user interface, which will be automatically updated
428/// // when signal values change
429/// view! {
430/// <p>"Your name is " {name} " and you are " {move || age.get()} " years old."</p>
431/// }
432/// }
433///
434/// #[component]
435/// fn App() -> impl IntoView {
436/// view! {
437/// <main>
438/// <HelloComponent name="Greg".to_string() age=32/>
439/// </main>
440/// }
441/// }
442/// ```
443///
444/// Here are some important details about how Leptos components work within the framework:
445/// * **The component function only runs once.** Your component function is not a “render” function
446/// that re-runs whenever changes happen in the state. It’s a “setup” function that runs once to
447/// create the user interface, and sets up a reactive system to update it. This means it’s okay
448/// to do relatively expensive work within the component function, as it will only happen once,
449/// not on every state change.
450///
451/// * Component names are usually in `PascalCase`. If you use a `snake_case` name, then the generated
452/// component's name will still be in `PascalCase`. This is how the framework recognizes that
453/// a particular tag is a component, not an HTML element.
454///
455/// ```
456/// # use leptos::prelude::*;
457///
458/// // PascalCase: Generated component will be called MyComponent
459/// #[component]
460/// fn MyComponent() -> impl IntoView {}
461///
462/// // snake_case: Generated component will be called MySnakeCaseComponent
463/// #[component]
464/// fn my_snake_case_component() -> impl IntoView {}
465/// ```
466///
467/// 5. You can access the children passed into the component with the `children` property, which takes
468/// an argument of the type `Children`. This is an alias for `Box<dyn FnOnce() -> AnyView<_>>`.
469/// If you need `children` to be a `Fn` or `FnMut`, you can use the `ChildrenFn` or `ChildrenFnMut`
470/// type aliases. If you want to iterate over the children, you can take `ChildrenFragment`.
471///
472/// ```
473/// # use leptos::prelude::*;
474/// #[component]
475/// fn ComponentWithChildren(children: ChildrenFragment) -> impl IntoView {
476/// view! {
477/// <ul>
478/// {children()
479/// .nodes
480/// .into_iter()
481/// .map(|child| view! { <li>{child}</li> })
482/// .collect::<Vec<_>>()}
483/// </ul>
484/// }
485/// }
486///
487/// #[component]
488/// fn WrapSomeChildren() -> impl IntoView {
489/// view! {
490/// <ComponentWithChildren>
491/// "Ooh, look at us!"
492/// <span>"We're being projected!"</span>
493/// </ComponentWithChildren>
494/// }
495/// }
496/// ```
497///
498/// ## Customizing Properties
499/// You can use the `#[prop]` attribute on individual component properties (function arguments) to
500/// customize the types that component property can receive. You can use the following attributes:
501/// * `#[prop(into)]`: This will call `.into()` on any value passed into the component prop. (For example,
502/// you could apply `#[prop(into)]` to a prop that takes
503/// [Signal](https://docs.rs/leptos/latest/leptos/struct.Signal.html), which would
504/// allow users to pass a [ReadSignal](https://docs.rs/leptos/latest/leptos/struct.ReadSignal.html) or
505/// [RwSignal](https://docs.rs/leptos/latest/leptos/struct.RwSignal.html)
506/// and automatically convert it.)
507/// * `#[prop(optional)]`: If the user does not specify this property when they use the component,
508/// it will be set to its default value. If the property type is `Option<T>`, values should be passed
509/// as `name=T` and will be received as `Some(T)`.
510/// * `#[prop(optional_no_strip)]`: The same as `optional`, but requires values to be passed as `None` or
511/// `Some(T)` explicitly. This means that the optional property can be omitted (and be `None`), or explicitly
512/// specified as either `None` or `Some(T)`.
513/// ```rust
514/// # use leptos::prelude::*;
515///
516/// #[component]
517/// pub fn MyComponent(
518/// #[prop(into)] name: String,
519/// #[prop(optional)] optional_value: Option<i32>,
520/// #[prop(optional_no_strip)] optional_no_strip: Option<i32>,
521/// ) -> impl IntoView {
522/// // whatever UI you need
523/// }
524///
525/// #[component]
526/// pub fn App() -> impl IntoView {
527/// view! {
528/// <MyComponent
529/// name="Greg" // automatically converted to String with `.into()`
530/// optional_value=42 // received as `Some(42)`
531/// optional_no_strip=Some(42) // received as `Some(42)`
532/// />
533/// <MyComponent
534/// name="Bob" // automatically converted to String with `.into()`
535/// // optional values can both be omitted, and received as `None`
536/// />
537/// }
538/// }
539/// ```
540#[proc_macro_error2::proc_macro_error]
541#[proc_macro_attribute]
542pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
543 let is_transparent = if !args.is_empty() {
544 let transparent = parse_macro_input!(args as syn::Ident);
545
546 if transparent != "transparent" {
547 abort!(
548 transparent,
549 "only `transparent` is supported";
550 help = "try `#[component(transparent)]` or `#[component]`"
551 );
552 }
553
554 true
555 } else {
556 false
557 };
558
559 component_macro(s, is_transparent, None)
560}
561
562/// Defines a component as an interactive island when you are using the
563/// `islands` feature of Leptos. Apart from the macro name,
564/// the API is the same as the [`component`](macro@component) macro.
565///
566/// When you activate the `islands` feature, every `#[component]`
567/// is server-only by default. This "default to server" behavior is important:
568/// you opt into shipping code to the client, rather than opting out. You can
569/// opt into client-side interactivity for any given component by changing from
570/// `#[component]` to `#[island]`—the two macros are otherwise identical.
571///
572/// Everything that is included inside an island will be compiled to WASM and
573/// shipped to the browser. So the key to really benefiting from this architecture
574/// is to make islands as small as possible, and include only the minimal
575/// required amount of functionality in islands themselves.
576///
577/// Only code included in an island itself is compiled to WASM. This means:
578/// 1. `children` can be provided from a server `#[component]` to an `#[island]`
579/// without the island needing to be able to hydrate them.
580/// 2. Props can be passed from the server to an island.
581///
582/// ## Present Limitations
583/// A few noteworthy limitations, at the moment:
584/// 1. `children` are completely opaque in islands. You can't iterate over `children`;
585/// in fact they're all bundled into a single `<leptos-children>` HTML element.
586/// 2. Similarly, `children` need to be used in the HTML rendered on the server.
587/// If they need to be displayed conditionally, they should be included in the HTML
588/// and rendered or not using `display: none` rather than `<Show>` or ordinary control flow.
589/// This is because the children aren't serialized at all, other than as HTML: if that
590/// HTML isn't present in the DOM, even if hidden, it is never sent and not available
591/// to the client at all.
592///
593/// ## Example
594/// ```rust,ignore
595/// use leptos::prelude::*;
596///
597/// #[component]
598/// pub fn App() -> impl IntoView {
599/// // this would panic if it ran in the browser
600/// // but because this isn't an island, it only runs on the server
601/// let file =
602/// std::fs::read_to_string("./src/is_this_a_server_component.txt")
603/// .unwrap();
604/// let len = file.len();
605///
606/// view! {
607/// <p>"The starting value for the button is the file's length."</p>
608/// // `value` is serialized and given to the island as a prop
609/// <Island value=len>
610/// // `file` is only available on the server
611/// // island props are projected in, so we can nest
612/// // server-only content inside islands inside server content etc.
613/// <p>{file}</p>
614/// </Island>
615/// }
616/// }
617///
618/// #[island]
619/// pub fn Island(
620/// #[prop(into)] value: RwSignal<usize>,
621/// children: Children,
622/// ) -> impl IntoView {
623/// // because `RwSignal<T>` implements `From<T>`, we can pass in a plain
624/// // value and use it as the starting value of a signal here
625/// view! {
626/// <button on:click=move |_| value.update(|n| *n += 1)>
627/// {value}
628/// </button>
629/// {children()}
630/// }
631/// }
632/// ```
633#[proc_macro_error2::proc_macro_error]
634#[proc_macro_attribute]
635pub fn island(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
636 let is_transparent = if !args.is_empty() {
637 let transparent = parse_macro_input!(args as syn::Ident);
638
639 if transparent != "transparent" {
640 abort!(
641 transparent,
642 "only `transparent` is supported";
643 help = "try `#[island(transparent)]` or `#[island]`"
644 );
645 }
646
647 true
648 } else {
649 false
650 };
651
652 let island_src = s.to_string();
653 component_macro(s, is_transparent, Some(island_src))
654}
655
656fn component_macro(
657 s: TokenStream,
658 is_transparent: bool,
659 island: Option<String>,
660) -> TokenStream {
661 let mut dummy = syn::parse::<DummyModel>(s.clone());
662 let parse_result = syn::parse::<component::Model>(s);
663
664 if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
665 let expanded = model.is_transparent(is_transparent).with_island(island).into_token_stream();
666 if !matches!(unexpanded.vis, Visibility::Public(_)) {
667 unexpanded.vis = Visibility::Public(Pub {
668 span: unexpanded.vis.span(),
669 })
670 }
671 unexpanded.sig.ident =
672 unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
673 quote! {
674 #expanded
675
676 #[doc(hidden)]
677 #[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
678 #unexpanded
679 }
680 } else {
681 match dummy {
682 Ok(mut dummy) => {
683 dummy.sig.ident = unmodified_fn_name_from_fn_name(&dummy.sig.ident);
684 quote! {
685 #[doc(hidden)]
686 #[allow(non_snake_case, dead_code, clippy::too_many_arguments, clippy::needless_lifetimes)]
687 #dummy
688 }
689 }
690 Err(e) => {
691 proc_macro_error2::abort!(e.span(), e);
692 }
693 }
694 }.into()
695}
696
697/// Annotates a struct so that it can be used with your Component as a `slot`.
698///
699/// The `#[slot]` macro allows you to annotate plain Rust struct as component slots and use them
700/// within your Leptos [`component`](macro@crate::component) properties. The struct can contain any number
701/// of fields. When you use the component somewhere else, the names of the slot fields are the
702/// names of the properties you use in the [view](crate::view!) macro.
703///
704/// Here’s how you would define and use a simple Leptos component which can accept a custom slot:
705/// ```rust
706/// # use leptos::prelude::*;
707/// use std::time::Duration;
708///
709/// #[slot]
710/// struct HelloSlot {
711/// // Same prop syntax as components.
712/// #[prop(optional)]
713/// children: Option<Children>,
714/// }
715///
716/// #[component]
717/// fn HelloComponent(
718/// /// Component slot, should be passed through the <HelloSlot slot> syntax.
719/// hello_slot: HelloSlot,
720/// ) -> impl IntoView {
721/// hello_slot.children.map(|children| children())
722/// }
723///
724/// #[component]
725/// fn App() -> impl IntoView {
726/// view! {
727/// <HelloComponent>
728/// <HelloSlot slot>
729/// "Hello, World!"
730/// </HelloSlot>
731/// </HelloComponent>
732/// }
733/// }
734/// ```
735///
736/// /// Here are some important details about how slots work within the framework:
737/// 1. Most of the same rules from [`macro@component`] macro should also be followed on slots.
738///
739/// 2. Specifying only `slot` without a name (such as in `<HelloSlot slot>`) will default the chosen slot to
740/// the a snake case version of the slot struct name (`hello_slot` for `<HelloSlot>`).
741///
742/// 3. Event handlers cannot be specified directly on the slot.
743///
744/// ```compile_error
745/// // ❌ This won't work
746/// # use leptos::prelude::*;
747///
748/// #[slot]
749/// struct SlotWithChildren {
750/// children: Children,
751/// }
752///
753/// #[component]
754/// fn ComponentWithSlot(slot: SlotWithChildren) -> impl IntoView {
755/// (slot.children)()
756/// }
757///
758/// #[component]
759/// fn App() -> impl IntoView {
760/// view! {
761/// <ComponentWithSlot>
762/// <SlotWithChildren slot:slot on:click=move |_| {}>
763/// <h1>"Hello, World!"</h1>
764/// </SlotWithChildren>
765/// </ComponentWithSlot>
766/// }
767/// }
768/// ```
769///
770/// ```
771/// // ✅ Do this instead
772/// # use leptos::prelude::*;
773///
774/// #[slot]
775/// struct SlotWithChildren {
776/// children: Children,
777/// }
778///
779/// #[component]
780/// fn ComponentWithSlot(slot: SlotWithChildren) -> impl IntoView {
781/// (slot.children)()
782/// }
783///
784/// #[component]
785/// fn App() -> impl IntoView {
786/// view! {
787/// <ComponentWithSlot>
788/// <SlotWithChildren slot:slot>
789/// <div on:click=move |_| {}>
790/// <h1>"Hello, World!"</h1>
791/// </div>
792/// </SlotWithChildren>
793/// </ComponentWithSlot>
794/// }
795/// }
796/// ```
797#[proc_macro_error2::proc_macro_error]
798#[proc_macro_attribute]
799pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
800 if !args.is_empty() {
801 abort!(
802 Span::call_site(),
803 "no arguments are supported";
804 help = "try just `#[slot]`"
805 );
806 }
807
808 parse_macro_input!(s as slot::Model)
809 .into_token_stream()
810 .into()
811}
812
813/// Declares that a function is a [server function](https://docs.rs/server_fn/latest/server_fn/index.html).
814/// This means that its body will only run on the server, i.e., when the `ssr` feature on this crate is enabled.
815///
816/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
817/// are enabled), it will instead make a network request to the server.
818///
819/// ## Named Arguments
820///
821/// You can provide any combination of the following named arguments:
822/// - `name`: sets the identifier for the server function’s type, which is a struct created
823/// to hold the arguments (defaults to the function identifier in PascalCase)
824/// - `prefix`: a prefix at which the server function handler will be mounted (defaults to `/api`)
825/// your prefix must begin with `/`. Otherwise your function won't be found.
826/// - `endpoint`: specifies the exact path at which the server function handler will be mounted,
827/// relative to the prefix (defaults to the function name followed by unique hash)
828/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
829/// - `output`: the encoding for the response (defaults to `Json`)
830/// - `client`: a custom `Client` implementation that will be used for this server fn
831/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
832/// of the following (not case sensitive)
833/// - `"Url"`: `POST` request with URL-encoded arguments and JSON response
834/// - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
835/// - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
836/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
837/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
838/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
839/// - `impl_from`: specifies whether to implement trait `From` for server function's type or not.
840/// By default, if a server function only has one argument, the macro automatically implements the `From` trait
841/// to convert from the argument type to the server function type, and vice versa, allowing you to convert
842/// between them easily. Setting `impl_from` to `false` disables this, which can be necessary for argument types
843/// for which this would create a conflicting implementation. (defaults to `true`)
844///
845/// ```rust,ignore
846/// #[server(
847/// name = SomeStructName,
848/// prefix = "/my_api",
849/// endpoint = "my_fn",
850/// input = Cbor,
851/// output = Json
852/// impl_from = true
853/// )]
854/// pub async fn my_wacky_server_fn(input: Vec<String>) -> Result<usize, ServerFnError> {
855/// todo!()
856/// }
857/// ```
858///
859/// ## Server Function Encodings
860///
861/// Server functions are designed to allow a flexible combination of `input` and `output` encodings, the set
862/// of which can be found in the [`server_fn::codec`](../server_fn/codec/index.html) module.
863///
864/// The serialization/deserialization process for server functions consists of a series of steps,
865/// each of which is represented by a different trait:
866/// 1. [`IntoReq`](../server_fn/codec/trait.IntoReq.html): The client serializes the [`ServerFn`](../server_fn/trait.ServerFn.html) argument type into an HTTP request.
867/// 2. The [`Client`](../server_fn/client/trait.Client.html) sends the request to the server.
868/// 3. [`FromReq`](../server_fn/codec/trait.FromReq.html): The server deserializes the HTTP request back into the [`ServerFn`](../server_fn/client/trait.Client.html) type.
869/// 4. The server calls calls [`ServerFn::run_body`](../server_fn/trait.ServerFn.html#tymethod.run_body) on the data.
870/// 5. [`IntoRes`](../server_fn/codec/trait.IntoRes.html): The server serializes the [`ServerFn::Output`](../server_fn/trait.ServerFn.html#associatedtype.Output) type into an HTTP response.
871/// 6. The server integration applies any middleware from [`ServerFn::middleware`](../server_fn/middleware/index.html) and responds to the request.
872/// 7. [`FromRes`](../server_fn/codec/trait.FromRes.html): The client deserializes the response back into the [`ServerFn::Output`](../server_fn/trait.ServerFn.html#associatedtype.Output) type.
873///
874/// Whatever encoding is provided to `input` should implement `IntoReq` and `FromReq`. Whatever encoding is provided
875/// to `output` should implement `IntoRes` and `FromRes`.
876///
877/// ## Default Values for Parameters
878///
879/// Individual function parameters can be annotated with `#[server(default)]`, which will pass
880/// through `#[serde(default)]`. This is useful for the empty values of arguments with some
881/// encodings. The URL encoding, for example, omits a field entirely if it is an empty `Vec<_>`,
882/// but this causes a deserialization error: the correct solution is to add `#[server(default)]`.
883/// ```rust,ignore
884/// pub async fn with_default_value(#[server(default)] values: Vec<u32>) /* etc. */
885/// ```
886///
887/// ## Important Notes
888/// - **Server functions must be `async`.** Even if the work being done inside the function body
889/// can run synchronously on the server, from the client’s perspective it involves an asynchronous
890/// function call.
891/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
892/// inside the function body can’t fail, the processes of serialization/deserialization and the
893/// network call are fallible.
894/// - [`ServerFnError`](../server_fn/error/enum.ServerFnError.html) can be generic over some custom error type. If so, that type should implement
895/// [`FromStr`](std::str::FromStr) and [`Display`](std::fmt::Display), but does not need to implement [`Error`](std::error::Error). This is so the value
896/// can be easily serialized and deserialized along with the result.
897/// - **Server functions are part of the public API of your application.** A server function is an
898/// ad hoc HTTP API endpoint, not a magic formula. Any server function can be accessed by any HTTP
899/// client. You should take care to sanitize any data being returned from the function to ensure it
900/// does not leak data that should exist only on the server.
901/// - **Server functions can’t be generic.** Because each server function creates a separate API endpoint,
902/// it is difficult to monomorphize. As a result, server functions cannot be generic (for now?) If you need to use
903/// a generic function, you can define a generic inner function called by multiple concrete server functions.
904/// - **Arguments and return types must be serializable.** We support a variety of different encodings,
905/// but one way or another arguments need to be serialized to be sent to the server and deserialized
906/// on the server, and the return type must be serialized on the server and deserialized on the client.
907/// This means that the set of valid server function argument and return types is a subset of all
908/// possible Rust argument and return types. (i.e., server functions are strictly more limited than
909/// ordinary functions.)
910/// - **Context comes from the server.** Server functions are provided access to the HTTP request and other relevant
911/// server data via the server integrations, but they do *not* have access to reactive state that exists in the client.
912/// - Your server must be ready to handle the server functions at the API prefix you list. The easiest way to do this
913/// is to use the `handle_server_fns` function from [`leptos_actix`](https://docs.rs/leptos_actix/latest/leptos_actix/fn.handle_server_fns.html)
914/// or [`leptos_axum`](https://docs.rs/leptos_axum/latest/leptos_axum/fn.handle_server_fns.html).
915/// - **Server functions must have unique paths**. Unique paths are automatically generated for each
916/// server function. If you choose to specify a path in the fourth argument, you must ensure that these
917/// are unique. You cannot define two server functions with the same URL prefix and endpoint path,
918/// even if they have different URL encodings, e.g. a POST method at `/api/foo` and a GET method at `/api/foo`.
919#[proc_macro_attribute]
920#[proc_macro_error]
921pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
922 match server_fn_macro::server_macro_impl(
923 args.into(),
924 s.into(),
925 Some(syn::parse_quote!(::leptos::server_fn)),
926 "/api",
927 None,
928 None,
929 ) {
930 Err(e) => e.to_compile_error().into(),
931 Ok(s) => s.to_token_stream().into(),
932 }
933}
934
935/// Derives a trait that parses a map of string keys and values into a typed
936/// data structure, e.g., for route params.
937#[proc_macro_derive(Params)]
938pub fn params_derive(
939 input: proc_macro::TokenStream,
940) -> proc_macro::TokenStream {
941 match syn::parse(input) {
942 Ok(ast) => params::params_impl(&ast),
943 Err(err) => err.to_compile_error().into(),
944 }
945}
946
947/// Generates a `slice` into a struct with a default getter and setter.
948///
949/// Can be used to access deeply nested fields within a global state object.
950///
951/// ```rust
952/// # use leptos::prelude::*;
953/// # use leptos_macro::slice;
954///
955/// #[derive(Default)]
956/// pub struct Outer {
957/// count: i32,
958/// inner: Inner,
959/// }
960///
961/// #[derive(Default)]
962/// pub struct Inner {
963/// inner_count: i32,
964/// inner_name: String,
965/// }
966///
967/// let outer_signal = RwSignal::new(Outer::default());
968///
969/// let (count, set_count) = slice!(outer_signal.count);
970///
971/// let (inner_count, set_inner_count) = slice!(outer_signal.inner.inner_count);
972/// let (inner_name, set_inner_name) = slice!(outer_signal.inner.inner_name);
973/// ```
974#[proc_macro]
975pub fn slice(input: TokenStream) -> TokenStream {
976 slice::slice_impl(input)
977}
978
979/// Generates a `memo` into a struct with a default getter.
980///
981/// Can be used to access deeply nested fields within a global state object.
982///
983/// ```rust
984/// # use leptos::prelude::*;
985/// # use leptos_macro::memo;
986///
987/// #[derive(Default)]
988/// pub struct Outer {
989/// count: i32,
990/// inner: Inner,
991/// }
992///
993/// #[derive(Default)]
994/// pub struct Inner {
995/// inner_count: i32,
996/// inner_name: String,
997/// }
998///
999/// let outer_signal = RwSignal::new(Outer::default());
1000///
1001/// let count = memo!(outer_signal.count);
1002///
1003/// let inner_count = memo!(outer_signal.inner.inner_count);
1004/// let inner_name = memo!(outer_signal.inner.inner_name);
1005/// ```
1006#[proc_macro]
1007pub fn memo(input: TokenStream) -> TokenStream {
1008 memo::memo_impl(input)
1009}
1010
1011/// The `#[lazy]` macro marks an `async` function as a function that can be lazy-loaded from a
1012/// separate (WebAssembly) binary.
1013///
1014/// The first time the function is called, calling the function will first load that other binary,
1015/// then call the function. On subsequent call it will be called immediately, but still return
1016/// asynchronously to maintain the same API.
1017///
1018/// All parameters and output types should be concrete types, with no generics.
1019#[proc_macro_attribute]
1020#[proc_macro_error]
1021pub fn lazy(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
1022 lazy::lazy_impl(args, s)
1023}