From 0e6953e952994e7f14410536ced0b787b5aab637 Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Fri, 7 Apr 2023 13:57:43 -0700
Subject: [PATCH 01/17] fix task spawning for wasm ssr
---
leptos_reactive/Cargo.toml | 3 +++
leptos_reactive/src/spawn.rs | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/leptos_reactive/Cargo.toml b/leptos_reactive/Cargo.toml
index c4439b4ffd..343e45673d 100644
--- a/leptos_reactive/Cargo.toml
+++ b/leptos_reactive/Cargo.toml
@@ -52,6 +52,9 @@ log = "0.4"
tokio-test = "0.4"
leptos = { path = "../leptos" }
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen-futures = { version = "0.4" }
+
[features]
default = []
csr = [
diff --git a/leptos_reactive/src/spawn.rs b/leptos_reactive/src/spawn.rs
index 5bc8126adc..f7fb310bc1 100644
--- a/leptos_reactive/src/spawn.rs
+++ b/leptos_reactive/src/spawn.rs
@@ -76,7 +76,7 @@ where
F: Future
+ }
+ }
+ />
+ }
+ .into_view(cx)
+}
diff --git a/examples/hackernews_js_fetch/src/fallback.rs b/examples/hackernews_js_fetch/src/fallback.rs
new file mode 100644
index 0000000000..c3cde0d2e3
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/fallback.rs
@@ -0,0 +1,37 @@
+use cfg_if::cfg_if;
+
+cfg_if! {
+if #[cfg(feature = "ssr")] {
+ use axum::{
+ body::{boxed, Body, BoxBody},
+ extract::State,
+ response::IntoResponse,
+ http::{Request, Response, StatusCode, Uri},
+ };
+ use axum::response::Response as AxumResponse;
+ use tower::ServiceExt;
+ use leptos::{LeptosOptions};
+ use crate::error_template::error_template;
+
+ pub async fn file_and_error_handler(uri: Uri, State(options): State, req: Request) -> AxumResponse {
+ let root = options.site_root.clone();
+ let res = get_static_file(uri.clone(), &root).await.unwrap();
+
+ if res.status() == StatusCode::OK {
+ res.into_response()
+ } else{
+ let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| error_template(cx, None));
+ handler(req).await.into_response()
+ }
+ }
+
+ async fn get_static_file(uri: Uri, root: &str) -> Result, (StatusCode, String)> {
+ let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
+ // `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
+ // This path is relative to the cargo root
+ todo!()
+ }
+
+
+}
+}
diff --git a/examples/hackernews_js_fetch/src/lib.rs b/examples/hackernews_js_fetch/src/lib.rs
new file mode 100644
index 0000000000..27c7949f86
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/lib.rs
@@ -0,0 +1,87 @@
+use cfg_if::cfg_if;
+use leptos::{component, view, IntoView, Scope};
+use leptos_meta::*;
+use leptos_router::*;
+use log::{info, Level};
+mod api;
+pub mod error_template;
+pub mod fallback;
+mod routes;
+use routes::{nav::*, stories::*, story::*, users::*};
+
+#[component]
+pub fn App(cx: Scope) -> impl IntoView {
+ provide_meta_context(cx);
+ view! {
+ cx,
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ }
+}
+
+// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
+cfg_if! {
+ if #[cfg(feature = "hydrate")] {
+ use wasm_bindgen::prelude::wasm_bindgen;
+
+ #[wasm_bindgen]
+ pub fn hydrate() {
+ _ = console_log::init_with_level(log::Level::Debug);
+ console_error_panic_hook::set_once();
+ leptos::mount_to_body(move |cx| {
+ view! { cx, }
+ });
+ }
+ } else if #[cfg(feature = "ssr")] {
+
+ use axum::{
+ Router,
+ };
+ use leptos_axum::{generate_route_list, LeptosRoutes};
+ use wasm_bindgen::prelude::*;
+ use leptos::*;
+
+ #[wasm_bindgen]
+ pub struct Handler(axum_js_fetch::App);
+
+ #[wasm_bindgen]
+ impl Handler {
+ pub async fn new() -> Self {
+ console_log::init_with_level(Level::Debug);
+ console_error_panic_hook::set_once();
+
+ let leptos_options = LeptosOptions::builder().output_name("client").site_pkg_dir("pkg").build();
+
+
+ let routes = generate_route_list(|cx| view! { cx, }).await;
+
+ // build our application with a route
+ let app: axum::Router<(), axum::body::Body> = Router::new()
+
+ .leptos_routes(&leptos_options, routes, |cx| view! { cx, } )
+ .with_state(leptos_options);
+
+ info!("creating handler instance");
+
+ Self(axum_js_fetch::App::new(app))
+ }
+
+ pub async fn serve(&self, req: web_sys::Request) -> web_sys::Response {
+ self.0.serve(req).await
+ }
+ }
+}
+}
diff --git a/examples/hackernews_js_fetch/src/routes.rs b/examples/hackernews_js_fetch/src/routes.rs
new file mode 100644
index 0000000000..1525419ff8
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes.rs
@@ -0,0 +1,4 @@
+pub mod nav;
+pub mod stories;
+pub mod story;
+pub mod users;
diff --git a/examples/hackernews_js_fetch/src/routes/nav.rs b/examples/hackernews_js_fetch/src/routes/nav.rs
new file mode 100644
index 0000000000..911c2523db
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/nav.rs
@@ -0,0 +1,30 @@
+use leptos::{component, view, IntoView, Scope};
+use leptos_router::*;
+
+#[component]
+pub fn Nav(cx: Scope) -> impl IntoView {
+ view! { cx,
+
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/stories.rs b/examples/hackernews_js_fetch/src/routes/stories.rs
new file mode 100644
index 0000000000..f26eb277c8
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/stories.rs
@@ -0,0 +1,164 @@
+use crate::api;
+use leptos::*;
+use leptos_router::*;
+
+fn category(from: &str) -> &'static str {
+ match from {
+ "new" => "newest",
+ "show" => "show",
+ "ask" => "ask",
+ "job" => "jobs",
+ _ => "news",
+ }
+}
+
+#[component]
+pub fn Stories(cx: Scope) -> impl IntoView {
+ let query = use_query_map(cx);
+ let params = use_params_map(cx);
+ let page = move || {
+ query
+ .with(|q| q.get("page").and_then(|page| page.parse::().ok()))
+ .unwrap_or(1)
+ };
+ let story_type = move || {
+ params
+ .with(|p| p.get("stories").cloned())
+ .unwrap_or_else(|| "top".to_string())
+ };
+ let stories = create_resource(
+ cx,
+ move || (page(), story_type()),
+ move |(page, story_type)| async move {
+ let path = format!("{}?page={}", category(&story_type), page);
+ api::fetch_api::>(cx, &api::story(&path)).await
+ },
+ );
+ let (pending, set_pending) = create_signal(cx, false);
+
+ let hide_more_link = move |cx| {
+ pending()
+ || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28
+ };
+
+ view! {
+ cx,
+
+
+
+ {move || if page() > 1 {
+ view! {
+ cx,
+
+ "< prev"
+
+ }.into_any()
+ } else {
+ view! {
+ cx,
+
+ "< prev"
+
+ }.into_any()
+ }}
+
+
"page " {page}
+
"Loading..." }
+ >
+
+
+ "more >"
+
+
+
+
+
+
+
"Loading..." }
+ set_pending=set_pending.into()
+ >
+ {move || match stories.read(cx) {
+ None => None,
+ Some(None) => Some(view! { cx, "Error loading stories."
}.into_any()),
+ Some(Some(stories)) => {
+ Some(view! { cx,
+
+ }.into_any())
+ }
+ }}
+
+
+
+
+ }
+}
+
+#[component]
+fn Story(cx: Scope, story: api::Story) -> impl IntoView {
+ view! { cx,
+
+ {story.points}
+
+ {if !story.url.starts_with("item?id=") {
+ view! { cx,
+
+
+ {story.title.clone()}
+
+ "("{story.domain}")"
+
+ }.into_view(cx)
+ } else {
+ let title = story.title.clone();
+ view! { cx, {title.clone()} }.into_view(cx)
+ }}
+
+
+
+ {if story.story_type != "job" {
+ view! { cx,
+
+ {"by "}
+ {story.user.map(|user| view ! { cx, {user.clone()}})}
+ {format!(" {} | ", story.time_ago)}
+
+ {if story.comments_count.unwrap_or_default() > 0 {
+ format!("{} comments", story.comments_count.unwrap_or_default())
+ } else {
+ "discuss".into()
+ }}
+
+
+ }.into_view(cx)
+ } else {
+ let title = story.title.clone();
+ view! { cx, {title.clone()} }.into_view(cx)
+ }}
+
+ {(story.story_type != "link").then(|| view! { cx,
+ " "
+ {story.story_type}
+ })}
+
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/story.rs b/examples/hackernews_js_fetch/src/routes/story.rs
new file mode 100644
index 0000000000..34c9d1b657
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/story.rs
@@ -0,0 +1,128 @@
+use crate::api;
+use leptos::*;
+use leptos_meta::*;
+use leptos_router::*;
+
+#[component]
+pub fn Story(cx: Scope) -> impl IntoView {
+ let params = use_params_map(cx);
+ let story = create_resource(
+ cx,
+ move || params().get("id").cloned().unwrap_or_default(),
+ move |id| async move {
+ if id.is_empty() {
+ None
+ } else {
+ api::fetch_api::(
+ cx,
+ &api::story(&format!("item/{id}")),
+ )
+ .await
+ }
+ },
+ );
+ let meta_description = move || {
+ story
+ .read(cx)
+ .and_then(|story| story.map(|story| story.title))
+ .unwrap_or_else(|| "Loading story...".to_string())
+ };
+
+ view! { cx,
+ <>
+
+
+ {move || story.read(cx).map(|story| match story {
+ None => view! { cx, "Error loading this story."
},
+ Some(story) => view! { cx,
+
+
+
+
+ }})
+ }
+
+ >
+ }
+}
+
+#[component]
+pub fn Comment(cx: Scope, comment: api::Comment) -> impl IntoView {
+ let (open, set_open) = create_signal(cx, true);
+
+ view! { cx,
+
+ }
+}
+
+fn pluralize(n: usize) -> &'static str {
+ if n == 1 {
+ " reply"
+ } else {
+ " replies"
+ }
+}
diff --git a/examples/hackernews_js_fetch/src/routes/users.rs b/examples/hackernews_js_fetch/src/routes/users.rs
new file mode 100644
index 0000000000..80b1e90107
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/users.rs
@@ -0,0 +1,47 @@
+use crate::api::{self, User};
+use leptos::*;
+use leptos_router::*;
+
+#[component]
+pub fn User(cx: Scope) -> impl IntoView {
+ let params = use_params_map(cx);
+ let user = create_resource(
+ cx,
+ move || params().get("id").cloned().unwrap_or_default(),
+ move |id| async move {
+ if id.is_empty() {
+ None
+ } else {
+ api::fetch_api::(cx, &api::user(&id)).await
+ }
+ },
+ );
+ view! { cx,
+
+
+ {move || user.read(cx).map(|user| match user {
+ None => view! { cx, "User not found."
}.into_any(),
+ Some(user) => view! { cx,
+
+ }.into_any()
+ })}
+
+
+ }
+}
From d69e7295a855a2aefe7703d7715c31d73a224bc3 Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Thu, 17 Aug 2023 00:51:09 -0400
Subject: [PATCH 04/17] bump axum fetch
---
examples/hackernews_js_fetch/Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/hackernews_js_fetch/Cargo.toml b/examples/hackernews_js_fetch/Cargo.toml
index 7a364cd48f..93aff037c4 100644
--- a/examples/hackernews_js_fetch/Cargo.toml
+++ b/examples/hackernews_js_fetch/Cargo.toml
@@ -30,7 +30,7 @@ http = { version = "0.2.8", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal", "Request", "Response"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4.37", features = ["futures-core-03-stream"], optional = true }
-axum-js-fetch = { path = "../../../axum-js-fetch", optional = true }
+axum-js-fetch = { version = "0.2", optional = true }
[features]
default = ["csr"]
From 6c633437b0ac9b177c11dfa2490f527d4db885f3 Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Thu, 17 Aug 2023 01:02:27 -0400
Subject: [PATCH 05/17] run cargo fmt
---
integrations/axum/src/lib.rs | 51 ++++++++++++++++++++----------------
1 file changed, 29 insertions(+), 22 deletions(-)
diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs
index 70b518f0b3..c3d0c7845e 100644
--- a/integrations/axum/src/lib.rs
+++ b/integrations/axum/src/lib.rs
@@ -942,39 +942,46 @@ where
let full_path = format!("http://leptos.dev{path}");
let (tx, rx) = futures::channel::oneshot::channel();
- spawn_local(
- async move {
- let app = {
- let full_path = full_path.clone();
- let (req, req_parts) = generate_request_and_parts(req).await;
- move |cx| {
- provide_contexts(cx, full_path, req_parts, req.into(), default_res_options);
- app_fn(cx).into_view(cx)
- }
- };
+ spawn_local(async move {
+ let app = {
+ let full_path = full_path.clone();
+ let (req, req_parts) =
+ generate_request_and_parts(req).await;
+ move |cx| {
+ provide_contexts(
+ cx,
+ full_path,
+ req_parts,
+ req.into(),
+ default_res_options,
+ );
+ app_fn(cx).into_view(cx)
+ }
+ };
- let (stream, runtime, scope) =
+ let (stream, runtime, scope) =
render_to_stream_in_order_with_prefix_undisposed_with_context(
app,
|_| "".into(),
add_context,
);
- // Extract the value of ResponseOptions from here
- let cx = leptos::Scope { runtime, id: scope };
- let res_options =
- use_context::(cx).unwrap();
+ // Extract the value of ResponseOptions from here
+ let cx = leptos::Scope { runtime, id: scope };
+ let res_options =
+ use_context::(cx).unwrap();
- let html = build_async_response(stream, &options, runtime, scope).await;
+ let html =
+ build_async_response(stream, &options, runtime, scope)
+ .await;
- let new_res_parts = res_options.0.read().clone();
+ let new_res_parts = res_options.0.read().clone();
- let mut writable = res_options2.0.write();
- *writable = new_res_parts;
+ let mut writable = res_options2.0.write();
+ *writable = new_res_parts;
- _ = tx.send(html);
- }
- );
+ _ = tx.send(html);
+ });
let html = rx.await.expect("to complete HTML rendering");
From 62268b8d175795166d38e23d80ac43051b15e2c0 Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Thu, 17 Aug 2023 22:57:34 -0400
Subject: [PATCH 06/17] add wasm feature and fix test
---
examples/hackernews_js_fetch/Cargo.toml | 2 +-
integrations/axum/Cargo.toml | 2 ++
integrations/axum/src/lib.rs | 4 ++++
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/examples/hackernews_js_fetch/Cargo.toml b/examples/hackernews_js_fetch/Cargo.toml
index 93aff037c4..8d4d787b99 100644
--- a/examples/hackernews_js_fetch/Cargo.toml
+++ b/examples/hackernews_js_fetch/Cargo.toml
@@ -43,7 +43,7 @@ ssr = [
"dep:wasm-bindgen-futures",
"dep:axum-js-fetch",
"leptos/ssr",
- "leptos_axum",
+ "leptos_axum/wasm",
"leptos_meta/ssr",
"leptos_router/ssr",
]
diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml
index 839905c637..fcf0566edf 100644
--- a/integrations/axum/Cargo.toml
+++ b/integrations/axum/Cargo.toml
@@ -25,3 +25,5 @@ once_cell = "1.17"
[features]
nonce = ["leptos/nonce"]
+wasm = []
+default = ["tokio/full", "axum/macros"]
diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs
index c3d0c7845e..ca2bc98480 100644
--- a/integrations/axum/src/lib.rs
+++ b/integrations/axum/src/lib.rs
@@ -161,6 +161,7 @@ pub async fn generate_request_and_parts(
/// use std::net::SocketAddr;
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let addr = SocketAddr::from(([127, 0, 0, 1], 8082));
@@ -380,6 +381,7 @@ pub type PinnedHtmlStream =
/// }
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
@@ -453,6 +455,7 @@ where
/// }
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
@@ -840,6 +843,7 @@ fn provide_contexts(
/// }
///
/// # if false { // don't actually try to run a server in a doctest...
+/// #[cfg(feature = "default")]
/// #[tokio::main]
/// async fn main() {
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
From 166a7a40053e43f142e4a2e66eba35dd5a1ff17e Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Fri, 18 Aug 2023 10:36:07 -0400
Subject: [PATCH 07/17] add task spawning macro
---
integrations/axum/Cargo.toml | 1 +
integrations/axum/src/lib.rs | 34 +++++++++++++++++++++++-----------
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml
index fcf0566edf..608c2dffae 100644
--- a/integrations/axum/Cargo.toml
+++ b/integrations/axum/Cargo.toml
@@ -22,6 +22,7 @@ parking_lot = "0.12.1"
tokio-util = { version = "0.7.7", features = ["rt"] }
tracing = "0.1.37"
once_cell = "1.17"
+cfg-if = "1.0.0"
[features]
nonce = ["leptos/nonce"]
diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs
index ca2bc98480..f5ca0a451a 100644
--- a/integrations/axum/src/lib.rs
+++ b/integrations/axum/src/lib.rs
@@ -196,6 +196,21 @@ pub async fn handle_server_fns(
handle_server_fns_inner(fn_name, headers, query, |_| {}, req).await
}
+/// Leptos pool causes wasm to panic and leptos_reactive::spawn::spawn_local causes native
+/// to panic so we define a macro to conditionally compile the correct code.
+macro_rules! spawn_task {
+ ($block:expr) => {
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "wasm")] {
+ spawn_local($block);
+ } else if #[cfg(feature = "default")] {
+ let pool_handle = get_leptos_pool();
+ pool_handle.spawn_pinned(move || { $block });
+ }
+ }
+ };
+}
+
/// An Axum handlers to listens for a request with Leptos server function arguments in the body,
/// run the server function if found, and return the resulting [Response].
///
@@ -236,12 +251,10 @@ async fn handle_server_fns_inner(
.unwrap_or(fn_name);
let (tx, rx) = futures::channel::oneshot::channel();
- let pool_handle = get_leptos_pool();
- pool_handle.spawn_pinned(move || {
- async move {
- let res = if let Some(server_fn) =
- server_fn_by_path(fn_name.as_str())
- {
+
+ spawn_task!(async move {
+ let res =
+ if let Some(server_fn) = server_fn_by_path(fn_name.as_str()) {
let runtime = create_runtime();
let (cx, disposer) = raw_scope_and_disposer(runtime);
@@ -349,8 +362,7 @@ async fn handle_server_fns_inner(
}
.expect("could not build Response");
- _ = tx.send(res);
- }
+ _ = tx.send(res);
});
rx.await.unwrap()
@@ -609,7 +621,7 @@ where
let (tx, rx) = futures::channel::mpsc::channel(8);
let current_span = tracing::Span::current();
- spawn_local(async move {
+ spawn_task!(async move {
let app = {
// Need to get the path and query string of the Request
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
@@ -773,7 +785,7 @@ where
let (tx, rx) = futures::channel::mpsc::channel(8);
let current_span = tracing::Span::current();
- spawn_local(async move {
+ spawn_task!(async move {
let app = {
let full_path = full_path.clone();
let (req, req_parts) = generate_request_and_parts(req).await;
@@ -946,7 +958,7 @@ where
let full_path = format!("http://leptos.dev{path}");
let (tx, rx) = futures::channel::oneshot::channel();
- spawn_local(async move {
+ spawn_task!(async move {
let app = {
let full_path = full_path.clone();
let (req, req_parts) =
From a09c77d0b87e0c58b56344d21f8502177d891a2d Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Mon, 21 Aug 2023 12:09:44 -0400
Subject: [PATCH 08/17] add counter server fn example
---
examples/hackernews_js_fetch/Cargo.toml | 3 +-
examples/hackernews_js_fetch/src/lib.rs | 9 +-
examples/hackernews_js_fetch/src/routes.rs | 1 +
.../src/routes/counters.rs | 89 +++++++++++++++++++
4 files changed, 99 insertions(+), 3 deletions(-)
create mode 100644 examples/hackernews_js_fetch/src/routes/counters.rs
diff --git a/examples/hackernews_js_fetch/Cargo.toml b/examples/hackernews_js_fetch/Cargo.toml
index 8d4d787b99..99b2ff656c 100644
--- a/examples/hackernews_js_fetch/Cargo.toml
+++ b/examples/hackernews_js_fetch/Cargo.toml
@@ -30,7 +30,8 @@ http = { version = "0.2.8", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal", "Request", "Response"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4.37", features = ["futures-core-03-stream"], optional = true }
-axum-js-fetch = { version = "0.2", optional = true }
+axum-js-fetch = { version = "0.2.1", optional = true }
+lazy_static = "1.4.0"
[features]
default = ["csr"]
diff --git a/examples/hackernews_js_fetch/src/lib.rs b/examples/hackernews_js_fetch/src/lib.rs
index 27c7949f86..fe336f29d8 100644
--- a/examples/hackernews_js_fetch/src/lib.rs
+++ b/examples/hackernews_js_fetch/src/lib.rs
@@ -7,7 +7,7 @@ mod api;
pub mod error_template;
pub mod fallback;
mod routes;
-use routes::{nav::*, stories::*, story::*, users::*};
+use routes::{counters::*, nav::*, stories::*, story::*, users::*};
#[component]
pub fn App(cx: Scope) -> impl IntoView {
@@ -25,6 +25,7 @@ pub fn App(cx: Scope) -> impl IntoView {
+
@@ -49,6 +50,7 @@ cfg_if! {
use axum::{
Router,
+ routing::post
};
use leptos_axum::{generate_route_list, LeptosRoutes};
use wasm_bindgen::prelude::*;
@@ -68,10 +70,13 @@ cfg_if! {
let routes = generate_route_list(|cx| view! { cx, }).await;
+ ClearServerCount::register_explicit().unwrap();
+ AdjustServerCount::register_explicit().unwrap();
+ GetServerCount::register_explicit().unwrap();
// build our application with a route
let app: axum::Router<(), axum::body::Body> = Router::new()
-
.leptos_routes(&leptos_options, routes, |cx| view! { cx, } )
+ .route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.with_state(leptos_options);
info!("creating handler instance");
diff --git a/examples/hackernews_js_fetch/src/routes.rs b/examples/hackernews_js_fetch/src/routes.rs
index 1525419ff8..9ed9dd4cc0 100644
--- a/examples/hackernews_js_fetch/src/routes.rs
+++ b/examples/hackernews_js_fetch/src/routes.rs
@@ -1,3 +1,4 @@
+pub mod counters;
pub mod nav;
pub mod stories;
pub mod story;
diff --git a/examples/hackernews_js_fetch/src/routes/counters.rs b/examples/hackernews_js_fetch/src/routes/counters.rs
new file mode 100644
index 0000000000..12cc5e89cf
--- /dev/null
+++ b/examples/hackernews_js_fetch/src/routes/counters.rs
@@ -0,0 +1,89 @@
+/// This file is mostly copied from the counters isomorphic example
+/// just to demonstrate server actions in wasm
+
+use leptos::*;
+use std::sync::atomic::{AtomicI32, Ordering};
+static COUNT: AtomicI32 = AtomicI32::new(0);
+
+
+
+// "/api" is an optional prefix that allows you to locate server functions wherever you'd like on the server
+#[server(GetServerCount, "/api")]
+pub async fn get_server_count() -> Result {
+ Ok(COUNT.load(Ordering::Relaxed))
+}
+
+#[server(AdjustServerCount, "/api")]
+pub async fn adjust_server_count(
+ delta: i32,
+ msg: String,
+) -> Result {
+ let new = COUNT.load(Ordering::Relaxed) + delta;
+ COUNT.store(new, Ordering::Relaxed);
+ println!("message = {:?}", msg);
+ Ok(new)
+}
+
+#[server(ClearServerCount, "/api")]
+pub async fn clear_server_count() -> Result {
+ COUNT.store(0, Ordering::Relaxed);
+ Ok(0)
+}
+
+// This is an example of "single-user" server functions
+// The counter value is loaded from the server, and re-fetches whenever
+// it's invalidated by one of the user's own actions
+// This is the typical pattern for a CRUD app
+#[component]
+pub fn Counter(cx: Scope) -> impl IntoView {
+ let dec = create_action(cx, |_| adjust_server_count(-1, "decing".into()));
+ let inc = create_action(cx, |_| adjust_server_count(1, "incing".into()));
+ let clear = create_action(cx, |_| clear_server_count());
+ let counter = create_resource(
+ cx,
+ move || {
+ (
+ dec.version().get(),
+ inc.version().get(),
+ clear.version().get(),
+ )
+ },
+ |_| get_server_count(),
+ );
+
+ let value = move || {
+ counter
+ .read(cx)
+ .map(|count| count.unwrap_or(0))
+ .unwrap_or(0)
+ };
+ let error_msg = move || {
+ counter.read(cx).and_then(|res| match res {
+ Ok(_) => None,
+ Err(e) => Some(e),
+ })
+ };
+
+ view! { cx,
+
+
"Simple Counter"
+
+ "This counter sets the value on the server and automatically reloads the new value."
+
+
+
+
+ "Value: " {value}
+
+
+ {move || {
+ error_msg()
+ .map(|msg| {
+ view! { cx,
"Error: " {msg.to_string()}
}
+ })
+ }}
+
+ }
+}
+
+
From 50d389e2fdfa001fc4a662bdb09bdc1cab5853d3 Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Mon, 21 Aug 2023 12:16:10 -0400
Subject: [PATCH 09/17] cargo fmt
---
examples/hackernews_js_fetch/src/routes/counters.rs | 5 -----
1 file changed, 5 deletions(-)
diff --git a/examples/hackernews_js_fetch/src/routes/counters.rs b/examples/hackernews_js_fetch/src/routes/counters.rs
index 12cc5e89cf..9d420a8508 100644
--- a/examples/hackernews_js_fetch/src/routes/counters.rs
+++ b/examples/hackernews_js_fetch/src/routes/counters.rs
@@ -1,12 +1,9 @@
/// This file is mostly copied from the counters isomorphic example
/// just to demonstrate server actions in wasm
-
use leptos::*;
use std::sync::atomic::{AtomicI32, Ordering};
static COUNT: AtomicI32 = AtomicI32::new(0);
-
-
// "/api" is an optional prefix that allows you to locate server functions wherever you'd like on the server
#[server(GetServerCount, "/api")]
pub async fn get_server_count() -> Result {
@@ -85,5 +82,3 @@ pub fn Counter(cx: Scope) -> impl IntoView {
}
}
-
-
From 84184483a35a09ac4e92d3e8e8392257e8e4d8c3 Mon Sep 17 00:00:00 2001
From: Sean Aye
Date: Tue, 22 Aug 2023 12:07:19 -0400
Subject: [PATCH 10/17] cargo fmt
---
integrations/axum/src/lib.rs | 34 ++++++++++++++++++++--------------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs
index b01427cd6b..fc12a834b0 100644
--- a/integrations/axum/src/lib.rs
+++ b/integrations/axum/src/lib.rs
@@ -1168,29 +1168,35 @@ where
let full_path = format!("http://leptos.dev{path}");
let (tx, rx) = futures::channel::oneshot::channel();
-
+
spawn_task!(async move {
- let app = {
- let full_path = full_path.clone();
- let (req, req_parts) = generate_request_and_parts(req).await;
- move || {
- provide_contexts(full_path, req_parts, req.into(), default_res_options);
- app_fn().into_view()
- }
- };
+ let app = {
+ let full_path = full_path.clone();
+ let (req, req_parts) =
+ generate_request_and_parts(req).await;
+ move || {
+ provide_contexts(
+ full_path,
+ req_parts,
+ req.into(),
+ default_res_options,
+ );
+ app_fn().into_view()
+ }
+ };
- let (stream, runtime) =
+ let (stream, runtime) =
render_to_stream_in_order_with_prefix_undisposed_with_context(
app,
|| "".into(),
add_context,
);
- // Extract the value of ResponseOptions from here
- let res_options =
- use_context::().unwrap();
+ // Extract the value of ResponseOptions from here
+ let res_options = use_context::().unwrap();
- let html = build_async_response(stream, &options, runtime).await;
+ let html =
+ build_async_response(stream, &options, runtime).await;
let new_res_parts = res_options.0.read().clone();
From e6477d16cad7010437918112cf69c88ff0e9f335 Mon Sep 17 00:00:00 2001
From: Greg Johnston
Date: Tue, 22 Aug 2023 21:37:13 -0400
Subject: [PATCH 11/17] upgrade example to 0.5
---
examples/hackernews_js_fetch/src/api.rs | 8 +--
.../hackernews_js_fetch/src/error_template.rs | 12 ++--
examples/hackernews_js_fetch/src/fallback.rs | 2 +-
examples/hackernews_js_fetch/src/lib.rs | 16 ++---
.../src/routes/counters.rs | 23 +++-----
.../hackernews_js_fetch/src/routes/nav.rs | 6 +-
.../hackernews_js_fetch/src/routes/stories.rs | 59 +++++++++----------
.../hackernews_js_fetch/src/routes/story.rs | 40 ++++++-------
.../hackernews_js_fetch/src/routes/users.rs | 19 +++---
9 files changed, 87 insertions(+), 98 deletions(-)
diff --git a/examples/hackernews_js_fetch/src/api.rs b/examples/hackernews_js_fetch/src/api.rs
index 93c2ab42f5..b30bbb3cc1 100644
--- a/examples/hackernews_js_fetch/src/api.rs
+++ b/examples/hackernews_js_fetch/src/api.rs
@@ -1,4 +1,4 @@
-use leptos::{Scope, Serializable};
+use leptos::Serializable;
use serde::{Deserialize, Serialize};
pub fn story(path: &str) -> String {
@@ -10,7 +10,7 @@ pub fn user(path: &str) -> String {
}
#[cfg(not(feature = "ssr"))]
-pub async fn fetch_api(cx: Scope, path: &str) -> Option
+pub async fn fetch_api(path: &str) -> Option
where
T: Serializable,
{
@@ -19,7 +19,7 @@ where
// abort in-flight requests if the Scope is disposed
// i.e., if we've navigated away from this page
- leptos::on_cleanup(cx, move || {
+ leptos::on_cleanup(move || {
if let Some(abort_controller) = abort_controller {
abort_controller.abort()
}
@@ -39,7 +39,7 @@ where
}
#[cfg(feature = "ssr")]
-pub async fn fetch_api(_cx: Scope, path: &str) -> Option
+pub async fn fetch_api(path: &str) -> Option
where
T: Serializable,
{
diff --git a/examples/hackernews_js_fetch/src/error_template.rs b/examples/hackernews_js_fetch/src/error_template.rs
index 17fd4ca55a..2937f8928c 100644
--- a/examples/hackernews_js_fetch/src/error_template.rs
+++ b/examples/hackernews_js_fetch/src/error_template.rs
@@ -1,13 +1,13 @@
-use leptos::{view, Errors, For, IntoView, RwSignal, Scope, View};
+use leptos::{view, Errors, For, IntoView, RwSignal, View};
// A basic function to display errors served by the error boundaries. Feel free to do more complicated things
// here than just displaying them
-pub fn error_template(cx: Scope, errors: Option>) -> View {
+pub fn error_template(errors: Option>) -> View {
let Some(errors) = errors else {
panic!("No Errors found and we expected errors!");
};
- view! {cx,
+ view! {
"Errors"
>) -> View {
// a unique key for each item as a reference
key=|(key, _)| key.clone()
// renders each item to a view
- view= move |cx, (_, error)| {
+ view= move |(_, error)| {
let error_string = error.to_string();
view! {
- cx,
+
"Error: " {error_string}
}
}
/>
}
- .into_view(cx)
+ .into_view()
}
diff --git a/examples/hackernews_js_fetch/src/fallback.rs b/examples/hackernews_js_fetch/src/fallback.rs
index c3cde0d2e3..60cfbdb153 100644
--- a/examples/hackernews_js_fetch/src/fallback.rs
+++ b/examples/hackernews_js_fetch/src/fallback.rs
@@ -20,7 +20,7 @@ if #[cfg(feature = "ssr")] {
if res.status() == StatusCode::OK {
res.into_response()
} else{
- let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| error_template(cx, None));
+ let handler = leptos_axum::render_app_to_stream(options.to_owned(), || error_template(None));
handler(req).await.into_response()
}
}
diff --git a/examples/hackernews_js_fetch/src/lib.rs b/examples/hackernews_js_fetch/src/lib.rs
index fe336f29d8..6d5832bfcc 100644
--- a/examples/hackernews_js_fetch/src/lib.rs
+++ b/examples/hackernews_js_fetch/src/lib.rs
@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
-use leptos::{component, view, IntoView, Scope};
+use leptos::{component, view, IntoView};
use leptos_meta::*;
use leptos_router::*;
use log::{info, Level};
@@ -10,10 +10,10 @@ mod routes;
use routes::{counters::*, nav::*, stories::*, story::*, users::*};
#[component]
-pub fn App(cx: Scope) -> impl IntoView {
- provide_meta_context(cx);
+pub fn App() -> impl IntoView {
+ provide_meta_context();
view! {
- cx,
+
<>
@@ -42,8 +42,8 @@ cfg_if! {
pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
- leptos::mount_to_body(move |cx| {
- view! { cx, }
+ leptos::mount_to_body(move || {
+ view! { }
});
}
} else if #[cfg(feature = "ssr")] {
@@ -68,14 +68,14 @@ cfg_if! {
let leptos_options = LeptosOptions::builder().output_name("client").site_pkg_dir("pkg").build();
- let routes = generate_route_list(|cx| view! { cx, }).await;
+ let routes = generate_route_list(|| view! { }).await;
ClearServerCount::register_explicit().unwrap();
AdjustServerCount::register_explicit().unwrap();
GetServerCount::register_explicit().unwrap();
// build our application with a route
let app: axum::Router<(), axum::body::Body> = Router::new()
- .leptos_routes(&leptos_options, routes, |cx| view! { cx, } )
+ .leptos_routes(&leptos_options, routes, || view! { } )
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.with_state(leptos_options);
diff --git a/examples/hackernews_js_fetch/src/routes/counters.rs b/examples/hackernews_js_fetch/src/routes/counters.rs
index 9d420a8508..618528e4dd 100644
--- a/examples/hackernews_js_fetch/src/routes/counters.rs
+++ b/examples/hackernews_js_fetch/src/routes/counters.rs
@@ -32,12 +32,11 @@ pub async fn clear_server_count() -> Result {
// it's invalidated by one of the user's own actions
// This is the typical pattern for a CRUD app
#[component]
-pub fn Counter(cx: Scope) -> impl IntoView {
- let dec = create_action(cx, |_| adjust_server_count(-1, "decing".into()));
- let inc = create_action(cx, |_| adjust_server_count(1, "incing".into()));
- let clear = create_action(cx, |_| clear_server_count());
+pub fn Counter() -> impl IntoView {
+ let dec = create_action(|_| adjust_server_count(-1, "decing".into()));
+ let inc = create_action(|_| adjust_server_count(1, "incing".into()));
+ let clear = create_action(|_| clear_server_count());
let counter = create_resource(
- cx,
move || {
(
dec.version().get(),
@@ -48,20 +47,16 @@ pub fn Counter(cx: Scope) -> impl IntoView {
|_| get_server_count(),
);
- let value = move || {
- counter
- .read(cx)
- .map(|count| count.unwrap_or(0))
- .unwrap_or(0)
- };
+ let value =
+ move || counter.get().map(|count| count.unwrap_or(0)).unwrap_or(0);
let error_msg = move || {
- counter.read(cx).and_then(|res| match res {
+ counter.get().and_then(|res| match res {
Ok(_) => None,
Err(e) => Some(e),
})
};
- view! { cx,
+ view! {
"Simple Counter"
@@ -76,7 +71,7 @@ pub fn Counter(cx: Scope) -> impl IntoView {
{move || {
error_msg()
.map(|msg| {
- view! { cx,
"Error: " {msg.to_string()}
}
+ view! {
"Error: " {msg.to_string()}
}
})
}}
diff --git a/examples/hackernews_js_fetch/src/routes/nav.rs b/examples/hackernews_js_fetch/src/routes/nav.rs
index 911c2523db..b4dd03cc01 100644
--- a/examples/hackernews_js_fetch/src/routes/nav.rs
+++ b/examples/hackernews_js_fetch/src/routes/nav.rs
@@ -1,9 +1,9 @@
-use leptos::{component, view, IntoView, Scope};
+use leptos::{component, view, IntoView};
use leptos_router::*;
#[component]
-pub fn Nav(cx: Scope) -> impl IntoView {
- view! { cx,
+pub fn Nav() -> impl IntoView {
+ view! {
+ {if story.comments_count.unwrap_or_default() > 0 { + format!("{} comments", story.comments_count.unwrap_or_default()) + } else { + "No comments yet.".into() + }} +
++ }
+ />
+
+