mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
render_app_to_stream
helper in leptos_actix
This commit is contained in:
parent
eff42a196f
commit
4e8c1758c3
7 changed files with 112 additions and 78 deletions
|
@ -1,6 +1,5 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
mod counters;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
|
@ -11,36 +10,6 @@ cfg_if! {
|
|||
use actix_web::*;
|
||||
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")]
|
||||
async fn counter_events() -> impl Responder {
|
||||
use futures::StreamExt;
|
||||
|
@ -67,7 +36,7 @@ cfg_if! {
|
|||
.service(Files::new("/pkg", "./pkg"))
|
||||
.service(counter_events)
|
||||
.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())
|
||||
})
|
||||
.bind(("127.0.0.1", 8081))?
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use cfg_if::cfg_if;
|
||||
use futures::StreamExt;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod todo;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
|
@ -13,45 +10,9 @@ cfg_if! {
|
|||
use actix_web::*;
|
||||
use crate::todo::*;
|
||||
|
||||
#[get("{tail:.*}")]
|
||||
async fn render(req: HttpRequest) -> impl Responder {
|
||||
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 = 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>),
|
||||
)
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
actix_files::NamedFile::open_async("./style.css").await
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
|
@ -67,8 +28,9 @@ cfg_if! {
|
|||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg"))
|
||||
.service(css)
|
||||
.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())
|
||||
})
|
||||
.bind(("127.0.0.1", 8081))?
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -91,6 +92,7 @@ pub fn TodoApp(cx: Scope) -> Element {
|
|||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<Stylesheet href="/style.css".into()/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
|
3
examples/todo-app-sqlite/style.css
Normal file
3
examples/todo-app-sqlite/style.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.pending {
|
||||
color: purple;
|
||||
}
|
|
@ -5,6 +5,13 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
futures = "0.3"
|
||||
leptos = { path = "../../leptos", default-features = false, version = "0.0", features = [
|
||||
"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 futures::StreamExt;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
/// 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,
|
||||
|
@ -38,6 +41,92 @@ pub fn handle_server_fns() -> Route {
|
|||
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(
|
||||
req: HttpRequest,
|
||||
params: web::Path<String>,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use leptos::*;
|
||||
use leptos::{leptos_dom::debug_warn, *};
|
||||
|
||||
mod stylesheet;
|
||||
mod title;
|
||||
|
@ -16,8 +16,10 @@ pub struct MetaContext {
|
|||
pub fn use_head(cx: Scope) -> MetaContext {
|
||||
match use_context::<MetaContext>(cx) {
|
||||
None => {
|
||||
log::warn!("use_head() can only be called if a MetaContext has been provided");
|
||||
panic!()
|
||||
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.");
|
||||
let meta = MetaContext::new();
|
||||
provide_context(cx, meta.clone());
|
||||
meta
|
||||
}
|
||||
Some(ctx) => ctx,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue