`render_app_to_stream` helper in `leptos_actix`
This commit is contained in:
parent
eff42a196f
commit
4e8c1758c3
|
@ -1,6 +1,5 @@
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
|
||||||
mod counters;
|
mod counters;
|
||||||
|
|
||||||
// boilerplate to run in different modes
|
// boilerplate to run in different modes
|
||||||
|
@ -11,36 +10,6 @@ cfg_if! {
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use crate::counters::*;
|
use crate::counters::*;
|
||||||
|
|
||||||
#[get("{tail:.*}")]
|
|
||||||
async fn render(req: HttpRequest) -> impl Responder {
|
|
||||||
let path = req.path();
|
|
||||||
let path = "http://leptos".to_string() + path;
|
|
||||||
println!("path = {path}");
|
|
||||||
|
|
||||||
HttpResponse::Ok().content_type("text/html").body(format!(
|
|
||||||
r#"<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<title>Isomorphic Counter</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{}
|
|
||||||
</body>
|
|
||||||
<script type="module">import init, {{ hydrate }} from './pkg/leptos_counter_isomorphic.js'; init().then(hydrate);</script>
|
|
||||||
</html>"#,
|
|
||||||
run_scope({
|
|
||||||
move |cx| {
|
|
||||||
let integration = ServerIntegration { path: path.clone() };
|
|
||||||
provide_context(cx, RouterIntegrationContext::new(integration));
|
|
||||||
|
|
||||||
view! { cx, <Counters/>}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/api/events")]
|
#[get("/api/events")]
|
||||||
async fn counter_events() -> impl Responder {
|
async fn counter_events() -> impl Responder {
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
@ -67,7 +36,7 @@ cfg_if! {
|
||||||
.service(Files::new("/pkg", "./pkg"))
|
.service(Files::new("/pkg", "./pkg"))
|
||||||
.service(counter_events)
|
.service(counter_events)
|
||||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||||
.service(render)
|
.route("/{tail:.*}", leptos_actix::render_app_to_stream("leptos_counter_isomorphic", |cx| view! { cx, <Counters/> }))
|
||||||
//.wrap(middleware::Compress::default())
|
//.wrap(middleware::Compress::default())
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8081))?
|
.bind(("127.0.0.1", 8081))?
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use futures::StreamExt;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_meta::*;
|
|
||||||
use leptos_router::*;
|
|
||||||
mod todo;
|
mod todo;
|
||||||
|
|
||||||
// boilerplate to run in different modes
|
// boilerplate to run in different modes
|
||||||
|
@ -13,45 +10,9 @@ cfg_if! {
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use crate::todo::*;
|
use crate::todo::*;
|
||||||
|
|
||||||
#[get("{tail:.*}")]
|
#[get("/style.css")]
|
||||||
async fn render(req: HttpRequest) -> impl Responder {
|
async fn css() -> impl Responder {
|
||||||
let path = req.path();
|
actix_files::NamedFile::open_async("./style.css").await
|
||||||
|
|
||||||
let query = req.query_string();
|
|
||||||
let path = if query.is_empty() {
|
|
||||||
"http://leptos".to_string() + path
|
|
||||||
} else {
|
|
||||||
"http://leptos".to_string() + path + "?" + query
|
|
||||||
};
|
|
||||||
|
|
||||||
let app = move |cx| {
|
|
||||||
let integration = ServerIntegration { path: path.clone() };
|
|
||||||
provide_context(cx, RouterIntegrationContext::new(integration));
|
|
||||||
provide_context(cx, req.clone());
|
|
||||||
|
|
||||||
view! { cx, <TodoApp/> }
|
|
||||||
};
|
|
||||||
|
|
||||||
let head = r#"<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<script type="module">import init, { hydrate } from '/pkg/todo_app_sqlite.js'; init().then(hydrate);</script>"#;
|
|
||||||
let tail = "</body></html>";
|
|
||||||
|
|
||||||
HttpResponse::Ok().content_type("text/html").streaming(
|
|
||||||
futures::stream::once(async { head.to_string() })
|
|
||||||
.chain(render_to_stream(move |cx| {
|
|
||||||
let app = app(cx);
|
|
||||||
let head = use_context::<MetaContext>(cx)
|
|
||||||
.map(|meta| meta.dehydrate())
|
|
||||||
.unwrap_or_default();
|
|
||||||
format!("{head}</head><body>{app}")
|
|
||||||
}))
|
|
||||||
.chain(futures::stream::once(async { tail.to_string() }))
|
|
||||||
.map(|html| Ok(web::Bytes::from(html)) as Result<web::Bytes>),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
|
@ -67,8 +28,9 @@ cfg_if! {
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
.service(Files::new("/pkg", "./pkg"))
|
.service(Files::new("/pkg", "./pkg"))
|
||||||
|
.service(css)
|
||||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||||
.service(render)
|
.route("/{tail:.*}", leptos_actix::render_app_to_stream("todo_app_sqlite", |cx| view! { cx, <TodoApp/> }))
|
||||||
//.wrap(middleware::Compress::default())
|
//.wrap(middleware::Compress::default())
|
||||||
})
|
})
|
||||||
.bind(("127.0.0.1", 8081))?
|
.bind(("127.0.0.1", 8081))?
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
use leptos_meta::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@ pub fn TodoApp(cx: Scope) -> Element {
|
||||||
view! {
|
view! {
|
||||||
cx,
|
cx,
|
||||||
<div>
|
<div>
|
||||||
|
<Stylesheet href="/style.css".into()/>
|
||||||
<Router>
|
<Router>
|
||||||
<header>
|
<header>
|
||||||
<h1>"My Tasks"</h1>
|
<h1>"My Tasks"</h1>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.pending {
|
||||||
|
color: purple;
|
||||||
|
}
|
|
@ -5,6 +5,13 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
|
futures = "0.3"
|
||||||
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
||||||
"ssr",
|
"ssr",
|
||||||
] }
|
] }
|
||||||
|
leptos_meta = { path = "../../meta", default-features = false, version = "0.0", features = [
|
||||||
|
"ssr",
|
||||||
|
] }
|
||||||
|
leptos_router = { path = "../../router", default-features = false, version = "0.0", features = [
|
||||||
|
"ssr",
|
||||||
|
] }
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
use futures::StreamExt;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
use leptos_meta::*;
|
||||||
|
use leptos_router::*;
|
||||||
|
|
||||||
/// An Actix [Route](actix_web::Route) that listens for a `POST` request with
|
/// An Actix [Route](actix_web::Route) that listens for a `POST` request with
|
||||||
/// Leptos server function arguments in the body, runs the server function if found,
|
/// Leptos server function arguments in the body, runs the server function if found,
|
||||||
|
@ -38,6 +41,92 @@ pub fn handle_server_fns() -> Route {
|
||||||
web::post().to(handle_server_fn)
|
web::post().to(handle_server_fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||||
|
/// to route it using [leptos_router], serving an HTML stream of your application.
|
||||||
|
///
|
||||||
|
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||||
|
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||||
|
///
|
||||||
|
/// The HTML stream is rendered using [render_to_stream], and also everything described in
|
||||||
|
/// the documentation for that function.
|
||||||
|
///
|
||||||
|
/// This can then be set up at an appropriate route in your application:
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{HttpServer, App};
|
||||||
|
/// use leptos::*;
|
||||||
|
///
|
||||||
|
/// #[component]
|
||||||
|
/// fn MyApp(cx: Scope) -> Element {
|
||||||
|
/// view! { cx, <main>"Hello, world!"</main> }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # if false { // don't actually try to run a server in a doctest...
|
||||||
|
/// #[actix_web::main]
|
||||||
|
/// async fn main() -> std::io::Result<()> {
|
||||||
|
/// HttpServer::new(|| {
|
||||||
|
/// App::new()
|
||||||
|
/// // {tail:.*} passes the remainder of the URL as the route
|
||||||
|
/// // the actual routing will be handled by `leptos_router`
|
||||||
|
/// .route("/{tail:.*}", leptos_actix::render_app_to_stream("leptos_example", |cx| view! { cx, <MyApp/> }))
|
||||||
|
/// })
|
||||||
|
/// .bind(("127.0.0.1", 8080))?
|
||||||
|
/// .run()
|
||||||
|
/// .await
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn render_app_to_stream(
|
||||||
|
client_pkg_name: &'static str,
|
||||||
|
app_fn: impl Fn(leptos::Scope) -> Element + Clone + 'static,
|
||||||
|
) -> Route {
|
||||||
|
web::get().to(move |req: HttpRequest| {
|
||||||
|
let app_fn = app_fn.clone();
|
||||||
|
async move {
|
||||||
|
let path = req.path();
|
||||||
|
|
||||||
|
let query = req.query_string();
|
||||||
|
let path = if query.is_empty() {
|
||||||
|
"http://leptos".to_string() + path
|
||||||
|
} else {
|
||||||
|
"http://leptos".to_string() + path + "?" + query
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = {
|
||||||
|
let app_fn = app_fn.clone();
|
||||||
|
move |cx| {
|
||||||
|
let integration = ServerIntegration { path: path.clone() };
|
||||||
|
provide_context(cx, RouterIntegrationContext::new(integration));
|
||||||
|
provide_context(cx, MetaContext::new());
|
||||||
|
provide_context(cx, req.clone());
|
||||||
|
|
||||||
|
(app_fn)(cx)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let head = format!(r#"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<script type="module">import init, {{ hydrate }} from '/pkg/{client_pkg_name}.js'; init().then(hydrate);</script>"#);
|
||||||
|
let tail = "</body></html>";
|
||||||
|
|
||||||
|
HttpResponse::Ok().content_type("text/html").streaming(
|
||||||
|
futures::stream::once(async move { head.clone() })
|
||||||
|
.chain(render_to_stream(move |cx| {
|
||||||
|
let app = app(cx);
|
||||||
|
let head = use_context::<MetaContext>(cx)
|
||||||
|
.map(|meta| meta.dehydrate())
|
||||||
|
.unwrap_or_default();
|
||||||
|
format!("{head}</head><body>{app}")
|
||||||
|
}))
|
||||||
|
.chain(futures::stream::once(async { tail.to_string() }))
|
||||||
|
.map(|html| Ok(web::Bytes::from(html)) as Result<web::Bytes>),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_server_fn(
|
async fn handle_server_fn(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
params: web::Path<String>,
|
params: web::Path<String>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use leptos::*;
|
use leptos::{leptos_dom::debug_warn, *};
|
||||||
|
|
||||||
mod stylesheet;
|
mod stylesheet;
|
||||||
mod title;
|
mod title;
|
||||||
|
@ -16,8 +16,10 @@ pub struct MetaContext {
|
||||||
pub fn use_head(cx: Scope) -> MetaContext {
|
pub fn use_head(cx: Scope) -> MetaContext {
|
||||||
match use_context::<MetaContext>(cx) {
|
match use_context::<MetaContext>(cx) {
|
||||||
None => {
|
None => {
|
||||||
log::warn!("use_head() can only be called if a MetaContext has been provided");
|
debug_warn!("use_head() is being called with a MetaContext being provided. We'll automatically create and provide one, but if this is being called in a child route it will cause bugs. To be safe, you should provide_context(cx, MetaContext::new()) somewhere in the root of the app.");
|
||||||
panic!()
|
let meta = MetaContext::new();
|
||||||
|
provide_context(cx, meta.clone());
|
||||||
|
meta
|
||||||
}
|
}
|
||||||
Some(ctx) => ctx,
|
Some(ctx) => ctx,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue