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 // this JSX is compiled to an HTML template string for performance
view! { view! {
cx,
<div> <div>
<button on:click=clear>"Clear"</button> <button on:click=clear>"Clear"</button>
<button on:click=decrement>"-1"</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 // Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setup
pub fn main() { 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] #[component]
fn Button(cx: Scope, text: &'static str) -> Element { fn Button(cx: Scope, text: &'static str) -> Element {
view! { view! { cx,
<button>{text}</button> <button>{text}</button>
} }
} }
#[component] #[component]
fn BoringButtons(cx: Scope) -> Element { fn BoringButtons(cx: Scope) -> Element {
view! { view! { cx,
<div> <div>
<Button text="These"/> <Button text="These"/>
<Button text="Do"/> <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) 1. Text within elements follows the rules of normal Rust strings (i.e., quotation marks or other string syntax)
```rust ```rust
view! { <p>"Hello, world!"</p> } view! { cx, <p>"Hello, world!"</p> }
``` ```
2. Values can be inserted between curly braces. Reactive values 2. Values can be inserted between curly braces. Reactive values
```rust ```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 { pub fn simple_counter(cx: Scope) -> web_sys::Element {
let (value, set_value) = create_signal(cx, 0); let (value, set_value) = create_signal(cx, 0);
view! { view! { cx,
<div> <div>
<button on:click=move |_| set_value(0)>"Clear"</button> <button on:click=move |_| set_value(0)>"Clear"</button>
<button on:click=move |_| set_value.update(|value| *value -= 1)>"-1"</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()); set_counters.update(|counters| counters.clear());
}; };
view! { view! { cx,
<div> <div>
<button on:click=add_counter> <button on:click=add_counter>
"Add Counter" "Add Counter"
@ -61,7 +61,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
<ul> <ul>
<For each={counters} key={|counter| counter.0}>{ <For each={counters} key={|counter| counter.0}>{
|cx, (id, (value, set_value)): &(usize, (ReadSignal<i32>, WriteSignal<i32>))| { |cx, (id, (value, set_value)): &(usize, (ReadSignal<i32>, WriteSignal<i32>))| {
view! { view! { cx,
<Counter id=*id value=*value set_value=*set_value/> <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()); let input = move |ev| set_value(event_target_value(&ev).parse::<i32>().unwrap_or_default());
view! { view! { cx,
<li> <li>
<button on:click={move |_| set_value.update(move |value| *value -= 1)}>"-1"</button> <button on:click={move |_| set_value.update(move |value| *value -= 1)}>"-1"</button>
<input type="text" <input type="text"

View file

@ -7,5 +7,5 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
fn main() { fn main() {
console_log::init_with_level(log::Level::Debug); 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] #[wasm_bindgen_test]
fn inc() { fn inc() {
mount_to_body(|cx| view! { <Counters/> }); mount_to_body(|cx| view! { cx, <Counters/> });
let document = leptos::document(); let document = leptos::document();
let div = document.query_selector("div").unwrap().unwrap(); let div = document.query_selector("div").unwrap().unwrap();
@ -82,7 +82,7 @@ fn inc() {
// but in user-land testing, RSX comparanda are cool // but in user-land testing, RSX comparanda are cool
assert_eq!( assert_eq!(
div.outer_html(), div.outer_html(),
view! { view! { cx,
<div> <div>
<button>"Add Counter"</button> <button>"Add Counter"</button>
<button>"Add 1000 Counters"</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 (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
let cats = create_resource(cx, cat_count, |count| fetch_cats(count)); let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
view! { view! { cx,
<div> <div>
<label> <label>
"How many cats would you like?" "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()}> <Suspense fallback={"Loading (Suspense Fallback)...".to_string()}>
{move || { {move || {
cats.read().map(|data| match data { cats.read().map(|data| match data {
Err(_) => view! { <pre>"Error"</pre> }, Err(_) => view! { cx, <pre>"Error"</pre> },
Ok(cats) => view! { Ok(cats) => view! { cx,
<div>{ <div>{
cats.iter() cats.iter()
.map(|src| { .map(|src| {
view! { view! { cx,
<img src={src}/> <img src={src}/>
} }
}) })

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -70,7 +70,7 @@ async fn render_app(req: HttpRequest) -> impl Responder {
move |cx| { move |cx| {
// the actual app body/template code // the actual app body/template code
// this does NOT contain any of the data being loaded asynchronously in resources // this does NOT contain any of the data being loaded asynchronously in resources
let shell = view! { let shell = view! { cx,
<div> <div>
<Router mode=integration> <Router mode=integration>
<App /> <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 { pub fn router_example(cx: Scope) -> Element {
view! { view! { cx,
<div id="root"> <div id="root">
<Router> <Router>
<nav> <nav>
@ -35,30 +35,30 @@ pub fn router_example(cx: Scope) -> Element {
<Routes> <Routes>
<Route <Route
path="" path=""
element=move |cx| view! { <ContactList/> } element=move |cx| view! { cx, <ContactList/> }
loader=contact_list_data.into() loader=contact_list_data.into()
> >
<Route <Route
path=":id" path=":id"
loader=contact_data.into() loader=contact_data.into()
element=move |cx| view! { <Contact/> } element=move |cx| view! { cx, <Contact/> }
/> />
<Route <Route
path="about" 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 <Route
path="" path=""
element=move |_| view! { <p class="contact">"Select a contact."</p> } element=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
/> />
</Route> </Route>
<Route <Route
path="about" path="about"
element=move |cx| view! { <About/> } element=move |cx| view! { cx, <About/> }
/> />
<Route <Route
path="settings" path="settings"
element=move |cx| view! { <Settings/> } element=move |cx| view! { cx, <Settings/> }
/> />
</Routes> </Routes>
</main> </main>
@ -72,18 +72,18 @@ pub fn ContactList(cx: Scope) -> Element {
let contacts = use_loader::<Vec<ContactSummary>>(cx); let contacts = use_loader::<Vec<ContactSummary>>(cx);
log::debug!("rendering <ContactList/>"); log::debug!("rendering <ContactList/>");
view! { view! { cx,
<div class="contact-list"> <div class="contact-list">
<h1>"Contacts"</h1> <h1>"Contacts"</h1>
<ul> <ul>
<Suspense fallback=move || view! { <p>"Loading contacts..."</p> }>{ <Suspense fallback=move || view! { cx, <p>"Loading contacts..."</p> }>{
move || { move || {
contacts.read().map(|contacts| view! { contacts.read().map(|contacts| view! { cx,
<For each=move || contacts.clone() key=|contact| contact.id> <For each=move || contacts.clone() key=|contact| contact.id>
{move |cx, contact: &ContactSummary| { {move |cx, contact: &ContactSummary| {
let id = contact.id; let id = contact.id;
let name = format!("{} {}", contact.first_name, contact.last_name); let name = format!("{} {}", contact.first_name, contact.last_name);
view! { view! { cx,
<li><A href=id.to_string()><span>{name.clone()}</span></A></li> <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 { pub fn Contact(cx: Scope) -> Element {
let contact = use_loader::<Option<Contact>>(cx); let contact = use_loader::<Option<Contact>>(cx);
view! { view! { cx,
<div class="contact"> <div class="contact">
<Suspense fallback=move || view! { <p>"Loading..."</p> }>{ <Suspense fallback=move || view! { cx, <p>"Loading..."</p> }>{
move || contact.read().map(|contact| contact.map(|contact| view! { move || contact.read().map(|contact| contact.map(|contact| view! { cx,
<section class="card"> <section class="card">
<h1>{contact.first_name} " " {contact.last_name}</h1> <h1>{contact.first_name} " " {contact.last_name}</h1>
<p>{contact.address_1}<br/>{contact.address_2}</p> <p>{contact.address_1}<br/>{contact.address_2}</p>
@ -117,7 +117,7 @@ pub fn Contact(cx: Scope) -> Element {
#[component] #[component]
pub fn About(_cx: Scope) -> Vec<Element> { pub fn About(_cx: Scope) -> Vec<Element> {
view! { view! { cx,
<> <>
<h1>"About"</h1> <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> <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] #[component]
pub fn Settings(_cx: Scope) -> Vec<Element> { pub fn Settings(_cx: Scope) -> Vec<Element> {
view! { view! { cx,
<> <>
<h1>"Settings"</h1> <h1>"Settings"</h1>
<form> <form>

View file

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

View file

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

View file

@ -6,5 +6,5 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
fn main() { fn main() {
_ = console_log::init_with_level(log::Level::Debug); _ = 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> <div>
<progress class:visible={move || transition.pending()} value={move || progress().to_string()} max="40"></progress> <progress class:visible={move || transition.pending()} value={move || progress().to_string()} max="40"></progress>
<nav class="tabs" class:pending={move || transition.pending()}> <nav class="tabs" class:pending={move || transition.pending()}>
@ -70,8 +70,8 @@ pub fn transition_tabs(cx: Scope) -> web_sys::Element {
</nav> </nav>
<p>{move || tab.get().to_string()}</p> <p>{move || tab.get().to_string()}</p>
<div class="tab"> <div class="tab">
//<Suspense fallback=view! { <div class="loader">"Loading..."</div> }> //<Suspense fallback=view! { cx, <div class="loader">"Loading..."</div> }>
{move || view! { <Child page=tab /> }} {move || view! { cx, <Child page=tab /> }}
//</Suspense> //</Suspense>
</div> </div>
</div> </div>
@ -82,11 +82,11 @@ pub fn transition_tabs(cx: Scope) -> web_sys::Element {
pub fn Child(cx: Scope, page: ReadSignal<Tab>) -> Element { pub fn Child(cx: Scope, page: ReadSignal<Tab>) -> Element {
let data = create_resource(cx, page, |page| fake_data_load(page)); let data = create_resource(cx, page, |page| fake_data_load(page));
view! { view! { cx,
<div class="tab-content"> <div class="tab-content">
<p> <p>
//<Suspense fallback=view! { <div class="loader">"Lower suspense..."</div> }> //<Suspense fallback=view! { cx, <div class="loader">"Lower suspense..."</div> }>
{move || data.read().map(|data| view! { {move || data.read().map(|data| view! { cx,
<div> <div>
<p>{data}</p> <p>{data}</p>
</div> </div>

View file

@ -25,7 +25,7 @@ where
/// Iterates over children and displays them, keyed by `PartialEq`. If you want to provide your /// Iterates over children and displays them, keyed by `PartialEq`. If you want to provide your
/// own key function, use [Index] instead. /// 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. /// as it avoids re-creating DOM nodes that are not being changed.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn For<E, T, G, I, K>(cx: Scope, props: ForProps<E, T, G, I, K>) -> Memo<Vec<Element>> 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 // 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() move || initial.clone()

View file

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

View file

@ -1,4 +1,4 @@
use proc_macro::TokenStream; use proc_macro::{TokenStream, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use syn::{parse_macro_input, DeriveInput}; use syn::{parse_macro_input, DeriveInput};
use syn_rsx::{parse, Node, NodeType}; use syn_rsx::{parse, Node, NodeType};
@ -32,12 +32,25 @@ mod props;
#[proc_macro] #[proc_macro]
pub fn view(tokens: TokenStream) -> TokenStream { pub fn view(tokens: TokenStream) -> TokenStream {
match parse(tokens) { let mut tokens = tokens.into_iter();
Ok(nodes) => render_view(&nodes, Mode::default()), 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(), Err(error) => error.to_compile_error(),
} }
.into() .into()
} }
_ => {
panic!("view! macro needs a context and RSX: e.g., view! {{ cx, <div>...</div> }}")
}
}
}
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn component(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream { pub fn component(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {

View file

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

View file

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

View file

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

View file

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

View file

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