mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 06:21:57 +00:00
Commit WIP version of common config struct that writes a KDL file for cargo-leptos
This commit is contained in:
parent
7b376b6d3a
commit
f63cb02277
13 changed files with 158 additions and 29 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,3 +6,4 @@ blob.rs
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.leptos.kdl
|
||||||
|
|
|
@ -4,6 +4,7 @@ members = [
|
||||||
"leptos",
|
"leptos",
|
||||||
"leptos_dom",
|
"leptos_dom",
|
||||||
"leptos_core",
|
"leptos_core",
|
||||||
|
"leptos_config",
|
||||||
"leptos_macro",
|
"leptos_macro",
|
||||||
"leptos_reactive",
|
"leptos_reactive",
|
||||||
"leptos_server",
|
"leptos_server",
|
||||||
|
|
|
@ -12,6 +12,7 @@ if #[cfg(feature = "ssr")] {
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
|
use leptos_axum::RenderOptions;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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
|
// build our application with a route
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
// `GET /` goes to `root`
|
// `GET /` goes to `root`
|
||||||
.nest_service("/pkg", pkg_service)
|
.nest_service("/pkg", pkg_service)
|
||||||
.nest_service("/static", static_service)
|
.nest_service("/static", static_service)
|
||||||
.fallback(leptos_axum::render_app_to_stream("/pkg/leptos_hackernews_axum", |cx| view! { cx, <App/> }));
|
.fallback(leptos_axum::render_app_to_stream(render_options, |cx| view! { cx, <App/> }));
|
||||||
|
|
||||||
// run our app with hyper
|
// run our app with hyper
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
// `axum::Server` is a re-export of `hyper::Server`
|
||||||
|
|
|
@ -14,7 +14,7 @@ if #[cfg(feature = "ssr")] {
|
||||||
use todo_app_sqlite_axum::*;
|
use todo_app_sqlite_axum::*;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use leptos_axum::RenderOptions;
|
use std::env;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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
|
// build our application with a route
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||||
|
|
|
@ -12,9 +12,9 @@ cfg_if! {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register_server_functions() {
|
pub fn register_server_functions() {
|
||||||
GetTodos::register();
|
_ = GetTodos::register();
|
||||||
AddTodo::register();
|
_ = AddTodo::register();
|
||||||
DeleteTodo::register();
|
_ = DeleteTodo::register();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||||
|
@ -34,7 +34,7 @@ cfg_if! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(GetTodos, "/api")]
|
#[server(GetTodos, "/api")]
|
||||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
pub async fn get_todos(_cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||||
// this is just an example of how to access server context injected in the handlers
|
// 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
|
// http::Request doesn't implement Clone, so more work will be needed to do use_context() on this
|
||||||
// let req = use_context::<http::Request<axum::body::BoxBody>>(cx)
|
// let req = use_context::<http::Request<axum::body::BoxBody>>(cx)
|
||||||
|
@ -70,7 +70,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(row) => Ok(()),
|
Ok(_row) => Ok(()),
|
||||||
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
|
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,7 @@ pub fn Todos(cx: Scope) -> Element {
|
||||||
<li>
|
<li>
|
||||||
{todo.title}
|
{todo.title}
|
||||||
<ActionForm action=delete_todo.clone()>
|
<ActionForm action=delete_todo.clone()>
|
||||||
<input type="hidden" name="id" value={todo.id}/>
|
<input type="hidden" name="id" value=todo.id/>
|
||||||
<input type="submit" value="X"/>
|
<input type="submit" value="X"/>
|
||||||
</ActionForm>
|
</ActionForm>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -9,6 +9,7 @@ cfg_if! {
|
||||||
use actix_files::{Files};
|
use actix_files::{Files};
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use crate::todo::*;
|
use crate::todo::*;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
#[get("/style.css")]
|
#[get("/style.css")]
|
||||||
async fn css() -> impl Responder {
|
async fn css() -> impl Responder {
|
||||||
|
@ -26,11 +27,14 @@ cfg_if! {
|
||||||
crate::todo::register_server_functions();
|
crate::todo::register_server_functions();
|
||||||
|
|
||||||
HttpServer::new(|| {
|
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()
|
App::new()
|
||||||
.service(Files::new("/pkg", "./pkg"))
|
.service(Files::new("/pkg", "./pkg"))
|
||||||
.service(css)
|
.service(css)
|
||||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream("/pkg/todo_app_sqlite", |cx| view! { cx, <TodoApp/> }))
|
.route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, <TodoApp/> }))
|
||||||
//.wrap(middleware::Compress::default())
|
//.wrap(middleware::Compress::default())
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8083))?
|
.bind(("127.0.0.1", 8083))?
|
||||||
|
|
|
@ -138,10 +138,11 @@ pub fn handle_server_fns() -> Route {
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn render_app_to_stream(
|
pub fn render_app_to_stream(
|
||||||
client_pkg_path: &'static str,
|
options: RenderOptions,
|
||||||
app_fn: impl Fn(leptos::Scope) -> Element + Clone + 'static,
|
app_fn: impl Fn(leptos::Scope) -> Element + Clone + 'static,
|
||||||
) -> Route {
|
) -> Route {
|
||||||
web::get().to(move |req: HttpRequest| {
|
web::get().to(move |req: HttpRequest| {
|
||||||
|
let options = options.clone();
|
||||||
let app_fn = app_fn.clone();
|
let app_fn = app_fn.clone();
|
||||||
async move {
|
async move {
|
||||||
let path = req.path();
|
let path = req.path();
|
||||||
|
@ -165,12 +166,39 @@ pub fn render_app_to_stream(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let head = format!(r#"<!DOCTYPE html>
|
let pkg_path = &options.pkg_path;
|
||||||
<html>
|
|
||||||
|
let leptos_autoreload = match options.reload_port {
|
||||||
|
Some(port) => match &options.environment {
|
||||||
|
RustEnv::DEV => format!(
|
||||||
|
r#"
|
||||||
|
<script crossorigin="">(function () {{
|
||||||
|
var ws = new WebSocket('ws://127.0.0.1:{port}/autoreload');
|
||||||
|
ws.onmessage = (ev) => {{
|
||||||
|
console.log(`Reload message: `);
|
||||||
|
if (ev.data === 'reload') window.location.reload();
|
||||||
|
}};
|
||||||
|
ws.onclose = () => console.warn('Autoreload stopped. Manual reload necessary.');
|
||||||
|
}})()
|
||||||
|
</script>
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RustEnv::PROD => "".to_string(),
|
||||||
|
},
|
||||||
|
None => "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let head = format!(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<script type="module">import init, {{ hydrate }} from '{client_pkg_path}.js'; init().then(hydrate);</script>"#);
|
<script type="module">import init, {{ hydrate }} from '{pkg_path}.js'; init().then(hydrate);</script>
|
||||||
|
{leptos_autoreload}
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
let tail = "</body></html>";
|
let tail = "</body></html>";
|
||||||
|
|
||||||
HttpResponse::Ok().content_type("text/html").streaming(
|
HttpResponse::Ok().content_type("text/html").streaming(
|
||||||
|
|
|
@ -11,6 +11,7 @@ description = "Axum integrations for the Leptos web framework."
|
||||||
axum = "0.6"
|
axum = "0.6"
|
||||||
derive_builder = "0.12.0"
|
derive_builder = "0.12.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
kdl = "4.6.0"
|
||||||
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
||||||
"ssr",
|
"ssr",
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -4,7 +4,6 @@ use axum::{
|
||||||
http::{HeaderMap, HeaderValue, Request, StatusCode},
|
http::{HeaderMap, HeaderValue, Request, StatusCode},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use derive_builder::Builder;
|
|
||||||
use futures::{Future, SinkExt, Stream, StreamExt};
|
use futures::{Future, SinkExt, Stream, StreamExt};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_meta::MetaContext;
|
use leptos_meta::MetaContext;
|
||||||
|
@ -128,14 +127,6 @@ pub async fn handle_server_fns(
|
||||||
|
|
||||||
pub type PinnedHtmlStream = Pin<Box<dyn Stream<Item = io::Result<Bytes>> + Send>>;
|
pub type PinnedHtmlStream = Pin<Box<dyn Stream<Item = io::Result<Bytes>> + Send>>;
|
||||||
|
|
||||||
#[derive(Default, Builder, Clone)]
|
|
||||||
pub struct RenderOptions {
|
|
||||||
#[builder(setter(into))]
|
|
||||||
client_pkg_path: String,
|
|
||||||
#[builder(setter(strip_option), default)]
|
|
||||||
auto_reload: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
/// 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.
|
/// 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()
|
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 {
|
let leptos_autoreload = match options.reload_port {
|
||||||
Some(port) => format!(
|
Some(port) => match &options.environment {
|
||||||
r#"
|
RustEnv::DEV => format!(
|
||||||
|
r#"
|
||||||
<script crossorigin="">(function () {{
|
<script crossorigin="">(function () {{
|
||||||
var ws = new WebSocket('ws://127.0.0.1:{port}/autoreload');
|
var ws = new WebSocket('ws://127.0.0.1:{port}/autoreload');
|
||||||
ws.onmessage = (ev) => {{
|
ws.onmessage = (ev) => {{
|
||||||
|
@ -216,7 +208,9 @@ pub fn render_app_to_stream(
|
||||||
}})()
|
}})()
|
||||||
</script>
|
</script>
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
|
RustEnv::PROD => "".to_string(),
|
||||||
|
},
|
||||||
None => "".to_string(),
|
None => "".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -226,7 +220,7 @@ pub fn render_app_to_stream(
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<script type="module">import init, {{ hydrate }} from '{client_pkg_path}.js'; init().then(hydrate);</script>
|
<script type="module">import init, {{ hydrate }} from '{pkg_path}.js'; init().then(hydrate);</script>
|
||||||
{leptos_autoreload}
|
{leptos_autoreload}
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,6 +10,7 @@ readme = "../README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
leptos_core = { path = "../leptos_core", default-features = false, version = "0.0.19" }
|
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_dom = { path = "../leptos_dom", default-features = false, version = "0.0.19" }
|
||||||
leptos_macro = { path = "../leptos_macro", 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" }
|
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.0.19" }
|
||||||
|
|
|
@ -141,6 +141,7 @@
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
pub use leptos_config::*;
|
||||||
pub use leptos_core::*;
|
pub use leptos_core::*;
|
||||||
pub use leptos_dom;
|
pub use leptos_dom;
|
||||||
pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt};
|
pub use leptos_dom::wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||||
|
|
11
leptos_config/Cargo.toml
Normal file
11
leptos_config/Cargo.toml
Normal file
|
@ -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"
|
83
leptos_config/src/lib.rs
Normal file
83
leptos_config/src/lib.rs
Normal file
|
@ -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<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Self, Self::Err> {
|
||||||
|
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<String, VarError>> for RustEnv {
|
||||||
|
fn from(input: &Result<String, VarError>) -> 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue