diff --git a/.gitignore b/.gitignore index fbe21a725..2c66f7bb5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ blob.rs Cargo.lock **/*.rs.bk .DS_Store +.leptos.kdl diff --git a/Cargo.toml b/Cargo.toml index 1967f0de9..96547bba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "leptos", "leptos_dom", "leptos_core", + "leptos_config", "leptos_macro", "leptos_reactive", "leptos_server", diff --git a/examples/hackernews-axum/src/main.rs b/examples/hackernews-axum/src/main.rs index 1bd111d56..28b10a78c 100644 --- a/examples/hackernews-axum/src/main.rs +++ b/examples/hackernews-axum/src/main.rs @@ -12,6 +12,7 @@ if #[cfg(feature = "ssr")] { use http::StatusCode; use std::net::SocketAddr; use tower_http::services::ServeDir; + use leptos_axum::RenderOptions; #[tokio::main] async fn main() { @@ -36,12 +37,14 @@ if #[cfg(feature = "ssr")] { ) } + let render_options: RenderOptions = leptos_axum::RenderOptionsBuilder::default().client_pkg_path("/pkg/leptos_hackernews_axum").auto_reload(3001).build().expect("Failed to Parse RenderOptions"); + // build our application with a route let app = Router::new() // `GET /` goes to `root` .nest_service("/pkg", pkg_service) .nest_service("/static", static_service) - .fallback(leptos_axum::render_app_to_stream("/pkg/leptos_hackernews_axum", |cx| view! { cx, })); + .fallback(leptos_axum::render_app_to_stream(render_options, |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/main.rs b/examples/todo-app-sqlite-axum/src/main.rs index b717b3d0b..1a5247214 100644 --- a/examples/todo-app-sqlite-axum/src/main.rs +++ b/examples/todo-app-sqlite-axum/src/main.rs @@ -14,7 +14,7 @@ if #[cfg(feature = "ssr")] { use todo_app_sqlite_axum::*; use http::StatusCode; use tower_http::services::ServeDir; - use leptos_axum::RenderOptions; + use std::env; #[tokio::main] async fn main() { @@ -45,8 +45,9 @@ if #[cfg(feature = "ssr")] { ) } - let render_options: RenderOptions = leptos_axum::RenderOptionsBuilder::default().client_pkg_path("/pkg/todo_app_sqlite_axum").auto_reload(3001).build().expect("Failed to Parse RenderOptions"); + let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/todo_app_sqlite_axum").reload_port(3001).environment(&env::var("RUST_ENV")).build(); + render_options.write_to_file(); // build our application with a route let app = Router::new() .route("/api/*fn_name", post(leptos_axum::handle_server_fns)) diff --git a/examples/todo-app-sqlite-axum/src/todo.rs b/examples/todo-app-sqlite-axum/src/todo.rs index c60af7fe9..fe8fcf7db 100644 --- a/examples/todo-app-sqlite-axum/src/todo.rs +++ b/examples/todo-app-sqlite-axum/src/todo.rs @@ -12,9 +12,9 @@ cfg_if! { } pub fn register_server_functions() { - GetTodos::register(); - AddTodo::register(); - DeleteTodo::register(); + _ = GetTodos::register(); + _ = AddTodo::register(); + _ = DeleteTodo::register(); } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)] @@ -34,7 +34,7 @@ cfg_if! { } #[server(GetTodos, "/api")] -pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { +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 = use_context::>(cx) @@ -70,7 +70,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> { .execute(&mut conn) .await { - Ok(row) => Ok(()), + Ok(_row) => Ok(()), Err(e) => Err(ServerFnError::ServerError(e.to_string())), } } @@ -167,7 +167,7 @@ pub fn Todos(cx: Scope) -> Element {
  • {todo.title} - +
  • diff --git a/examples/todo-app-sqlite/src/main.rs b/examples/todo-app-sqlite/src/main.rs index a352dad96..2e2621353 100644 --- a/examples/todo-app-sqlite/src/main.rs +++ b/examples/todo-app-sqlite/src/main.rs @@ -9,6 +9,7 @@ cfg_if! { use actix_files::{Files}; use actix_web::*; use crate::todo::*; + use std::env; #[get("/style.css")] async fn css() -> impl Responder { @@ -26,11 +27,14 @@ cfg_if! { crate::todo::register_server_functions(); HttpServer::new(|| { + let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/todo_app_sqlite").reload_port(3001).environment(&env::var("RUST_ENV")).build(); + render_options.write_to_file(); + App::new() .service(Files::new("/pkg", "./pkg")) .service(css) .route("/api/{tail:.*}", leptos_actix::handle_server_fns()) - .route("/{tail:.*}", leptos_actix::render_app_to_stream("/pkg/todo_app_sqlite", |cx| view! { cx, })) + .route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, })) //.wrap(middleware::Compress::default()) }) .bind(("127.0.0.1", 8083))? diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index c410a46ec..25c179b5d 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -138,10 +138,11 @@ pub fn handle_server_fns() -> Route { /// # } /// ``` pub fn render_app_to_stream( - client_pkg_path: &'static str, + options: RenderOptions, app_fn: impl Fn(leptos::Scope) -> Element + Clone + 'static, ) -> Route { web::get().to(move |req: HttpRequest| { + let options = options.clone(); let app_fn = app_fn.clone(); async move { let path = req.path(); @@ -165,12 +166,39 @@ pub fn render_app_to_stream( } }; - let head = format!(r#" - + let pkg_path = &options.pkg_path; + + let leptos_autoreload = match options.reload_port { + Some(port) => match &options.environment { + RustEnv::DEV => format!( + r#" + + "# + ), + RustEnv::PROD => "".to_string(), + }, + None => "".to_string(), + }; + + let head = format!( + r#" + - "#); + + {leptos_autoreload} + "# + ); + let tail = ""; HttpResponse::Ok().content_type("text/html").streaming( diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index faec4e628..3827eb4d9 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -11,6 +11,7 @@ description = "Axum integrations for the Leptos web framework." axum = "0.6" derive_builder = "0.12.0" futures = "0.3" +kdl = "4.6.0" leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [ "ssr", ] } diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index cb64f6757..d2453efa4 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -4,7 +4,6 @@ use axum::{ http::{HeaderMap, HeaderValue, Request, StatusCode}, response::{IntoResponse, Response}, }; -use derive_builder::Builder; use futures::{Future, SinkExt, Stream, StreamExt}; use leptos::*; use leptos_meta::MetaContext; @@ -128,14 +127,6 @@ pub async fn handle_server_fns( pub type PinnedHtmlStream = Pin> + Send>>; -#[derive(Default, Builder, Clone)] -pub struct RenderOptions { - #[builder(setter(into))] - client_pkg_path: String, - #[builder(setter(strip_option), default)] - auto_reload: Option, -} - /// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries /// to route it using [leptos_router], serving an HTML stream of your application. /// @@ -201,11 +192,12 @@ pub fn render_app_to_stream( full_path = "http://leptos".to_string() + &path.to_string() } - let client_pkg_path = &options.client_pkg_path; + let pkg_path = &options.pkg_path; - let leptos_autoreload = match options.auto_reload { - Some(port) => format!( - r#" + let leptos_autoreload = match options.reload_port { + Some(port) => match &options.environment { + RustEnv::DEV => format!( + r#" "# - ), + ), + RustEnv::PROD => "".to_string(), + }, None => "".to_string(), }; @@ -226,7 +220,7 @@ pub fn render_app_to_stream( - + {leptos_autoreload} "# ); diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index 8c4f1c7d6..feb50c90c 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -10,6 +10,7 @@ readme = "../README.md" [dependencies] leptos_core = { path = "../leptos_core", default-features = false, version = "0.0.19" } +leptos_config = { path = "../leptos_config", default-features = false, version = "0.0.1" } leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.0.19" } leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0.19" } leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.19" } diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 015886160..a24b92cc0 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -141,6 +141,7 @@ //! # } //! ``` +pub use leptos_config::*; pub use leptos_core::*; pub use leptos_dom; pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt}; diff --git a/leptos_config/Cargo.toml b/leptos_config/Cargo.toml new file mode 100644 index 000000000..33f81c1e5 --- /dev/null +++ b/leptos_config/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "leptos_config" +version = "0.0.1" +edition = "2021" +authors = ["Greg Johnston"] +license = "MIT" +repository = "https://github.com/gbj/leptos" +description = "Configuraiton for Leptos" + +[dependencies] +typed-builder = "0.11.0" diff --git a/leptos_config/src/lib.rs b/leptos_config/src/lib.rs new file mode 100644 index 000000000..8b62f8969 --- /dev/null +++ b/leptos_config/src/lib.rs @@ -0,0 +1,83 @@ +use std::{env::VarError, str::FromStr}; +use typed_builder::TypedBuilder; + +#[derive(Default, TypedBuilder, Clone)] +pub struct RenderOptions { + #[builder(setter(into))] + pub pkg_path: String, + #[builder(setter(into))] + pub environment: RustEnv, + #[builder(setter(strip_option), default)] + pub reload_port: Option, +} + +impl RenderOptions { + /// Creates a hidden file at ./.leptos_toml so cargo-leptos can monitor settings + pub fn write_to_file(&self) { + use std::fs; + let options = format!( + r#"render_options: {{ + pkg_path {} + environment {:?} + reload_port {:?} +}} +"#, + self.pkg_path, self.environment, self.reload_port + ); + fs::write("./.leptos.kdl", options).expect("Unable to write file"); + } +} +#[derive(Default, Debug, Clone)] +pub enum RustEnv { + #[default] + PROD, + DEV, +} + +impl FromStr for RustEnv { + type Err = (); + fn from_str(input: &str) -> Result { + let sanitized = input.to_lowercase(); + match sanitized.as_ref() { + "dev" => Ok(Self::DEV), + "development" => Ok(Self::DEV), + "prod" => Ok(Self::PROD), + "production" => Ok(Self::PROD), + _ => Ok(Self::PROD), + } + } +} + +impl From<&str> for RustEnv { + fn from(str: &str) -> Self { + let sanitized = str.to_lowercase(); + match sanitized.as_str() { + "dev" => Self::DEV, + "development" => Self::DEV, + "prod" => Self::PROD, + "production" => Self::PROD, + _ => { + panic!("Environment var is not recognized. Maybe try `dev` or `prod`") + } + } + } +} +impl From<&Result> for RustEnv { + fn from(input: &Result) -> Self { + match input { + Ok(str) => { + let sanitized = str.to_lowercase(); + match sanitized.as_ref() { + "dev" => Self::DEV, + "development" => Self::DEV, + "prod" => Self::PROD, + "production" => Self::PROD, + _ => { + panic!("Environment var is not recognized. Maybe try `dev` or `prod`") + } + } + } + Err(_) => Self::PROD, + } + } +}