From 63b18373152f7c34cf286f27c0d45b7860ee9211 Mon Sep 17 00:00:00 2001 From: benwis Date: Fri, 6 Jan 2023 14:08:45 -0800 Subject: [PATCH 01/14] First pass of method to generate routelist --- examples/todo_app_sqlite/src/main.rs | 5 ++++ examples/todo_app_sqlite/src/todo.rs | 12 +++++++--- router/src/components/router.rs | 19 ++++++++++++---- router/src/components/routes.rs | 7 +++++- router/src/extract_routes.rs | 34 ++++++++++++++++++++++++++++ router/src/lib.rs | 2 ++ 6 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 router/src/extract_routes.rs diff --git a/examples/todo_app_sqlite/src/main.rs b/examples/todo_app_sqlite/src/main.rs index d5af05e74..63b92df28 100644 --- a/examples/todo_app_sqlite/src/main.rs +++ b/examples/todo_app_sqlite/src/main.rs @@ -1,5 +1,6 @@ use cfg_if::cfg_if; use leptos::*; +use leptos_router::generate_route_list; mod todo; // boilerplate to run in different modes @@ -28,6 +29,10 @@ cfg_if! { let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); let addr = conf.leptos_options.site_address.clone(); + println!("BEFFOORE"); + let routes = generate_route_list(|cx| view! { cx, }); + println!("HIIIIIIIIIIII"); + println!("Routes: {:#?}", routes); HttpServer::new(move || { let leptos_options = &conf.leptos_options; diff --git a/examples/todo_app_sqlite/src/todo.rs b/examples/todo_app_sqlite/src/todo.rs index 41ced4b23..02e4017da 100644 --- a/examples/todo_app_sqlite/src/todo.rs +++ b/examples/todo_app_sqlite/src/todo.rs @@ -38,9 +38,11 @@ cfg_if! { pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { // this is just an example of how to access server context injected in the handlers let req = - use_context::(cx).expect("couldn't get HttpRequest from context"); - println!("req.path = {:?}", req.path()); - + use_context::(cx); + + if let Some(req) = req{ + println!("req.path = {:#?}", req.path()); + } use futures::TryStreamExt; let mut conn = db().await?; @@ -103,6 +105,10 @@ pub fn TodoApp(cx: Scope) -> impl IntoView { cx, }/> + + }/> diff --git a/router/src/components/router.rs b/router/src/components/router.rs index 58e4e1f50..f2b66055d 100644 --- a/router/src/components/router.rs +++ b/router/src/components/router.rs @@ -11,8 +11,8 @@ use wasm_bindgen::JsCast; use leptos_reactive::use_transition; use crate::{ - create_location, matching::resolve_path, History, Location, LocationChange, RouteContext, - RouterIntegrationContext, State, + create_location, matching::resolve_path, Branch, History, Location, LocationChange, + RouteContext, RouterIntegrationContext, State, }; #[cfg(not(feature = "ssr"))] @@ -49,6 +49,7 @@ pub struct RouterContext { pub(crate) struct RouterContextInner { pub location: Location, pub base: RouteContext, + pub possible_routes: RefCell>>, base_path: String, history: Box, cx: Scope, @@ -104,10 +105,10 @@ impl RouterContext { value: base_path.to_string(), replace: true, scroll: false, - state: State(None) + state: State(None), }); } - } + } // the current URL let (reference, set_reference) = create_signal(cx, source.with(|s| s.value.clone())); @@ -153,6 +154,7 @@ impl RouterContext { referrers, state, set_state, + possible_routes: Default::default(), }); // handle all click events on anchor tags @@ -175,6 +177,15 @@ impl RouterContext { pub fn base(&self) -> RouteContext { self.inner.base.clone() } + + /// A list of all possible routes this router can match. + pub fn possible_branches(&self) -> Vec { + self.inner + .possible_routes + .borrow() + .clone() + .unwrap_or_default() + } } impl RouterContextInner { diff --git a/router/src/components/routes.rs b/router/src/components/routes.rs index 6c83d4689..de7784f58 100644 --- a/router/src/components/routes.rs +++ b/router/src/components/routes.rs @@ -12,7 +12,7 @@ use crate::{ expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition, RouteMatch, }, - RouteContext, RouterContext, + PossibleBranchContext, RouteContext, RouterContext, }; /// Contains route definitions and manages the actual routing process. @@ -42,6 +42,7 @@ pub fn Routes( }) .cloned() .collect::>(); + create_branches( &children, &base.unwrap_or_default(), @@ -49,6 +50,10 @@ pub fn Routes( &mut branches, ); + if let Some(context) = use_context::(cx) { + *context.0.borrow_mut() = branches.clone(); + } + // whenever path changes, update matches let matches = create_memo(cx, { let router = router.clone(); diff --git a/router/src/extract_routes.rs b/router/src/extract_routes.rs new file mode 100644 index 000000000..ed4b68acb --- /dev/null +++ b/router/src/extract_routes.rs @@ -0,0 +1,34 @@ +use leptos::*; +use std::{cell::RefCell, rc::Rc}; + +use crate::{Branch, RouterIntegrationContext, ServerIntegration}; + +/// Context to contain all possible routes. +#[derive(Clone, Default, Debug)] +pub struct PossibleBranchContext(pub(crate) Rc>>); + +/// Generates a list of all routes this application could possibly serve. +#[cfg(feature = "ssr")] +pub fn generate_route_list(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec +where + IV: IntoView + 'static, +{ + let runtime = create_runtime(); + run_scope(runtime, move |cx| { + let integration = ServerIntegration { + path: "http://leptos.rs/".to_string(), + }; + + provide_context(cx, RouterIntegrationContext::new(integration)); + let branches = PossibleBranchContext::default(); + provide_context(cx, branches.clone()); + + let _ = app_fn(cx).into_view(cx); + + let branches = branches.0.borrow(); + branches + .iter() + .flat_map(|branch| branch.routes.last().map(|route| route.pattern.clone())) + .collect() + }) +} diff --git a/router/src/lib.rs b/router/src/lib.rs index 68ec353f8..fd205cb48 100644 --- a/router/src/lib.rs +++ b/router/src/lib.rs @@ -184,12 +184,14 @@ #![cfg_attr(not(feature = "stable"), feature(type_name_of_val))] mod components; +mod extract_routes; mod history; mod hooks; #[doc(hidden)] pub mod matching; pub use components::*; +pub use extract_routes::*; pub use history::*; pub use hooks::*; pub use matching::*; From 677e4f2540b195b71dd021a33744e24a1f9f9b17 Mon Sep 17 00:00:00 2001 From: benwis Date: Fri, 6 Jan 2023 19:52:38 -0800 Subject: [PATCH 02/14] Leptos can now generate routes and provide them to the Axum router. More testing and Actix version to come --- examples/todo_app_sqlite/src/main.rs | 2 +- examples/todo_app_sqlite/src/todo.rs | 1 + examples/todo_app_sqlite_axum/src/main.rs | 18 +++++-- examples/todo_app_sqlite_axum/src/todo.rs | 7 ++- integrations/axum/src/lib.rs | 64 ++++++++++++++++++++++- router/src/extract_routes.rs | 2 +- 6 files changed, 85 insertions(+), 9 deletions(-) diff --git a/examples/todo_app_sqlite/src/main.rs b/examples/todo_app_sqlite/src/main.rs index 63b92df28..2533307b6 100644 --- a/examples/todo_app_sqlite/src/main.rs +++ b/examples/todo_app_sqlite/src/main.rs @@ -31,7 +31,7 @@ cfg_if! { let addr = conf.leptos_options.site_address.clone(); println!("BEFFOORE"); let routes = generate_route_list(|cx| view! { cx, }); - println!("HIIIIIIIIIIII"); + println!("Routes: {:#?}", routes); HttpServer::new(move || { diff --git a/examples/todo_app_sqlite/src/todo.rs b/examples/todo_app_sqlite/src/todo.rs index 02e4017da..542d10e71 100644 --- a/examples/todo_app_sqlite/src/todo.rs +++ b/examples/todo_app_sqlite/src/todo.rs @@ -92,6 +92,7 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { #[component] pub fn TodoApp(cx: Scope) -> impl IntoView { + provide_meta_context(cx); view! { cx, diff --git a/examples/todo_app_sqlite_axum/src/main.rs b/examples/todo_app_sqlite_axum/src/main.rs index 1fc84f2e3..00c5e2ada 100644 --- a/examples/todo_app_sqlite_axum/src/main.rs +++ b/examples/todo_app_sqlite_axum/src/main.rs @@ -1,6 +1,5 @@ use cfg_if::cfg_if; use leptos::*; - // boilerplate to run in different modes cfg_if! { if #[cfg(feature = "ssr")] { @@ -13,12 +12,18 @@ if #[cfg(feature = "ssr")] { use todo_app_sqlite_axum::*; use http::StatusCode; use tower_http::services::ServeDir; + use tokio::{sync::RwLock, task::spawn_blocking, task::LocalSet}; + use leptos_axum::{generate_route_list, LeptosRoutes}; + + + use std::sync::Arc; + #[tokio::main] async fn main() { simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging"); - let mut conn = db().await.expect("couldn't connect to DB"); + let conn = db().await.expect("couldn't connect to DB"); /* sqlx::migrate!() .run(&mut conn) .await @@ -53,13 +58,18 @@ if #[cfg(feature = "ssr")] { ) } + let routes = generate_route_list(|cx| view! { cx, }).await; + println!("Routes_Outside: {:#?}",&routes); + + // build our application with a route - let app = Router::new() + let mut app = Router::new() .route("/api/*fn_name", post(leptos_axum::handle_server_fns)) .nest_service("/pkg", pkg_service) // Only need if using wasm-pack. Can be deleted if using cargo-leptos .nest_service(&bundle_path, cargo_leptos_service) // Only needed if using cargo-leptos. Can be deleted if using wasm-pack and cargo-run .nest_service("/static", static_service) - .fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, })); + .leptos_routes(leptos_options, routes, |cx| view! { cx, } ); + // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 4b35ca315..389c569f3 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -39,9 +39,11 @@ cfg_if! { pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { // this is just an example of how to access server context injected in the handlers // http::Request doesn't implement Clone, so more work will be needed to do use_context() on this - let req_parts = use_context::(cx).unwrap(); - println!("\ncalling server fn"); + let req_parts = use_context::(cx); + + if let Some(req_parts) = req_parts{ println!("Uri = {:?}", req_parts.uri); + } use futures::TryStreamExt; @@ -105,6 +107,7 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { #[component] pub fn TodoApp(cx: Scope) -> impl IntoView { + provide_meta_context(cx); view! { cx, diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index b01e0f2a1..7e9f69ae6 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -3,6 +3,7 @@ use axum::{ extract::Path, http::{header::HeaderName, header::HeaderValue, HeaderMap, Request, StatusCode}, response::IntoResponse, + routing::get, }; use futures::{Future, SinkExt, Stream, StreamExt}; use http::{header, method::Method, uri::Uri, version::Version, Response}; @@ -11,7 +12,7 @@ use leptos::*; use leptos_meta::MetaContext; use leptos_router::*; use std::{io, pin::Pin, sync::Arc}; -use tokio::{sync::RwLock, task::spawn_blocking}; +use tokio::{sync::RwLock, task::spawn_blocking, task::LocalSet}; /// A struct to hold the parts of the incoming Request. Since `http::Request` isn't cloneable, we're forced /// to construct this for Leptos to use in Axum @@ -499,3 +500,64 @@ where }) } } + +pub async fn generate_route_list(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec +where + IV: IntoView + 'static, +{ + #[derive(Default, Clone, Debug)] + pub struct Routes(pub Arc>>); + + let routes = Routes::default(); + let routes_inner = routes.clone(); + + let local = LocalSet::new(); + // Run the local task set. + + local + .run_until(async move { + tokio::task::spawn_local(async move { + let routes = leptos_router::generate_route_list_inner(app_fn); + let mut writable = routes_inner.0.write().await; + *writable = routes; + }) + .await + .unwrap(); + }) + .await; + + let routes = routes.0.read().await.to_owned(); + routes.iter().map(|s| s.replace("", "/")).collect() +} + +pub trait LeptosRoutes { + fn leptos_routes( + self, + options: LeptosOptions, + paths: Vec, + app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, + ) -> Self + where + IV: IntoView + 'static; +} + +impl LeptosRoutes for axum::Router { + fn leptos_routes( + self, + options: LeptosOptions, + paths: Vec, + app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, + ) -> Self + where + IV: IntoView + 'static, + { + let mut router = self; + for path in paths.iter() { + router = router.route( + path, + get(render_app_to_stream(options.clone(), app_fn.clone())), + ); + } + router + } +} diff --git a/router/src/extract_routes.rs b/router/src/extract_routes.rs index ed4b68acb..b7850ea54 100644 --- a/router/src/extract_routes.rs +++ b/router/src/extract_routes.rs @@ -9,7 +9,7 @@ pub struct PossibleBranchContext(pub(crate) Rc>>); /// Generates a list of all routes this application could possibly serve. #[cfg(feature = "ssr")] -pub fn generate_route_list(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec +pub fn generate_route_list_inner(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec where IV: IntoView + 'static, { From 5d3cfc6483bdc9cfc89e7bf59658cfe74c5bb023 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 7 Jan 2023 14:49:25 -0800 Subject: [PATCH 03/14] Actix seems to be working now, plus applied Henrik's path recommendations --- examples/todo_app_sqlite/src/main.rs | 17 +++--- examples/todo_app_sqlite/src/todo.rs | 2 +- integrations/actix/Cargo.toml | 1 + integrations/actix/src/lib.rs | 89 +++++++++++++++++++++++----- integrations/axum/src/lib.rs | 17 ++++-- 5 files changed, 95 insertions(+), 31 deletions(-) diff --git a/examples/todo_app_sqlite/src/main.rs b/examples/todo_app_sqlite/src/main.rs index 2533307b6..68b20d293 100644 --- a/examples/todo_app_sqlite/src/main.rs +++ b/examples/todo_app_sqlite/src/main.rs @@ -1,7 +1,7 @@ use cfg_if::cfg_if; use leptos::*; -use leptos_router::generate_route_list; mod todo; +use leptos_actix::{generate_route_list, LeptosRoutes}; // boilerplate to run in different modes cfg_if! { @@ -26,27 +26,24 @@ cfg_if! { crate::todo::register_server_functions(); - let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); let addr = conf.leptos_options.site_address.clone(); - println!("BEFFOORE"); - let routes = generate_route_list(|cx| view! { cx, }); - println!("Routes: {:#?}", routes); + // Generate the list of routes in your Leptos App + let routes = generate_route_list(|cx| view! { cx, }); HttpServer::new(move || { let leptos_options = &conf.leptos_options; let site_root = &leptos_options.site_root; let pkg_dir = &leptos_options.site_pkg_dir; - let bundle_path = format!("/{site_root}/{pkg_dir}"); + let routes = &routes; App::new() - .service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos - .service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run. .service(css) .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) - .route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, })) - //.wrap(middleware::Compress::default()) + .leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, }) + .service(Files::new("/", &site_root)) + //.wrap(middleware::Compress::default()) }) .bind(addr)? .run() diff --git a/examples/todo_app_sqlite/src/todo.rs b/examples/todo_app_sqlite/src/todo.rs index 542d10e71..fa2cdcf4d 100644 --- a/examples/todo_app_sqlite/src/todo.rs +++ b/examples/todo_app_sqlite/src/todo.rs @@ -95,7 +95,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView { provide_meta_context(cx); view! { cx, - +

"My Tasks"

diff --git a/integrations/actix/Cargo.toml b/integrations/actix/Cargo.toml index b41dde60d..fbaa52227 100644 --- a/integrations/actix/Cargo.toml +++ b/integrations/actix/Cargo.toml @@ -19,4 +19,5 @@ leptos_meta = { path = "../../meta", default-features = false, version = "0.1.0- leptos_router = { path = "../../router", default-features = false, version = "0.1.0-alpha", features = [ "ssr", ] } +regex = "1.7.0" tokio = { version = "1.0", features = ["full"] } diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index 40e73be3e..40b1ee8d2 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -1,12 +1,12 @@ -use actix_web::{http::header, web::Bytes, *}; -use futures::{StreamExt}; - +use actix_web::{http::header, web::Bytes, dev::{ServiceFactory, ServiceRequest}, *}; +use futures::StreamExt; use http::StatusCode; use leptos::*; use leptos_meta::*; use leptos_router::*; use std::sync::Arc; use tokio::sync::RwLock; +use regex::Regex; /// This struct lets you define headers and override the status of the Response from an Element or a Server Function /// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses. @@ -279,20 +279,11 @@ where IV: IntoView wasm_output_name.push_str("_bg"); } - let site_ip = &options.site_address.ip().to_string(); let reload_port = options.reload_port; let site_root = &options.site_root; let pkg_path = &options.site_pkg_dir; - // We need to do some logic to check if the site_root is pkg - // if it is, then we need to not add pkg_path. This would mean - // the site was built with cargo run and not cargo-leptos - let bundle_path = match site_root.as_ref() { - "pkg" => "pkg".to_string(), - _ => format!("{}/{}", site_root, pkg_path), - }; - let leptos_autoreload = match std::env::var("LEPTOS_WATCH").is_ok() { true => format!( r#" @@ -326,9 +317,9 @@ where IV: IntoView - - - + + + {leptos_autoreload} "# ); @@ -382,3 +373,71 @@ where IV: IntoView } }) } + +/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically +/// create routes in Actix's App without having to use wildcard matching or fallbacks. Takes in your root app Element +/// as an argument so it can walk you app tree. This version is tailored to generated Actix compatible paths. +pub fn generate_route_list(app_fn: impl FnOnce(leptos::Scope) -> IV + 'static) -> Vec +where + IV: IntoView + 'static, +{ + let mut routes = leptos_router::generate_route_list_inner(app_fn); + + // Empty strings screw with Actix pathing, they need to be "/" + routes = routes.iter().map(|s| { + if s.is_empty() { + return "/".to_string() + } + s.to_string() + }).collect(); + + // Actix's Router doesn't do "/*" or "/*someting", so we need to replace it with "/{tail.*}" + // These are the regular expressions to support that + // Match `*` or `*someword` + let wildcard_re = Regex::new(r"\*.*").unwrap(); + // Match `:some_word` but only capture `some_word` in the groups + let capture_re = Regex::new(r":((?:[^.,/]+)+)[^/]?").unwrap(); + routes.iter().map(|s| + wildcard_re.replace_all(s, "{tail:.*}").to_string() + ) + .map(|s| { + capture_re.replace_all(&s, "{$1}").to_string() + }) + .collect() +} + +/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid +/// having to use wildcards or manually define all routes in multiple places. +pub trait LeptosRoutes { + fn leptos_routes( + self, + options: LeptosOptions, + paths: Vec, + app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, + ) -> Self + where + IV: IntoView + 'static; +} +/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests +/// to those paths to Leptos's renderer. +impl LeptosRoutes for actix_web::App where + T: ServiceFactory{ + fn leptos_routes( + self, + options: LeptosOptions, + paths: Vec, + app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static, + ) -> Self + where + IV: IntoView + 'static, + { + let mut router = self; + for path in paths.iter() { + router = router.route( + path, + render_app_to_stream(options.clone(), app_fn.clone()), + ); + } + router + } +} diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 7e9f69ae6..a92fd9389 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -338,7 +338,7 @@ where // We need to do some logic to check if the site_root is pkg // if it is, then we need to not add pkg_path. This would mean // the site was built with cargo run and not cargo-leptos - let bundle_path = match site_root.as_ref() { + let pkg_path = match site_root.as_ref() { "pkg" => "pkg".to_string(), _ => format!("{}/{}", site_root, pkg_path), }; @@ -389,9 +389,9 @@ where - - - + + + {leptos_autoreload} "# ); @@ -501,6 +501,9 @@ where } } +/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically +/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element +/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. pub async fn generate_route_list(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec where IV: IntoView + 'static, @@ -527,9 +530,12 @@ where .await; let routes = routes.0.read().await.to_owned(); + // Axum's Router defines Root routes as "/" not "" routes.iter().map(|s| s.replace("", "/")).collect() } +/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid +/// having to use wildcards or manually define all routes in multiple places. pub trait LeptosRoutes { fn leptos_routes( self, @@ -540,7 +546,8 @@ pub trait LeptosRoutes { where IV: IntoView + 'static; } - +/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests +/// to those paths to Leptos's renderer. impl LeptosRoutes for axum::Router { fn leptos_routes( self, From de73622949e4ca5ae244933d78625d40cc8e91b3 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 7 Jan 2023 14:53:38 -0800 Subject: [PATCH 04/14] Change Axum's "" matching --- integrations/axum/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index a92fd9389..c6bb60b14 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -531,7 +531,15 @@ where let routes = routes.0.read().await.to_owned(); // Axum's Router defines Root routes as "/" not "" - routes.iter().map(|s| s.replace("", "/")).collect() + routes + .iter() + .map(|s| { + if s.is_empty() { + return "/".to_string(); + } + s.to_string() + }) + .collect() } /// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid From bdd9abc04d5e4fdc492da87c4c113e9ab2d5165c Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 7 Jan 2023 15:06:21 -0800 Subject: [PATCH 05/14] Removing some missed code and changing the stylesheet --- examples/todo_app_sqlite_axum/src/todo.rs | 2 +- integrations/axum/src/lib.rs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index 389c569f3..78b74c1cc 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -110,7 +110,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView { provide_meta_context(cx); view! { cx, - +

"My Tasks"

diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index c6bb60b14..e57699ec9 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -335,14 +335,6 @@ where let site_root = &options.site_root; let pkg_path = &options.site_pkg_dir; - // We need to do some logic to check if the site_root is pkg - // if it is, then we need to not add pkg_path. This would mean - // the site was built with cargo run and not cargo-leptos - let pkg_path = match site_root.as_ref() { - "pkg" => "pkg".to_string(), - _ => format!("{}/{}", site_root, pkg_path), - }; - let output_name = &options.output_name; // Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options From b34f2070d334d421559e2d02ce90ad80f6d45171 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 7 Jan 2023 15:09:49 -0800 Subject: [PATCH 06/14] Remove extraneous route --- examples/todo_app_sqlite/src/todo.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/todo_app_sqlite/src/todo.rs b/examples/todo_app_sqlite/src/todo.rs index fa2cdcf4d..9dc02fa80 100644 --- a/examples/todo_app_sqlite/src/todo.rs +++ b/examples/todo_app_sqlite/src/todo.rs @@ -106,10 +106,6 @@ pub fn TodoApp(cx: Scope) -> impl IntoView { cx, }/> - - }/> From c41cf879d18fc6b4b24b0a6eb1c4d49702596fc0 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 7 Jan 2023 15:44:35 -0800 Subject: [PATCH 07/14] Formatting --- examples/todo_app_sqlite_axum/src/main.rs | 5 ----- router/src/extract_routes.rs | 4 +++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/todo_app_sqlite_axum/src/main.rs b/examples/todo_app_sqlite_axum/src/main.rs index 00c5e2ada..fae351337 100644 --- a/examples/todo_app_sqlite_axum/src/main.rs +++ b/examples/todo_app_sqlite_axum/src/main.rs @@ -14,11 +14,8 @@ if #[cfg(feature = "ssr")] { use tower_http::services::ServeDir; use tokio::{sync::RwLock, task::spawn_blocking, task::LocalSet}; use leptos_axum::{generate_route_list, LeptosRoutes}; - - use std::sync::Arc; - #[tokio::main] async fn main() { simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging"); @@ -61,7 +58,6 @@ if #[cfg(feature = "ssr")] { let routes = generate_route_list(|cx| view! { cx, }).await; println!("Routes_Outside: {:#?}",&routes); - // build our application with a route let mut app = Router::new() .route("/api/*fn_name", post(leptos_axum::handle_server_fns)) @@ -70,7 +66,6 @@ if #[cfg(feature = "ssr")] { .nest_service("/static", static_service) .leptos_routes(leptos_options, routes, |cx| view! { cx, } ); - // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` log!("listening on {}", &addr); diff --git a/router/src/extract_routes.rs b/router/src/extract_routes.rs index b7850ea54..baa3ba8f8 100644 --- a/router/src/extract_routes.rs +++ b/router/src/extract_routes.rs @@ -7,7 +7,9 @@ use crate::{Branch, RouterIntegrationContext, ServerIntegration}; #[derive(Clone, Default, Debug)] pub struct PossibleBranchContext(pub(crate) Rc>>); -/// Generates a list of all routes this application could possibly serve. +/// Generates a list of all routes this application could possibly serve. This returns the raw routes in the leptos_router +/// format. Odds are you want `generate_route_list()` from either the actix or axum integrations if you want +/// to work with their router #[cfg(feature = "ssr")] pub fn generate_route_list_inner(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec where From 1b8175e2fa2b752f1d8cfd61cefe46cbc1eb92c7 Mon Sep 17 00:00:00 2001 From: benwis Date: Sat, 7 Jan 2023 20:27:05 -0800 Subject: [PATCH 08/14] Add missing tokio dep for RwLock --- integrations/actix/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integrations/actix/Cargo.toml b/integrations/actix/Cargo.toml index d9685af07..44f26e41b 100644 --- a/integrations/actix/Cargo.toml +++ b/integrations/actix/Cargo.toml @@ -13,4 +13,5 @@ futures = "0.3" leptos = { workspace = true, features = ["ssr"] } leptos_meta = { workspace = true, features = ["ssr"] } leptos_router = { workspace = true, features = ["ssr"] } -regex = "1.7.0" \ No newline at end of file +regex = "1.7.0" +tokio = "1.24.1" From dc60c35b586487329e6007153f18c6e2db3d2087 Mon Sep 17 00:00:00 2001 From: benwis Date: Sun, 8 Jan 2023 14:18:51 -0800 Subject: [PATCH 09/14] Rewrite file handlers for Axum, and update all examples to use the new generated routes. Fix a few issues in the integrations, and reduce the number of warnings --- examples/counter_isomorphic/src/main.rs | 17 +++---- examples/hackernews/src/lib.rs | 4 +- examples/hackernews/src/main.rs | 12 +++-- examples/hackernews_axum/src/file.rs | 45 ++++++++++++++++++ examples/hackernews_axum/src/lib.rs | 3 +- examples/hackernews_axum/src/main.rs | 34 +++----------- examples/tailwind/Cargo.toml | 2 +- examples/tailwind/src/app.rs | 40 +++++++++------- examples/tailwind/src/main.rs | 16 ++++--- examples/tailwind/style/output.css | 56 +++++++++++++++++++---- examples/todo_app_sqlite/src/main.rs | 3 +- examples/todo_app_sqlite_axum/src/file.rs | 45 ++++++++++++++++++ examples/todo_app_sqlite_axum/src/lib.rs | 1 + examples/todo_app_sqlite_axum/src/main.rs | 40 +++------------- integrations/actix/src/lib.rs | 10 ++-- integrations/axum/src/lib.rs | 12 +---- leptos_config/src/lib.rs | 4 +- 17 files changed, 212 insertions(+), 132 deletions(-) create mode 100644 examples/hackernews_axum/src/file.rs create mode 100644 examples/todo_app_sqlite_axum/src/file.rs diff --git a/examples/counter_isomorphic/src/main.rs b/examples/counter_isomorphic/src/main.rs index 23e87919a..ea0e7f08a 100644 --- a/examples/counter_isomorphic/src/main.rs +++ b/examples/counter_isomorphic/src/main.rs @@ -9,6 +9,7 @@ cfg_if! { use actix_files::{Files}; use actix_web::*; use crate::counters::*; + use leptos_actix::{generate_route_list, LeptosRoutes}; #[get("/api/events")] async fn counter_events() -> impl Responder { @@ -29,23 +30,23 @@ cfg_if! { #[actix_web::main] async fn main() -> std::io::Result<()> { + crate::counters::register_server_functions(); + let conf = get_configuration(Some("Cargo.toml")).await.unwrap(); - let leptos_options = &conf.leptos_options; - let site_root = &leptos_options.site_root; - let pkg_dir = &leptos_options.site_pkg_dir; - let bundle_path = format!("/{site_root}/{pkg_dir}"); let addr = conf.leptos_options.site_address.clone(); + let routes = generate_route_list(|cx| view! { cx, }); HttpServer::new(move || { let leptos_options = &conf.leptos_options; + let site_root = &leptos_options.site_root; + App::new() - .service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos - .service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run. .service(counter_events) .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) - .route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, })) - //.wrap(middleware::Compress::default()) + .leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, }) + .service(Files::new("/", &site_root)) + //.wrap(middleware::Compress::default()) }) .bind(&addr)? .run() diff --git a/examples/hackernews/src/lib.rs b/examples/hackernews/src/lib.rs index 289403588..57b994bc6 100644 --- a/examples/hackernews/src/lib.rs +++ b/examples/hackernews/src/lib.rs @@ -15,7 +15,7 @@ pub fn App(cx: Scope) -> impl IntoView { view! { cx, <> - +