diff --git a/leptos_dom/Cargo.toml b/leptos_dom/Cargo.toml index a39a82f02..24dee8571 100644 --- a/leptos_dom/Cargo.toml +++ b/leptos_dom/Cargo.toml @@ -70,11 +70,12 @@ features = [ ] [dev-dependencies] +leptos = { path = "../leptos", default-features = false, version = "0.0" } leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0" } [features] -csr = ["leptos_reactive/csr", "leptos_macro/csr"] -hydrate = ["leptos_reactive/hydrate", "leptos_macro/hydrate"] -ssr = ["leptos_reactive/ssr", "leptos_macro/ssr"] -stable = ["leptos_reactive/stable", "leptos_macro/stable"] +csr = ["leptos_reactive/csr", "leptos_macro/csr", "leptos/csr"] +hydrate = ["leptos_reactive/hydrate", "leptos_macro/hydrate", "leptos/hydrate"] +ssr = ["leptos_reactive/ssr", "leptos_macro/ssr", "leptos/ssr"] +stable = ["leptos_reactive/stable", "leptos_macro/stable", "leptos/stable"] diff --git a/leptos_dom/src/lib.rs b/leptos_dom/src/lib.rs index 807ad8c40..26120dc36 100644 --- a/leptos_dom/src/lib.rs +++ b/leptos_dom/src/lib.rs @@ -140,3 +140,15 @@ macro_rules! is_dev { cfg!(debug_assertions) }; } + +#[doc(hidden)] +pub fn __leptos_renderer_error(expected: &'static str, location: &'static str) -> web_sys::Node { + cfg_if! { + if #[cfg(debug_assertions)] { + panic!("Yikes! Something went wrong while Leptos was trying to traverse the DOM to set up the reactive system.\n\nThe renderer expected {expected:?} as {location} and couldn't get it.\n\nThis is almost certainly a bug in the framework, not your application. Please open an issue on GitHub and provide example code if possible.\n\nIn the meantime, these bugs are often related to s or {{block}}s when they are siblings of each other. Try wrapping those in a or
for now. Sorry for the pain!") + } else { + _ = expected; + panic!("Renderer error. You can find a more detailed error message if you compile in debug mode.") + } + } +} diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index c5616b8ce..8efd1b3e4 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -244,6 +244,60 @@ pub fn view(tokens: TokenStream) -> TokenStream { } } +/// Annotates a function so that it can be used with your template as a +/// +/// Here are some things you should know. +/// 1. The component name should be `CamelCase` instead of `snake_case`. This is how the renderer +/// recognizes that a particular tag is a component, not an HTML element. +/// +/// ``` +/// # use leptos::*; +/// // ❌ not snake_case +/// #[component] +/// fn my_component(cx: Scope) -> Element { todo!() } +/// +/// // ✅ CamelCase +/// #[component] +/// fn MyComponent(cx: Scope) -> Element { todo!() } +/// ``` +/// +/// 2. The macro generates a type `ComponentProps` for every `Component` (so, `HomePage` generates `HomePageProps`, +/// `Button` generates `ButtonProps`, etc.) When you’re importing the component, you also need to **explicitly import +/// the prop type.** +/// +/// ``` +/// # use leptos::*; +/// +/// use component::{MyComponent, MyComponentProps}; +/// +/// mod component { +/// use leptos::*; +/// +/// #[component] +/// pub fn MyComponent(cx: Scope) -> Element { todo!() } +/// } +/// ``` +/// +/// 3. You can pass generic arguments, but they should be defined in a `where` clause and not inline. +/// +/// ```compile_error +/// # use leptos::*; +/// #[component] +/// fn MyComponent Element>(cx: Scope, render_prop: T) -> Element { +/// todo!() +/// } +/// ``` +/// +/// ``` +/// # use leptos::*; +/// #[component] +/// fn MyComponent(cx: Scope, render_prop: T) -> Element +/// where +/// T: Fn() -> Element, +/// { +/// todo!() +/// } +/// ``` #[proc_macro_attribute] pub fn component(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { match syn::parse::(s) { diff --git a/leptos_macro/src/view.rs b/leptos_macro/src/view.rs index 56bc18a90..3de95801a 100644 --- a/leptos_macro/src/view.rs +++ b/leptos_macro/src/view.rs @@ -383,14 +383,14 @@ fn element_to_tokens( quote_spanned! { span => let #this_el_ident = #debug_name; //log::debug!("next_sibling ({})", #debug_name); - let #this_el_ident = #prev_sib.next_sibling().unwrap_throw(); + let #this_el_ident = #prev_sib.next_sibling().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#debug_name, "nextSibling")); //log::debug!("=> got {}", #this_el_ident.node_name()); } } else { quote_spanned! { span => let #this_el_ident = #debug_name; //log::debug!("first_child ({})", #debug_name); - let #this_el_ident = #parent.first_child().unwrap_throw(); + let #this_el_ident = #parent.first_child().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#debug_name, "firstChild")); //log::debug!("=> got {}", #this_el_ident.node_name()); } }; @@ -816,13 +816,13 @@ fn block_to_tokens( let location = if let Some(sibling) = &prev_sib { quote_spanned! { span => //log::debug!("-> next sibling"); - let #name = #sibling.next_sibling().unwrap_throw(); + let #name = #sibling.next_sibling().unwrap_or_else(|| ::leptos::__leptos_renderer_error("{block}", "nextSibling")); //log::debug!("\tnext sibling = {}", #name.node_name()); } } else { quote_spanned! { span => //log::debug!("\\|/ first child on {}", #parent.node_name()); - let #name = #parent.first_child().unwrap_throw(); + let #name = #parent.first_child().unwrap_or_else(|| ::leptos::__leptos_renderer_error("{block}", "firstChild")); //log::debug!("\tfirst child = {}", #name.node_name()); } }; @@ -938,6 +938,8 @@ fn component_to_tokens( mode: Mode, is_first_child: bool, ) -> PrevSibChange { + let component_name = ident_from_tag_name(&node.name); + let component_name = format!("<{component_name}/>"); let create_component = create_component(cx, node, mode); let span = node.name.span(); @@ -974,13 +976,13 @@ fn component_to_tokens( let starts_at = if let Some(prev_sib) = prev_sib { quote::quote! {{ //log::debug!("starts_at = next_sibling"); - #prev_sib.next_sibling().unwrap_throw() + #prev_sib.next_sibling().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#component_name, "nextSibling")) //log::debug!("ok starts_at"); }} } else { quote::quote! {{ //log::debug!("starts_at first_child"); - #parent.first_child().unwrap_throw() + #parent.first_child().unwrap_or_else(|| ::leptos::__leptos_renderer_error(#component_name, "firstChild")) //log::debug!("starts_at ok"); }} };