Proc-macro hygiene by including cx in view! macro. :-( but necessary...

This commit is contained in:
Greg Johnston 2022-10-10 20:44:44 -04:00
parent 79f383c89e
commit 6e89ea4447
30 changed files with 166 additions and 136 deletions

View file

@ -29,6 +29,7 @@ pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
// this JSX is compiled to an HTML template string for performance
view! {
cx,
<div>
<button on:click=clear>"Clear"</button>
<button on:click=decrement>"-1"</button>
@ -40,7 +41,7 @@ pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
// Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setup
pub fn main() {
mount_to_body(|cx| view! { <SimpleCounter initial_value=3> })
mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=3> })
}
```

View file

@ -11,14 +11,14 @@ use leptos::*;
#[component]
fn Button(cx: Scope, text: &'static str) -> Element {
view! {
view! { cx,
<button>{text}</button>
}
}
#[component]
fn BoringButtons(cx: Scope) -> Element {
view! {
view! { cx,
<div>
<Button text="These"/>
<Button text="Do"/>
@ -35,11 +35,11 @@ Leptos uses a simple `view` macro to create the user interface. Its much like
1. Text within elements follows the rules of normal Rust strings (i.e., quotation marks or other string syntax)
```rust
view! { <p>"Hello, world!"</p> }
view! { cx, <p>"Hello, world!"</p> }
```
2. Values can be inserted between curly braces. Reactive values
```rust
view! { <p id={non_reactive_variable}>{move || value()}</p> }
view! { cx, <p id={non_reactive_variable}>{move || value()}</p> }
```

View file

@ -3,7 +3,7 @@ use leptos::*;
pub fn simple_counter(cx: Scope) -> web_sys::Element {
let (value, set_value) = create_signal(cx, 0);
view! {
view! { cx,
<div>
<button on:click=move |_| set_value(0)>"Clear"</button>
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</button>

View file

@ -34,7 +34,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
set_counters.update(|counters| counters.clear());
};
view! {
view! { cx,
<div>
<button on:click=add_counter>
"Add Counter"
@ -61,7 +61,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
<ul>
<For each={counters} key={|counter| counter.0}>{
|cx, (id, (value, set_value)): &(usize, (ReadSignal<i32>, WriteSignal<i32>))| {
view! {
view! { cx,
<Counter id=*id value=*value set_value=*set_value/>
}
}
@ -82,7 +82,7 @@ fn Counter(
let input = move |ev| set_value(event_target_value(&ev).parse::<i32>().unwrap_or_default());
view! {
view! { cx,
<li>
<button on:click={move |_| set_value.update(move |value| *value -= 1)}>"-1"</button>
<input type="text"

View file

@ -7,5 +7,5 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
fn main() {
console_log::init_with_level(log::Level::Debug);
mount_to_body(|cx| view! { <Counters/> })
mount_to_body(|cx| view! { cx, <Counters/> })
}

View file

@ -8,7 +8,7 @@ use counters::{Counters, CountersProps};
#[wasm_bindgen_test]
fn inc() {
mount_to_body(|cx| view! { <Counters/> });
mount_to_body(|cx| view! { cx, <Counters/> });
let document = leptos::document();
let div = document.query_selector("div").unwrap().unwrap();
@ -82,7 +82,7 @@ fn inc() {
// but in user-land testing, RSX comparanda are cool
assert_eq!(
div.outer_html(),
view! {
view! { cx,
<div>
<button>"Add Counter"</button>
<button>"Add 1000 Counters"</button>

View file

@ -33,7 +33,7 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
view! {
view! { cx,
<div>
<label>
"How many cats would you like?"
@ -49,12 +49,12 @@ pub fn fetch_example(cx: Scope) -> web_sys::Element {
<Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
{move || {
cats.read().map(|data| match data {
Err(_) => view! { <pre>"Error"</pre> },
Ok(cats) => view! {
Err(_) => view! { cx, <pre>"Error"</pre> },
Ok(cats) => view! { cx,
<div>{
cats.iter()
.map(|src| {
view! {
view! { cx,
<img src={src}/>
}
})

View file

@ -15,12 +15,12 @@ use users::*;
#[component]
pub fn App(cx: Scope) -> Vec<Branch> {
view! {
view! { cx,
<Routes>
<Route path="" element=|cx| view! { <Main/> }>
<Route path="users/:id" element=|cx| view! { <User/> } loader=user_data.into() />
<Route path="stories/:id" element=|cx| view! { <Story/> } loader=story_data.into() />
<Route path="*stories" element=|cx| view! { <Stories/> } loader=stories_data.into()/>
<Route path="" element=|cx| view! { cx, <Main/> }>
<Route path="users/:id" element=|cx| view! { cx, <User/> } loader=user_data.into() />
<Route path="stories/:id" element=|cx| view! { cx, <Story/> } loader=story_data.into() />
<Route path="*stories" element=|cx| view! { cx, <Stories/> } loader=stories_data.into()/>
</Route>
</Routes>
}
@ -28,7 +28,7 @@ pub fn App(cx: Scope) -> Vec<Branch> {
#[component]
pub fn Main(cx: Scope) -> Element {
view! {
view! { cx,
<article>
<Nav />
<Outlet />

View file

@ -7,7 +7,7 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
mount_to_body(|cx| {
view! {
view! { cx,
<div>
<Router mode=BrowserIntegration {}><App/></Router>
</div>

View file

@ -2,7 +2,7 @@ use leptos::*;
#[component]
pub fn Nav(cx: Scope) -> Element {
view! {
view! { cx,
<header class="header">
<nav class="inner">
<Link to="/".into()>

View file

@ -70,12 +70,12 @@ pub fn Stories(cx: Scope) -> Element {
let hide_more_link = move || stories.read().unwrap_or(Err(())).unwrap_or_default().len() < 28;
view! {
view! { cx,
<div class="news-view">
<div class="news-list-nav">
// TODO fix
/* {move || if page() > 1 {
view! {
view! { cx,
//<Link
//attr:class="page-link"
//to={format!("/{}?page={}", story_type(), page() - 1)}
@ -85,7 +85,7 @@ pub fn Stories(cx: Scope) -> Element {
</a>//</Link>
}
} else {
view! {
view! { cx,
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
@ -105,16 +105,16 @@ pub fn Stories(cx: Scope) -> Element {
</div>
<main class="news-list">
<div>
<Suspense fallback=view! { <p>"Loading..."</p> }>
<Suspense fallback=view! { cx, <p>"Loading..."</p> }>
{move || match stories.read() {
None => None,
Some(Err(_)) => Some(view! { <p>"Error loading stories."</p> }),
Some(Err(_)) => Some(view! { cx, <p>"Error loading stories."</p> }),
Some(Ok(stories)) => {
Some(view! {
Some(view! { cx,
<ul>
<For each={move || stories.clone()} key=|story| story.id>{
move |cx: Scope, story: &api::Story| {
view! {
view! { cx,
<Story story={story.clone()} />
}
}
@ -132,12 +132,12 @@ pub fn Stories(cx: Scope) -> Element {
#[component]
fn Story(cx: Scope, story: api::Story) -> Element {
view! {
view! { cx,
<li class="news-item">
<span class="score">{story.points}</span>
<span class="title">
{if !story.url.starts_with("item?id=") {
view! {
view! { cx,
<span>
<a href={story.url} target="_blank" rel="noreferrer">
{story.title.clone()}
@ -147,13 +147,13 @@ fn Story(cx: Scope, story: api::Story) -> Element {
}
} else {
let title = story.title.clone();
view! { <Link to={format!("/stories/{}", story.id)}>{title}</Link> }
view! { cx, <Link to={format!("/stories/{}", story.id)}>{title}</Link> }
}}
</span>
<br />
<span class="meta">
{if story.story_type != "job" {
view! {
view! { cx,
<span>
//{"by "}
//{story.user.map(|user| view ! { <Link to={format!("/users/{}", user)}>{&user}</Link>})}
@ -169,10 +169,10 @@ fn Story(cx: Scope, story: api::Story) -> Element {
}
} else {
let title = story.title.clone();
view! { <Link to={format!("/item/{}", story.id)}>{title}</Link> }
view! { cx, <Link to={format!("/item/{}", story.id)}>{title}</Link> }
}}
</span>
{(story.story_type != "link").then(|| view! {
{(story.story_type != "link").then(|| view! { cx,
<span>
//{" "}
<span class="label">{story.story_type}</span>

View file

@ -18,11 +18,11 @@ pub fn story_data(
pub fn Story(cx: Scope) -> Element {
let story = use_loader::<Resource<String, Result<api::Story, ()>>>(cx);
view! {
view! { cx,
<div>
{move || story.read().map(|story| match story {
Err(_) => view! { <div class="item-view">"Error loading this story."</div> },
Ok(story) => view! {
Err(_) => view! { cx, <div class="item-view">"Error loading this story."</div> },
Ok(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href={story.url} target="_blank">
@ -31,7 +31,7 @@ pub fn Story(cx: Scope) -> Element {
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { <p class="meta">
{story.user.map(|user| view! { cx, <p class="meta">
// TODO issue here in renderer
{story.points}
" points | by "
@ -49,7 +49,7 @@ pub fn Story(cx: Scope) -> Element {
</p>
<ul class="comment-children">
<For each={move || story.comments.clone().unwrap_or_default()} key={|comment| comment.id}>
{move |cx, comment: &api::Comment| view! { <Comment comment={comment.clone()} /> }}
{move |cx, comment: &api::Comment| view! { cx, <Comment comment={comment.clone()} /> }}
</For>
</ul>
</div>
@ -63,7 +63,7 @@ pub fn Story(cx: Scope) -> Element {
pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
let (open, set_open) = create_signal(cx, true);
view! {
view! { cx,
<li class="comment">
<div class="by">
<Link to={format!("/users/{}", comment.user)}>{&comment.user}</Link>
@ -71,7 +71,7 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
</div>
<div class="text" inner_html={comment.content}></div>
{(!comment.comments.is_empty()).then(|| {
view! {
view! { cx,
<div>
<div class="toggle" class:open=open>
<a on:click=move |_| set_open(|n| *n = !*n)>
@ -87,10 +87,10 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
</div>
{move || open().then({
let comments = comment.comments.clone();
move || view! {
move || view! { cx,
<ul class="comment-children">
<For each={move || comments.clone()} key=|comment| comment.id>
{|cx, comment: &api::Comment| view! {
{|cx, comment: &api::Comment| view! { cx,
<Comment comment={comment.clone()} />
}}
</For>

View file

@ -17,11 +17,11 @@ pub fn user_data(
#[component]
pub fn User(cx: Scope) -> Element {
let user = use_loader::<Resource<String, Result<api::User, ()>>>(cx);
view! {
view! { cx,
<div class="user-view">
{move || user.read().map(|user| match user {
Err(_) => view! { <h1>"User not found."</h1> },
Ok(user) => view! {
Err(_) => view! { cx, <h1>"User not found."</h1> },
Ok(user) => view! { cx,
<div>
<h1>"User: " {user.id}</h1>
<ul class="meta">
@ -31,7 +31,7 @@ pub fn User(cx: Scope) -> Element {
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
//{user.about.map(|about| view! { <li inner_html={user.about} class="about"></li> })}
//{user.about.map(|about| view! { cx, <li inner_html={user.about} class="about"></li> })}
</ul>
/* <p class="links">
<a href={format!("https://news.ycombinator.com/submitted?id={}", user.id)}>"submissions"</a>

View file

@ -12,7 +12,7 @@ pub fn main() {
let integration = BrowserIntegration {};
leptos::hydrate(body().unwrap(), move |cx| {
view! {
view! { cx,
<div>
<Router mode=integration>
<App />

View file

@ -70,7 +70,7 @@ async fn render_app(req: HttpRequest) -> impl Responder {
move |cx| {
// the actual app body/template code
// this does NOT contain any of the data being loaded asynchronously in resources
let shell = view! {
let shell = view! { cx,
<div>
<Router mode=integration>
<App />

View file

@ -23,7 +23,7 @@ async fn contact_data(_cx: Scope, params: ParamsMap, _url: Url) -> Option<Contac
}
pub fn router_example(cx: Scope) -> Element {
view! {
view! { cx,
<div id="root">
<Router>
<nav>
@ -35,30 +35,30 @@ pub fn router_example(cx: Scope) -> Element {
<Routes>
<Route
path=""
element=move |cx| view! { <ContactList/> }
element=move |cx| view! { cx, <ContactList/> }
loader=contact_list_data.into()
>
<Route
path=":id"
loader=contact_data.into()
element=move |cx| view! { <Contact/> }
element=move |cx| view! { cx, <Contact/> }
/>
<Route
path="about"
element=move |_| view! { <p class="contact">"Here is your list of contacts"</p> }
element=move |_| view! { cx, <p class="contact">"Here is your list of contacts"</p> }
/>
<Route
path=""
element=move |_| view! { <p class="contact">"Select a contact."</p> }
element=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
/>
</Route>
<Route
path="about"
element=move |cx| view! { <About/> }
element=move |cx| view! { cx, <About/> }
/>
<Route
path="settings"
element=move |cx| view! { <Settings/> }
element=move |cx| view! { cx, <Settings/> }
/>
</Routes>
</main>
@ -72,18 +72,18 @@ pub fn ContactList(cx: Scope) -> Element {
let contacts = use_loader::<Vec<ContactSummary>>(cx);
log::debug!("rendering <ContactList/>");
view! {
view! { cx,
<div class="contact-list">
<h1>"Contacts"</h1>
<ul>
<Suspense fallback=move || view! { <p>"Loading contacts..."</p> }>{
<Suspense fallback=move || view! { cx, <p>"Loading contacts..."</p> }>{
move || {
contacts.read().map(|contacts| view! {
contacts.read().map(|contacts| view! { cx,
<For each=move || contacts.clone() key=|contact| contact.id>
{move |cx, contact: &ContactSummary| {
let id = contact.id;
let name = format!("{} {}", contact.first_name, contact.last_name);
view! {
view! { cx,
<li><A href=id.to_string()><span>{name.clone()}</span></A></li>
}
}}
@ -101,10 +101,10 @@ pub fn ContactList(cx: Scope) -> Element {
pub fn Contact(cx: Scope) -> Element {
let contact = use_loader::<Option<Contact>>(cx);
view! {
view! { cx,
<div class="contact">
<Suspense fallback=move || view! { <p>"Loading..."</p> }>{
move || contact.read().map(|contact| contact.map(|contact| view! {
<Suspense fallback=move || view! { cx, <p>"Loading..."</p> }>{
move || contact.read().map(|contact| contact.map(|contact| view! { cx,
<section class="card">
<h1>{contact.first_name} " " {contact.last_name}</h1>
<p>{contact.address_1}<br/>{contact.address_2}</p>
@ -117,7 +117,7 @@ pub fn Contact(cx: Scope) -> Element {
#[component]
pub fn About(_cx: Scope) -> Vec<Element> {
view! {
view! { cx,
<>
<h1>"About"</h1>
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
@ -127,7 +127,7 @@ pub fn About(_cx: Scope) -> Vec<Element> {
#[component]
pub fn Settings(_cx: Scope) -> Vec<Element> {
view! {
view! { cx,
<>
<h1>"Settings"</h1>
<form>

View file

@ -19,6 +19,6 @@ pub fn main() {
Todo::new(cx, 2, "Profit!".to_string()),
]);
view! { <TodoMVC todos=todos/> }
view! { cx, <TodoMVC todos=todos/> }
});
}

View file

@ -30,7 +30,7 @@ async fn render_todomvc() -> impl Responder {
Todo::new(cx, 2, "Profit!".to_string()),
]);
view! {
view! { cx,
<TodoMVC todos=todos/>
}
}

View file

@ -178,7 +178,7 @@ pub fn TodoMVC(cx: Scope, todos: Todos) -> Element {
}
});
view! {
view! { cx,
<main>
<section class="todoapp">
<header class="header">
@ -193,7 +193,7 @@ pub fn TodoMVC(cx: Scope, todos: Todos) -> Element {
<label for="toggle-all">"Mark all as complete"</label>
<ul class="todo-list">
<For each={filtered_todos} key={|todo| todo.id}>
{move |cx, todo: &Todo| view! { <Todo todo={todo.clone()} /> }}
{move |cx, todo: &Todo| view! { cx, <Todo todo={todo.clone()} /> }}
</For>
</ul>
</section>
@ -246,7 +246,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element {
set_editing(false);
};
let tpl = view! {
let tpl = view! { cx,
<li
class="todo"
class:editing={editing}
@ -268,7 +268,7 @@ pub fn Todo(cx: Scope, todo: Todo) -> Element {
</label>
<button class="destroy" on:click={move |_| set_todos.update(|t| t.remove(todo.id))}/>
</div>
{move || editing().then(|| view! {
{move || editing().then(|| view! { cx,
<input
class="edit"
class:hidden={move || !(editing)()}

View file

@ -6,5 +6,5 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
fn main() {
_ = console_log::init_with_level(log::Level::Debug);
mount_to_body(|cx| view! { <TodoMVC todos=Todos::new(cx)/> })
mount_to_body(|cx| view! { cx, <TodoMVC todos=Todos::new(cx)/> })
}

View file

@ -54,7 +54,7 @@ pub fn transition_tabs(cx: Scope) -> web_sys::Element {
}
});
view! {
view! { cx,
<div>
<progress class:visible={move || transition.pending()} value={move || progress().to_string()} max="40"></progress>
<nav class="tabs" class:pending={move || transition.pending()}>
@ -70,8 +70,8 @@ pub fn transition_tabs(cx: Scope) -> web_sys::Element {
</nav>
<p>{move || tab.get().to_string()}</p>
<div class="tab">
//<Suspense fallback=view! { <div class="loader">"Loading..."</div> }>
{move || view! { <Child page=tab /> }}
//<Suspense fallback=view! { cx, <div class="loader">"Loading..."</div> }>
{move || view! { cx, <Child page=tab /> }}
//</Suspense>
</div>
</div>
@ -82,11 +82,11 @@ pub fn transition_tabs(cx: Scope) -> web_sys::Element {
pub fn Child(cx: Scope, page: ReadSignal<Tab>) -> Element {
let data = create_resource(cx, page, |page| fake_data_load(page));
view! {
view! { cx,
<div class="tab-content">
<p>
//<Suspense fallback=view! { <div class="loader">"Lower suspense..."</div> }>
{move || data.read().map(|data| view! {
//<Suspense fallback=view! { cx, <div class="loader">"Lower suspense..."</div> }>
{move || data.read().map(|data| view! { cx,
<div>
<p>{data}</p>
</div>

View file

@ -25,7 +25,7 @@ where
/// Iterates over children and displays them, keyed by `PartialEq`. If you want to provide your
/// own key function, use [Index] instead.
///
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { ... })...`,
/// This is much more efficient than naively iterating over nodes with `.iter().map(|n| view! { cx, ... })...`,
/// as it avoids re-creating DOM nodes that are not being changed.
#[allow(non_snake_case)]
pub fn For<E, T, G, I, K>(cx: Scope, props: ForProps<E, T, G, I, K>) -> Memo<Vec<Element>>

View file

@ -102,7 +102,7 @@ where
});
// return the fallback for now, wrapped in fragment identifer
Child::Node(view! { <div data-fragment-id={key}>{fallback.into_child(cx)}</div> })
Child::Node(view! { cx, <div data-fragment-id={key}>{fallback.into_child(cx)}</div> })
}
};
move || initial.clone()

View file

@ -19,9 +19,9 @@ syn-rsx = "0.8.1"
uuid = { version = "1", features = ["v4"] }
[dev-dependencies]
leptos_core = { path = "../leptos_core", features = ["ssr"], version = "0.0.1" }
leptos_dom = { path = "../leptos_dom", features = ["ssr"], version = "0.0.3" }
leptos_reactive = { path = "../leptos_reactive", features = ["ssr"], version = "0.0.3" }
leptos_core = { path = "../leptos_core", features = ["ssr"] }
leptos_dom = { path = "../leptos_dom", features = ["ssr"] }
leptos_reactive = { path = "../leptos_reactive", features = ["ssr"] }
[features]
csr = []

View file

@ -1,4 +1,4 @@
use proc_macro::TokenStream;
use proc_macro::{TokenStream, TokenTree};
use quote::ToTokens;
use syn::{parse_macro_input, DeriveInput};
use syn_rsx::{parse, Node, NodeType};
@ -32,11 +32,24 @@ mod props;
#[proc_macro]
pub fn view(tokens: TokenStream) -> TokenStream {
match parse(tokens) {
Ok(nodes) => render_view(&nodes, Mode::default()),
Err(error) => error.to_compile_error(),
let mut tokens = tokens.into_iter();
let (cx, comma) = (tokens.next(), tokens.next());
match (cx, comma) {
(Some(TokenTree::Ident(cx)), Some(TokenTree::Punct(punct))) if punct.as_char() == ',' => {
match parse(tokens.collect()) {
Ok(nodes) => render_view(
&proc_macro2::Ident::new(&cx.to_string(), cx.span().into()),
&nodes,
Mode::default(),
),
Err(error) => error.to_compile_error(),
}
.into()
}
_ => {
panic!("view! macro needs a context and RSX: e.g., view! {{ cx, <div>...</div> }}")
}
}
.into()
}
#[proc_macro_attribute]

View file

@ -6,18 +6,18 @@ use uuid::Uuid;
use crate::{is_component_node, Mode};
pub(crate) fn render_view(nodes: &[Node], mode: Mode) -> TokenStream {
pub(crate) fn render_view(cx: &Ident, nodes: &[Node], mode: Mode) -> TokenStream {
let template_uid = Ident::new(
&format!("TEMPLATE_{}", Uuid::new_v4().simple()),
Span::call_site(),
);
if nodes.len() == 1 {
first_node_to_tokens(&template_uid, &nodes[0], mode)
first_node_to_tokens(cx, &template_uid, &nodes[0], mode)
} else {
let nodes = nodes
.iter()
.map(|node| first_node_to_tokens(&template_uid, node, mode));
.map(|node| first_node_to_tokens(cx, &template_uid, node, mode));
quote! {
{
vec![
@ -28,14 +28,14 @@ pub(crate) fn render_view(nodes: &[Node], mode: Mode) -> TokenStream {
}
}
fn first_node_to_tokens(template_uid: &Ident, node: &Node, mode: Mode) -> TokenStream {
fn first_node_to_tokens(cx: &Ident, template_uid: &Ident, node: &Node, mode: Mode) -> TokenStream {
match node.node_type {
NodeType::Doctype | NodeType::Comment => quote! {},
NodeType::Fragment => {
let nodes = node
.children
.iter()
.map(|node| first_node_to_tokens(template_uid, node, mode));
.map(|node| first_node_to_tokens(cx, template_uid, node, mode));
quote! {
{
vec![
@ -44,7 +44,7 @@ fn first_node_to_tokens(template_uid: &Ident, node: &Node, mode: Mode) -> TokenS
}
}
}
NodeType::Element => root_element_to_tokens(template_uid, node, mode),
NodeType::Element => root_element_to_tokens(cx, template_uid, node, mode),
NodeType::Block => node
.value
.as_ref()
@ -60,15 +60,21 @@ fn first_node_to_tokens(template_uid: &Ident, node: &Node, mode: Mode) -> TokenS
}
}
fn root_element_to_tokens(template_uid: &Ident, node: &Node, mode: Mode) -> TokenStream {
fn root_element_to_tokens(
cx: &Ident,
template_uid: &Ident,
node: &Node,
mode: Mode,
) -> TokenStream {
let mut template = String::new();
let mut navigations = Vec::new();
let mut expressions = Vec::new();
if is_component_node(node) {
create_component(node, mode)
create_component(cx, node, mode)
} else {
element_to_tokens(
cx,
node,
&Ident::new("root", Span::call_site()),
None,
@ -104,7 +110,7 @@ fn root_element_to_tokens(template_uid: &Ident, node: &Node, mode: Mode) -> Toke
Mode::Hydrate => {
let name = node.name_as_string().unwrap();
quote! {
let root = #template_uid.with(|template| cx.get_next_element(template));
let root = #template_uid.with(|template| #cx.get_next_element(template));
// //log::debug!("root = {}", root.node_name());
}
}
@ -152,6 +158,7 @@ enum PrevSibChange {
#[allow(clippy::too_many_arguments)]
fn element_to_tokens(
cx: &Ident,
node: &Node,
parent: &Ident,
prev_sib: Option<Ident>,
@ -187,7 +194,7 @@ fn element_to_tokens(
if mode == Mode::Ssr && is_root_el {
expressions.push(quote::quote_spanned! {
span => leptos_buffer.push_str(" data-hk=\"");
leptos_buffer.push_str(&cx.next_hydration_key().to_string());
leptos_buffer.push_str(&#cx.next_hydration_key().to_string());
leptos_buffer.push('"');
});
}
@ -195,6 +202,7 @@ fn element_to_tokens(
// attributes
for attr in &node.attributes {
attr_to_tokens(
cx,
attr,
&this_el_ident,
template,
@ -274,6 +282,7 @@ fn element_to_tokens(
let next_sib = next_sibling_node(&node.children, idx + 1, next_el_id);
let curr_id = child_to_tokens(
cx,
child,
&this_el_ident,
if idx == 0 { None } else { prev_sib.clone() },
@ -324,6 +333,7 @@ fn next_sibling_node(children: &[Node], idx: usize, next_el_id: &mut usize) -> O
}
fn attr_to_tokens(
cx: &Ident,
node: &Node,
el_id: &Ident,
template: &mut String,
@ -414,7 +424,7 @@ fn attr_to_tokens(
let name = name.replacen("prop:", "", 1);
let value = node.value.as_ref().expect("prop: blocks need values");
expressions.push(quote_spanned! {
span => leptos_dom::property(cx, #el_id.unchecked_ref(), #name, #value.into_property(cx))
span => leptos_dom::property(#cx, #el_id.unchecked_ref(), #name, #value.into_property(#cx))
});
}
}
@ -426,7 +436,7 @@ fn attr_to_tokens(
let name = name.replacen("class:", "", 1);
let value = node.value.as_ref().expect("class: attributes need values");
expressions.push(quote_spanned! {
span => leptos_dom::class(cx, #el_id.unchecked_ref(), #name, #value.into_class(cx))
span => leptos_dom::class(#cx, #el_id.unchecked_ref(), #name, #value.into_class(#cx))
});
}
}
@ -469,14 +479,14 @@ fn attr_to_tokens(
(AttributeValue::Dynamic(value), Mode::Ssr) => {
expressions.push(quote_spanned! {
span => leptos_buffer.push(' ');
leptos_buffer.push_str(&{#value}.into_attribute(cx).as_value_string(#name));
leptos_buffer.push_str(&{#value}.into_attribute(#cx).as_value_string(#name));
});
}
(AttributeValue::Dynamic(value), _) => {
// For client-side rendering, dynamic attributes don't need to be rendered in the template
// They'll immediately be set synchronously before the cloned template is mounted
expressions.push(quote_spanned! {
span => leptos_dom::attribute(cx, #el_id.unchecked_ref(), #name, {#value}.into_attribute(cx))
span => leptos_dom::attribute(#cx, #el_id.unchecked_ref(), #name, {#value}.into_attribute(#cx))
});
}
}
@ -491,6 +501,7 @@ enum AttributeValue<'a> {
#[allow(clippy::too_many_arguments)]
fn child_to_tokens(
cx: &Ident,
node: &Node,
parent: &Ident,
prev_sib: Option<Ident>,
@ -507,6 +518,7 @@ fn child_to_tokens(
NodeType::Element => {
if is_component_node(node) {
component_to_tokens(
cx,
node,
Some(parent),
prev_sib,
@ -521,6 +533,7 @@ fn child_to_tokens(
)
} else {
PrevSibChange::Sib(element_to_tokens(
cx,
node,
parent,
prev_sib,
@ -603,14 +616,14 @@ fn child_to_tokens(
navigations.push(location);
let current = match current {
Some(i) => quote! { Some(#i.into_child(cx)) },
Some(i) => quote! { Some(#i.into_child(#cx)) },
None => quote! { None },
};
expressions.push(quote! {
leptos::insert(
cx,
#cx,
#parent.clone(),
#value.into_child(cx),
#value.into_child(#cx),
#before,
#current,
);
@ -628,15 +641,15 @@ fn child_to_tokens(
template.push_str("<!#><!/>");
navigations.push(quote! {
#location;
let (#el, #co) = cx.get_next_marker(&#name);
let (#el, #co) = #cx.get_next_marker(&#name);
//log::debug!("get_next_marker => {}", #el.node_name());
});
expressions.push(quote! {
leptos::insert(
cx,
#cx,
#parent.clone(),
#value.into_child(cx),
#value.into_child(#cx),
#before,
Some(Child::Nodes(#co)),
);
@ -647,7 +660,7 @@ fn child_to_tokens(
// in SSR, it needs to insert the value, wrapped in comments
Mode::Ssr => expressions.push(quote::quote_spanned! {
span => leptos_buffer.push_str("<!--#-->");
leptos_buffer.push_str(&#value.into_child(cx).as_child_string());
leptos_buffer.push_str(&#value.into_child(#cx).as_child_string());
leptos_buffer.push_str("<!--/-->");
}),
}
@ -661,6 +674,7 @@ fn child_to_tokens(
#[allow(clippy::too_many_arguments)]
fn component_to_tokens(
cx: &Ident,
node: &Node,
parent: Option<&Ident>,
prev_sib: Option<Ident>,
@ -673,7 +687,7 @@ fn component_to_tokens(
multi: bool,
mode: Mode,
) -> PrevSibChange {
let create_component = create_component(node, mode);
let create_component = create_component(cx, node, mode);
let span = node.name_span().unwrap();
let mut current = None;
@ -694,7 +708,7 @@ fn component_to_tokens(
expressions.push(quote::quote_spanned! {
span => // TODO wrap components but use get_next_element() instead of first_child/next_sibling?
leptos_buffer.push_str("<!--#-->");
leptos_buffer.push_str(&#create_component.into_child(cx).as_child_string());
leptos_buffer.push_str(&#create_component.into_child(#cx).as_child_string());
leptos_buffer.push_str("<!--/-->");
});
@ -724,14 +738,14 @@ fn component_to_tokens(
template.push_str("<!#><!/>");
navigations.push(quote! {
let (#el, #co) = cx.get_next_marker(&#starts_at);
let (#el, #co) = #cx.get_next_marker(&#starts_at);
});
expressions.push(quote! {
leptos::insert(
cx,
#cx,
#parent.clone(),
#create_component.into_child(cx),
#create_component.into_child(#cx),
Marker::BeforeChild(#el),
Some(Child::Nodes(#co)),
);
@ -739,9 +753,9 @@ fn component_to_tokens(
} else {
expressions.push(quote! {
leptos::insert(
cx,
#cx,
#parent.clone(),
#create_component.into_child(cx),
#create_component.into_child(#cx),
#before,
None,
);
@ -757,7 +771,7 @@ fn component_to_tokens(
}
}
fn create_component(node: &Node, mode: Mode) -> TokenStream {
fn create_component(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
let component_name = ident_from_tag_name(node.name.as_ref().unwrap());
let span = node.name_span().unwrap();
let component_props_name = Ident::new(&format!("{component_name}Props"), span);
@ -765,7 +779,7 @@ fn create_component(node: &Node, mode: Mode) -> TokenStream {
let (initialize_children, children) = if node.children.is_empty() {
(quote! {}, quote! {})
} else if node.children.len() == 1 {
let child = render_view(&node.children, mode);
let child = render_view(cx, &node.children, mode);
if mode == Mode::Hydrate {
(
@ -779,7 +793,7 @@ fn create_component(node: &Node, mode: Mode) -> TokenStream {
)
}
} else {
let children = render_view(&node.children, mode);
let children = render_view(cx, &node.children, mode);
if mode == Mode::Hydrate {
(
@ -832,14 +846,14 @@ fn create_component(node: &Node, mode: Mode) -> TokenStream {
else if let Some(name) = attr_name.strip_prefix("prop:") {
let value = attr.value.as_ref().expect("prop: attributes need values");
Some(quote_spanned! {
span => leptos_dom::property(cx, #component_name.unchecked_ref(), #name, #value.into_property(cx))
span => leptos_dom::property(#cx, #component_name.unchecked_ref(), #name, #value.into_property(#cx))
})
}
// Classes
else if let Some(name) = attr_name.strip_prefix("class:") {
let value = attr.value.as_ref().expect("class: attributes need values");
Some(quote_spanned! {
span => leptos_dom::class(cx, #component_name.unchecked_ref(), #name, #value.into_class(cx))
span => leptos_dom::class(#cx, #component_name.unchecked_ref(), #name, #value.into_class(#cx))
})
}
// Attributes
@ -847,7 +861,7 @@ fn create_component(node: &Node, mode: Mode) -> TokenStream {
let value = attr.value.as_ref().expect("attr: attributes need values");
let name = name.replace('_', "-");
Some(quote_spanned! {
span => leptos_dom::attribute(cx, #component_name.unchecked_ref(), #name, #value.into_attribute(cx))
span => leptos_dom::attribute(#cx, #component_name.unchecked_ref(), #name, #value.into_attribute(#cx))
})
}
else {
@ -857,10 +871,10 @@ fn create_component(node: &Node, mode: Mode) -> TokenStream {
if other_attrs.peek().is_none() {
quote_spanned! {
span => create_component(cx, move || {
span => create_component(#cx, move || {
#initialize_children
#component_name(
cx,
#cx,
#component_props_name::builder()
#(#props)*
#children
@ -870,10 +884,10 @@ fn create_component(node: &Node, mode: Mode) -> TokenStream {
}
} else {
quote_spanned! {
span => create_component(cx, move || {
span => create_component(#cx, move || {
#initialize_children
let #component_name = #component_name(
cx,
#cx,
#component_props_name::builder()
#(#props)*
#children

View file

@ -7,7 +7,8 @@ fn simple_ssr_test() {
_ = create_scope(|cx| {
let (value, set_value) = create_signal(cx, 0);
let rendered = view! {
let rendered = view! { cx,
cx,
<div>
<button on:click=move |_| set_value(|value| *value -= 1)>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
@ -34,7 +35,8 @@ fn ssr_test_with_components() {
#[component]
fn Counter(cx: Scope, initial_value: i32) -> Element {
let (value, set_value) = create_signal(cx, 0);
view! {
view! { cx,
cx,
<div>
<button on:click=move |_| set_value(|value| *value -= 1)>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
@ -44,7 +46,8 @@ fn ssr_test_with_components() {
}
_ = create_scope(|cx| {
let rendered = view! {
let rendered = view! { cx,
cx,
<div class="counters">
<Counter initial_value=1/>
<Counter initial_value=2/>

View file

@ -66,8 +66,7 @@ where
use futures::FutureExt;
let initial_fut = fetcher(source());
let initial_value = initial_fut.now_or_never();
create_resource_with_initial_value(cx, source, fetcher, initial_value)
initial_fut.now_or_never()
};
create_resource_with_initial_value(cx, source, fetcher, initial_value)

View file

@ -142,7 +142,7 @@ where
let children = children().into_vec();
view! {
view! { cx,
<form
method=method
action=action

View file

@ -87,7 +87,7 @@ where
}
let child = children.remove(0);
view! {
view! { cx,
<a
href=move || href().unwrap_or_default()
prop:state={props.state.map(|s| s.to_js_value())}