mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
stash
This commit is contained in:
parent
782cb93743
commit
789eef914d
15 changed files with 353 additions and 442 deletions
|
@ -13,7 +13,7 @@ lazy_static = "1"
|
|||
leptos = { path = "../../leptos"}
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
routing = { path = "../../routing" }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
|
@ -24,16 +24,14 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"], optio
|
|||
wasm-bindgen = "0.2"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
use lazy_static::lazy_static;
|
||||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
use leptos::{
|
||||
component, server, server::Resource, server_fn::ServerFnError, suspend,
|
||||
view, ErrorBoundary, IntoView, Params, Suspense,
|
||||
};
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use routing::{
|
||||
components::{Route, Router, Routes},
|
||||
hooks::use_params,
|
||||
params::Params,
|
||||
ParamSegment, SsrMode, StaticSegment,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -15,21 +24,22 @@ pub fn App() -> impl IntoView {
|
|||
<Stylesheet id="leptos" href="/pkg/ssr_modes.css"/>
|
||||
<Title text="Welcome to Leptos"/>
|
||||
|
||||
<Router fallback>
|
||||
<Router>
|
||||
<main>
|
||||
<Routes>
|
||||
// TODO should fallback be on Routes or Router?
|
||||
<Routes fallback>
|
||||
// We’ll load the home page with out-of-order streaming and <Suspense/>
|
||||
<Route path="" view=HomePage/>
|
||||
<Route path=StaticSegment("") view=HomePage/>
|
||||
|
||||
// We'll load the posts with async rendering, so they can set
|
||||
// the title and metadata *after* loading the data
|
||||
<Route
|
||||
path="/post/:id"
|
||||
path=(StaticSegment("post"), ParamSegment("id"))
|
||||
view=Post
|
||||
ssr=SsrMode::Async
|
||||
/>
|
||||
<Route
|
||||
path="/post_in_order/:id"
|
||||
path=(StaticSegment("post_in_order"), ParamSegment("id"))
|
||||
view=Post
|
||||
ssr=SsrMode::InOrder
|
||||
/>
|
||||
|
@ -42,20 +52,20 @@ pub fn App() -> impl IntoView {
|
|||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
// load the posts
|
||||
let posts =
|
||||
create_resource(|| (), |_| async { list_post_metadata().await });
|
||||
let posts_view = move || {
|
||||
posts.and_then(|posts| {
|
||||
posts.iter()
|
||||
let posts = Resource::new_serde(|| (), |_| list_post_metadata());
|
||||
let posts_view = suspend!(
|
||||
posts.await.map(|posts| {
|
||||
posts.into_iter()
|
||||
.map(|post| view! {
|
||||
<li>
|
||||
<a href=format!("/post/{}", post.id)>{&post.title}</a> "|"
|
||||
<a href=format!("/post_in_order/{}", post.id)>{&post.title}"(in order)"</a>
|
||||
<a href=format!("/post/{}", post.id)>{post.title.clone()}</a>
|
||||
"|"
|
||||
<a href=format!("/post_in_order/{}", post.id)>{post.title} "(in order)"</a>
|
||||
</li>
|
||||
})
|
||||
.collect_view()
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
};
|
||||
);
|
||||
|
||||
view! {
|
||||
<h1>"My Great Blog"</h1>
|
||||
|
@ -80,7 +90,7 @@ fn Post() -> impl IntoView {
|
|||
.map_err(|_| PostError::InvalidId)
|
||||
})
|
||||
};
|
||||
let post_resource = create_resource(id, |id| async move {
|
||||
let post_resource = Resource::new_serde(id, |id| async move {
|
||||
match id {
|
||||
Err(e) => Err(e),
|
||||
Ok(id) => get_post(id)
|
||||
|
@ -90,45 +100,42 @@ fn Post() -> impl IntoView {
|
|||
}
|
||||
});
|
||||
|
||||
let post = move || match post_resource.get() {
|
||||
Some(Ok(Ok(v))) => Ok(v),
|
||||
_ => Err(PostError::ServerError),
|
||||
};
|
||||
|
||||
let post_view = move || {
|
||||
post().map(|post| {
|
||||
view! {
|
||||
// render content
|
||||
<h1>{&post.title}</h1>
|
||||
<p>{&post.content}</p>
|
||||
let post_view = suspend!({
|
||||
match post_resource.await {
|
||||
Ok(Ok(post)) => Ok(view! {
|
||||
<h1>{post.title.clone()}</h1>
|
||||
<p>{post.content.clone()}</p>
|
||||
|
||||
// since we're using async rendering for this page,
|
||||
// this metadata should be included in the actual HTML <head>
|
||||
// when it's first served
|
||||
<Title text=post.title.clone()/>
|
||||
<Meta name="description" content=post.content.clone()/>
|
||||
}
|
||||
})
|
||||
};
|
||||
<Title text=post.title/>
|
||||
<Meta name="description" content=post.content/>
|
||||
}),
|
||||
_ => Err(PostError::ServerError),
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<Suspense fallback=move || view! { <p>"Loading post..."</p> }>
|
||||
<ErrorBoundary fallback=|errors| {
|
||||
let errors = errors.clone();
|
||||
view! {
|
||||
<div class="error">
|
||||
<h1>"Something went wrong."</h1>
|
||||
<ul>
|
||||
{move || errors.get()
|
||||
.into_iter()
|
||||
.map(|(_, error)| view! { <li>{error.to_string()} </li> })
|
||||
.collect_view()
|
||||
}
|
||||
{move || {
|
||||
errors
|
||||
.get()
|
||||
.into_iter()
|
||||
.map(|(_, error)| view! { <li>{error.to_string()}</li> })
|
||||
.collect::<Vec<_>>()
|
||||
}}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
}>
|
||||
{post_view}
|
||||
</ErrorBoundary>
|
||||
}>{post_view}</ErrorBoundary>
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use axum::{
|
|||
http::{Request, Response, StatusCode, Uri},
|
||||
response::{IntoResponse, Response as AxumResponse},
|
||||
};
|
||||
use leptos::{view, LeptosOptions};
|
||||
use leptos::{config::LeptosOptions, view};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
|
|
|
@ -12,5 +12,5 @@ pub fn hydrate() {
|
|||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::mount_to_body(App);
|
||||
leptos::hydrate_body(App);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::Router;
|
||||
use leptos::{logging::log, *};
|
||||
use leptos::{logging::log, config::get_configuration, view, HydrationScripts};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use ssr_modes_axum::{app::*, fallback::file_and_error_handler};
|
||||
|
||||
|
@ -19,7 +19,28 @@ async fn main() {
|
|||
// _ = ListPostMetadata::register();
|
||||
|
||||
let app = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, || view! { <App/> })
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || {
|
||||
use leptos::prelude::*;
|
||||
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
// <AutoReload options=app_state.leptos_options.clone() />
|
||||
<HydrationScripts options=leptos_options.clone()/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/benwis_leptos.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<App/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}})
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ ssr = [
|
|||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
|
|
@ -53,6 +53,7 @@ use leptos::{
|
|||
config::LeptosOptions,
|
||||
context::{provide_context, use_context},
|
||||
reactive_graph::{computed::ScopedFuture, owner::Owner},
|
||||
tachys::ssr::StreamBuilder,
|
||||
IntoView,
|
||||
};
|
||||
use leptos_meta::{MetaContext, ServerMetaContext};
|
||||
|
@ -458,7 +459,7 @@ pub fn render_app_to_stream<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
render_app_to_stream_with_context(options, || {}, app_fn)
|
||||
}
|
||||
|
@ -480,7 +481,7 @@ pub fn render_route<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
render_route_with_context(options, paths, || {}, app_fn)
|
||||
}
|
||||
|
@ -549,7 +550,7 @@ pub fn render_app_to_stream_in_order<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
render_app_to_stream_in_order_with_context(options, || {}, app_fn)
|
||||
}
|
||||
|
@ -591,7 +592,7 @@ pub fn render_app_to_stream_with_context<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
render_app_to_stream_with_context_and_replace_blocks(
|
||||
options,
|
||||
|
@ -619,7 +620,7 @@ pub fn render_route_with_context<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let ooo = render_app_to_stream_with_context(
|
||||
LeptosOptions::from_ref(&options),
|
||||
|
@ -652,11 +653,8 @@ where
|
|||
.as_str();
|
||||
// 2. Find RouteListing in paths. This should probably be optimized, we probably don't want to
|
||||
// search for this every time
|
||||
let listing: &AxumRouteListing = paths
|
||||
.iter()
|
||||
// TODO this should be cached rather than recalculating the Axum version of the path
|
||||
.find(|r| r.path() == path)
|
||||
.unwrap_or_else(|| {
|
||||
let listing: &AxumRouteListing =
|
||||
paths.iter().find(|r| r.path() == path).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Failed to find the route {path} requested by the user. \
|
||||
This suggests that the routing rules in the Router that \
|
||||
|
@ -706,144 +704,11 @@ pub fn render_app_to_stream_with_context_and_replace_blocks<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
move |req: Request<Body>| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let add_context = additional_context.clone();
|
||||
let default_res_options = ResponseOptions::default();
|
||||
let res_options2 = default_res_options.clone();
|
||||
let res_options3 = default_res_options.clone();
|
||||
|
||||
let owner = Owner::new_root(Some(Arc::new(SsrSharedContext::new())));
|
||||
Box::pin(Sandboxed::new(async move {
|
||||
let meta_context = ServerMetaContext::new();
|
||||
let stream = owner.with(|| {
|
||||
// Need to get the path and query string of the Request
|
||||
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
|
||||
let path = req.uri().path_and_query().unwrap().as_str();
|
||||
|
||||
let full_path = format!("http://leptos.dev{path}");
|
||||
let (_, req_parts) = generate_request_and_parts(req);
|
||||
provide_contexts(
|
||||
&full_path,
|
||||
&meta_context,
|
||||
req_parts,
|
||||
default_res_options,
|
||||
);
|
||||
add_context();
|
||||
|
||||
// run app
|
||||
let app = app_fn();
|
||||
|
||||
// convert app to appropriate response type
|
||||
let app_stream = app.to_html_stream_out_of_order();
|
||||
|
||||
// TODO nonce
|
||||
|
||||
let shared_context = Owner::current_shared_context().unwrap();
|
||||
let chunks = Box::pin(
|
||||
shared_context
|
||||
.pending_data()
|
||||
.unwrap()
|
||||
.map(|chunk| format!("<script>{chunk}</script>")),
|
||||
);
|
||||
futures::stream::select(app_stream, chunks)
|
||||
});
|
||||
|
||||
let stream = meta_context.inject_meta_context(stream).await;
|
||||
|
||||
Html(Body::from_stream(Sandboxed::new(
|
||||
stream
|
||||
.map(|chunk| Ok(chunk) as Result<String, std::io::Error>)
|
||||
// drop the owner, cleaning up the reactive runtime,
|
||||
// once the stream is over
|
||||
.chain(once(async move {
|
||||
drop(owner);
|
||||
Ok(Default::default())
|
||||
})),
|
||||
)))
|
||||
.into_response()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
async fn generate_response(
|
||||
res_options: ResponseOptions,
|
||||
rx: Receiver<String>,
|
||||
) -> Response<Body> {
|
||||
todo!() /*
|
||||
let mut stream = Box::pin(rx.map(|html| Ok(Bytes::from(html))));
|
||||
|
||||
// Get the first and second chunks in the stream, which renders the app shell, and thus allows Resources to run
|
||||
let first_chunk = stream.next().await;
|
||||
|
||||
let second_chunk = stream.next().await;
|
||||
|
||||
// Extract the resources now that they've been rendered
|
||||
let res_options = res_options.0.read();
|
||||
|
||||
let complete_stream =
|
||||
futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap()])
|
||||
.chain(stream);
|
||||
|
||||
let mut res =
|
||||
Body::from_stream(Box::pin(complete_stream) as PinnedHtmlStream)
|
||||
.into_response();
|
||||
|
||||
if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
|
||||
let headers = res.headers_mut();
|
||||
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
headers.extend(res_headers.drain());
|
||||
|
||||
if !headers.contains_key(header::CONTENT_TYPE) {
|
||||
// Set the Content Type headers on all responses. This makes Firefox show the page source
|
||||
// without complaining
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_str("text/html; charset=utf-8").unwrap(),
|
||||
);
|
||||
}
|
||||
res*/
|
||||
}
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
async fn forward_stream(
|
||||
options: &LeptosOptions,
|
||||
res_options2: ResponseOptions,
|
||||
bundle: impl Stream<Item = String> + 'static,
|
||||
mut tx: Sender<String>,
|
||||
) {
|
||||
/*let mut shell = Box::pin(bundle);
|
||||
let first_app_chunk = shell.next().await.unwrap_or_default();
|
||||
|
||||
let (head, tail) =
|
||||
html_parts_separated(options, use_context::<MetaContext>().as_ref());
|
||||
|
||||
_ = tx.send(head).await;
|
||||
|
||||
_ = tx.send(first_app_chunk).await;
|
||||
|
||||
while let Some(fragment) = shell.next().await {
|
||||
_ = tx.send(fragment).await;
|
||||
}
|
||||
|
||||
_ = tx.send(tail.to_string()).await;
|
||||
|
||||
// Extract the value of ResponseOptions from here
|
||||
let res_options = use_context::<ResponseOptions>().unwrap();
|
||||
|
||||
let new_res_parts = res_options.0.read().clone();
|
||||
|
||||
let mut writable = res_options2.0.write();
|
||||
*writable = new_res_parts;
|
||||
|
||||
tx.close_channel();*/
|
||||
handle_response(options, additional_context, app_fn, |app| {
|
||||
Box::pin(app.to_html_stream_out_of_order())
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
|
@ -885,55 +750,103 @@ pub fn render_app_to_stream_in_order_with_context<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
|req| todo!()
|
||||
/*
|
||||
move |req: Request<Body>| {
|
||||
Box::pin({
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let add_context = additional_context.clone();
|
||||
let default_res_options = ResponseOptions::default();
|
||||
let res_options2 = default_res_options.clone();
|
||||
let res_options3 = default_res_options.clone();
|
||||
handle_response(options, additional_context, app_fn, |app| {
|
||||
Box::pin(app.to_html_stream_in_order())
|
||||
})
|
||||
}
|
||||
|
||||
async move {
|
||||
fn handle_response<IV>(
|
||||
options: LeptosOptions,
|
||||
additional_context: impl Fn() + 'static + Clone + Send,
|
||||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
stream_builder: fn(IV) -> Pin<Box<dyn Stream<Item = String> + Send>>,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
move |req: Request<Body>| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let add_context = additional_context.clone();
|
||||
let res_options = ResponseOptions::default();
|
||||
|
||||
let owner = Owner::new_root(Some(Arc::new(SsrSharedContext::new())));
|
||||
Box::pin(Sandboxed::new(async move {
|
||||
let meta_context = ServerMetaContext::new();
|
||||
let stream = owner.with(|| {
|
||||
// Need to get the path and query string of the Request
|
||||
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
|
||||
// if http, it returns a relative path. Adding .path() seems to make it explicitly return the relative uri
|
||||
let path = req.uri().path_and_query().unwrap().as_str();
|
||||
|
||||
let full_path = format!("http://leptos.dev{path}");
|
||||
let (_, req_parts) = generate_request_and_parts(req);
|
||||
provide_contexts(
|
||||
&full_path,
|
||||
&meta_context,
|
||||
req_parts,
|
||||
res_options.clone(),
|
||||
);
|
||||
add_context();
|
||||
|
||||
let (tx, rx) = futures::channel::mpsc::channel(8);
|
||||
let current_span = tracing::Span::current();
|
||||
spawn_task!(async move {
|
||||
let app = {
|
||||
let full_path = full_path.clone();
|
||||
let (parts, _) = req.into_parts();
|
||||
move || {
|
||||
provide_contexts(full_path, parts, default_res_options);
|
||||
app_fn().into_view()
|
||||
}
|
||||
};
|
||||
// run app
|
||||
let app = app_fn();
|
||||
|
||||
let (bundle, runtime) =
|
||||
leptos::ssr::render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||
app,
|
||||
|| generate_head_metadata_separated().1.into(),
|
||||
add_context,
|
||||
);
|
||||
// convert app to appropriate response type
|
||||
let app_stream = stream_builder(app);
|
||||
|
||||
forward_stream(&options, res_options2, bundle, tx).await;
|
||||
// TODO nonce
|
||||
|
||||
runtime.dispose();
|
||||
}.instrument(current_span));
|
||||
let shared_context = Owner::current_shared_context().unwrap();
|
||||
let chunks = Box::pin(
|
||||
shared_context
|
||||
.pending_data()
|
||||
.unwrap()
|
||||
.map(|chunk| format!("<script>{chunk}</script>")),
|
||||
);
|
||||
futures::stream::select(app_stream, chunks)
|
||||
});
|
||||
|
||||
generate_response(res_options3, rx).await
|
||||
let stream = meta_context.inject_meta_context(stream).await;
|
||||
|
||||
// TODO test this
|
||||
/*if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
})
|
||||
}*/
|
||||
|
||||
let headers = res.headers_mut();
|
||||
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
headers.extend(res_headers.drain());
|
||||
|
||||
if !headers.contains_key(header::CONTENT_TYPE) {
|
||||
// Set the Content Type headers on all responses. This makes Firefox show the page source
|
||||
// without complaining
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_str("text/html; charset=utf-8").unwrap(),
|
||||
);
|
||||
}*/
|
||||
|
||||
Html(Body::from_stream(Sandboxed::new(
|
||||
stream
|
||||
.map(|chunk| Ok(chunk) as Result<String, std::io::Error>)
|
||||
// drop the owner, cleaning up the reactive runtime,
|
||||
// once the stream is over
|
||||
.chain(once(async move {
|
||||
drop(owner);
|
||||
Ok(Default::default())
|
||||
})),
|
||||
)))
|
||||
.into_response()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
|
@ -1012,12 +925,12 @@ pub fn render_app_async<IV>(
|
|||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<String>> + Send + 'static>>
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
render_app_async_with_context(options, || {}, app_fn)
|
||||
}
|
||||
|
@ -1060,96 +973,15 @@ pub fn render_app_async_stream_with_context<IV>(
|
|||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
move |req: Request<Body>| {
|
||||
todo!()
|
||||
/*Box::pin({
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let add_context = additional_context.clone();
|
||||
let default_res_options = ResponseOptions::default();
|
||||
let res_options2 = default_res_options.clone();
|
||||
let res_options3 = default_res_options.clone();
|
||||
handle_response(options, additional_context, app_fn, |app| {
|
||||
Box::pin(futures::stream::once(async move {
|
||||
use futures::StreamExt;
|
||||
|
||||
async move {
|
||||
// Need to get the path and query string of the Request
|
||||
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
|
||||
// if http, it returns a relative path. Adding .path() seems to make it explicitly return the relative uri
|
||||
let path = req.uri().path_and_query().unwrap().as_str();
|
||||
|
||||
let full_path = format!("http://leptos.dev{path}");
|
||||
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
spawn_task!(async move {
|
||||
let app = {
|
||||
let full_path = full_path.clone();
|
||||
let (_, req_parts) = generate_request_and_parts(req);
|
||||
move || {
|
||||
provide_contexts(
|
||||
full_path,
|
||||
req_parts,
|
||||
default_res_options,
|
||||
);
|
||||
app_fn().into_view()
|
||||
}
|
||||
};
|
||||
|
||||
let (stream, runtime) =
|
||||
render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||
app,
|
||||
|| "".into(),
|
||||
add_context,
|
||||
);
|
||||
|
||||
// Extract the value of ResponseOptions from here
|
||||
let res_options = use_context::<ResponseOptions>().unwrap();
|
||||
|
||||
let html =
|
||||
build_async_response(stream, &options, runtime).await;
|
||||
|
||||
let new_res_parts = res_options.0.read().clone();
|
||||
|
||||
let mut writable = res_options2.0.write();
|
||||
*writable = new_res_parts;
|
||||
|
||||
_ = tx.send(html);
|
||||
});
|
||||
|
||||
let html = rx.await.expect("to complete HTML rendering");
|
||||
|
||||
let res_options = res_options3.0.read();
|
||||
|
||||
let complete_stream =
|
||||
futures::stream::iter([Ok(Bytes::from(html))]);
|
||||
|
||||
let mut res = Body::from_stream(
|
||||
Box::pin(complete_stream) as PinnedHtmlStream
|
||||
)
|
||||
.into_response();
|
||||
if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
let headers = res.headers_mut();
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
|
||||
headers.extend(res_headers.drain());
|
||||
|
||||
// This one doesn't use generate_response(), so we need to do this separately
|
||||
if !headers.contains_key(header::CONTENT_TYPE) {
|
||||
// Set the Content Type headers on all responses. This makes Firefox show the page source
|
||||
// without complaining
|
||||
headers.insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_str("text/html; charset=utf-8")
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
})*/
|
||||
}
|
||||
app.to_html_stream_out_of_order().collect::<String>().await
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
|
||||
|
@ -1185,84 +1017,20 @@ pub fn render_app_async_with_context<IV>(
|
|||
app_fn: impl Fn() -> IV + Clone + Send + 'static,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<Box<dyn Future<Output = Response<String>> + Send + 'static>>
|
||||
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
|_| todo!()
|
||||
/* move |req: Request<Body>| {
|
||||
Box::pin({
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let add_context = additional_context.clone();
|
||||
let default_res_options = ResponseOptions::default();
|
||||
let res_options2 = default_res_options.clone();
|
||||
let res_options3 = default_res_options.clone();
|
||||
handle_response(options, additional_context, app_fn, |app| {
|
||||
Box::pin(futures::stream::once(async move {
|
||||
use futures::StreamExt;
|
||||
|
||||
async move {
|
||||
// Need to get the path and query string of the Request
|
||||
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
|
||||
// if http, it returns a relative path. Adding .path() seems to make it explicitly return the relative uri
|
||||
let path = req.uri().path_and_query().unwrap().as_str();
|
||||
|
||||
let full_path = format!("http://leptos.dev{path}");
|
||||
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
|
||||
spawn_task!(async move {
|
||||
let app = {
|
||||
let full_path = full_path.clone();
|
||||
let (_, req_parts) = generate_request_and_parts(req);
|
||||
move || {
|
||||
provide_contexts(
|
||||
full_path,
|
||||
req_parts,
|
||||
default_res_options,
|
||||
);
|
||||
app_fn().into_view()
|
||||
}
|
||||
};
|
||||
|
||||
let (stream, runtime) =
|
||||
render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||
app,
|
||||
|| "".into(),
|
||||
add_context,
|
||||
);
|
||||
|
||||
// Extract the value of ResponseOptions from here
|
||||
let res_options = use_context::<ResponseOptions>().unwrap();
|
||||
|
||||
let html =
|
||||
build_async_response(stream, &options, runtime).await;
|
||||
|
||||
let new_res_parts = res_options.0.read().clone();
|
||||
|
||||
let mut writable = res_options2.0.write();
|
||||
*writable = new_res_parts;
|
||||
|
||||
_ = tx.send(html);
|
||||
});
|
||||
|
||||
let html = rx.await.expect("to complete HTML rendering");
|
||||
|
||||
let mut res = Response::new(html);
|
||||
|
||||
let res_options = res_options3.0.read();
|
||||
|
||||
if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
res.headers_mut().extend(res_headers.drain());
|
||||
|
||||
res
|
||||
}
|
||||
})
|
||||
}*/
|
||||
app.to_html_stream_out_of_order().collect::<String>().await
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
|
|
|
@ -168,6 +168,7 @@ impl ServerMetaContext {
|
|||
self,
|
||||
mut stream: impl Stream<Item = String> + Send + Unpin,
|
||||
) -> impl Stream<Item = String> + Send {
|
||||
println!("injecting meta context!");
|
||||
let mut first_chunk = stream.next().await.unwrap_or_default();
|
||||
|
||||
let meta_buf =
|
||||
|
@ -185,6 +186,7 @@ impl ServerMetaContext {
|
|||
let mut buf = String::with_capacity(
|
||||
first_chunk.len() + title_len + meta_buf.len(),
|
||||
);
|
||||
println!("first_chunk = {first_chunk:?}");
|
||||
let head_loc = first_chunk
|
||||
.find("</head>")
|
||||
.expect("you are using leptos_meta without a </head> tag");
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crate::{
|
||||
hooks::use_navigate,
|
||||
location::{
|
||||
BrowserUrl, Location, LocationChange, LocationProvider, State, Url,
|
||||
BrowserUrl, Location, LocationChange, LocationProvider, RequestUrl,
|
||||
State, Url,
|
||||
},
|
||||
navigate::{NavigateOptions, UseNavigate},
|
||||
resolve_path::resolve_path,
|
||||
MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes,
|
||||
MatchNestedRoutes, NestedRoute, NestedRoutesView, Routes, SsrMode,
|
||||
};
|
||||
use leptos::{
|
||||
children::{ToChildren, TypedChildren},
|
||||
|
@ -69,10 +70,18 @@ pub fn Router<Chil>(
|
|||
where
|
||||
Chil: IntoView,
|
||||
{
|
||||
let location =
|
||||
BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||
location.init(base.clone());
|
||||
let current_url = location.as_url().clone();
|
||||
let current_url = if Owner::current_shared_context()
|
||||
.map(|sc| sc.is_browser())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let location = BrowserUrl::new().expect("could not access browser navigation"); // TODO options here
|
||||
location.init(base.clone());
|
||||
location.as_url().clone()
|
||||
} else {
|
||||
let req = use_context::<RequestUrl>().expect("no RequestUrl provided");
|
||||
let parsed = req.parse().expect("could not parse RequestUrl");
|
||||
ArcRwSignal::new(parsed)
|
||||
};
|
||||
|
||||
// provide router context
|
||||
let state = ArcRwSignal::new(State::new(None));
|
||||
|
@ -228,11 +237,12 @@ where
|
|||
pub fn Route<Segments, View, ViewFn>(
|
||||
path: Segments,
|
||||
view: ViewFn,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, (), (), ViewFn, Dom>
|
||||
where
|
||||
ViewFn: Fn() -> View,
|
||||
{
|
||||
NestedRoute::new(path, view)
|
||||
NestedRoute::new(path, view, ssr)
|
||||
}
|
||||
|
||||
#[component]
|
||||
|
@ -240,12 +250,13 @@ pub fn ParentRoute<Segments, View, Children, ViewFn>(
|
|||
path: Segments,
|
||||
view: ViewFn,
|
||||
children: RouteChildren<Children>,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, Children, (), ViewFn, Dom>
|
||||
where
|
||||
ViewFn: Fn() -> View,
|
||||
{
|
||||
let children = children.into_inner();
|
||||
NestedRoute::new(path, view).child(children)
|
||||
NestedRoute::new(path, view, ssr).child(children)
|
||||
}
|
||||
|
||||
/// Redirects the user to a new URL, whether on the client side or on the server
|
||||
|
|
|
@ -24,16 +24,13 @@ impl Default for RequestUrl {
|
|||
}
|
||||
|
||||
impl RequestUrl {
|
||||
pub fn parse(url: &str) -> Result<Url, url::ParseError> {
|
||||
Self::parse_with_base(url, BASE)
|
||||
pub fn parse(&self) -> Result<Url, url::ParseError> {
|
||||
self.parse_with_base(BASE)
|
||||
}
|
||||
|
||||
pub fn parse_with_base(
|
||||
url: &str,
|
||||
base: &str,
|
||||
) -> Result<Url, url::ParseError> {
|
||||
pub fn parse_with_base(&self, base: &str) -> Result<Url, url::ParseError> {
|
||||
let base = url::Url::parse(base)?;
|
||||
let url = url::Url::options().base_url(Some(&base)).parse(url)?;
|
||||
let url = url::Url::options().base_url(Some(&base)).parse(&self.0)?;
|
||||
|
||||
let search_params = url
|
||||
.query_pairs()
|
||||
|
|
|
@ -6,6 +6,7 @@ pub use path_segment::*;
|
|||
mod horizontal;
|
||||
mod nested;
|
||||
mod vertical;
|
||||
use crate::{Method, SsrMode};
|
||||
pub use horizontal::*;
|
||||
pub use nested::*;
|
||||
use std::{borrow::Cow, marker::PhantomData};
|
||||
|
@ -91,7 +92,7 @@ where
|
|||
&self,
|
||||
) -> (
|
||||
Option<&str>,
|
||||
impl IntoIterator<Item = Vec<PathSegment>> + '_,
|
||||
impl IntoIterator<Item = GeneratedRouteData> + '_,
|
||||
) {
|
||||
(self.base.as_deref(), self.children.generate_routes())
|
||||
}
|
||||
|
@ -137,7 +138,13 @@ where
|
|||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_;
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct GeneratedRouteData {
|
||||
pub segments: Vec<PathSegment>,
|
||||
pub ssr_mode: SsrMode,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::{
|
|||
MatchInterface, MatchNestedRoutes, PartialPathMatch, PathSegment,
|
||||
PossibleRouteMatch, RouteMatchId,
|
||||
};
|
||||
use crate::{ChooseView, MatchParams};
|
||||
use crate::{ChooseView, MatchParams, SsrMode, GeneratedRouteData};
|
||||
use core::{fmt, iter};
|
||||
use std::{borrow::Cow, marker::PhantomData, sync::atomic::{AtomicU16, Ordering}};
|
||||
use either_of::Either;
|
||||
|
@ -23,18 +23,19 @@ pub struct NestedRoute<Segments, Children, Data, ViewFn, R> {
|
|||
pub data: Data,
|
||||
pub view: ViewFn,
|
||||
pub rndr: PhantomData<R>,
|
||||
pub ssr_mode: SsrMode
|
||||
}
|
||||
|
||||
impl<Segments, Children, Data, ViewFn, R> Clone for NestedRoute<Segments, Children, Data, ViewFn, R> where Segments: Clone, Children: Clone, Data: Clone, ViewFn: Clone{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,segments: self.segments.clone(),children: self.children.clone(),data: self.data.clone(), view: self.view.clone(), rndr: PhantomData
|
||||
id: self.id,segments: self.segments.clone(),children: self.children.clone(),data: self.data.clone(), view: self.view.clone(), rndr: PhantomData, ssr_mode: self.ssr_mode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Segments, ViewFn, R> NestedRoute<Segments, (), (), ViewFn, R> {
|
||||
pub fn new<View>(path: Segments, view: ViewFn) -> Self
|
||||
pub fn new<View>(path: Segments, view: ViewFn, ssr_mode: SsrMode) -> Self
|
||||
where
|
||||
ViewFn: Fn() -> View,
|
||||
R: Renderer + 'static,
|
||||
|
@ -46,6 +47,7 @@ impl<Segments, ViewFn, R> NestedRoute<Segments, (), (), ViewFn, R> {
|
|||
data: (),
|
||||
view,
|
||||
rndr: PhantomData,
|
||||
ssr_mode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +63,7 @@ impl<Segments, Data, ViewFn, R> NestedRoute<Segments, (), Data, ViewFn, R> {
|
|||
data,
|
||||
view,
|
||||
rndr,
|
||||
ssr_mode,
|
||||
..
|
||||
} = self;
|
||||
NestedRoute {
|
||||
|
@ -69,6 +72,7 @@ impl<Segments, Data, ViewFn, R> NestedRoute<Segments, (), Data, ViewFn, R> {
|
|||
children: Some(child),
|
||||
data,
|
||||
view,
|
||||
ssr_mode,
|
||||
rndr,
|
||||
}
|
||||
}
|
||||
|
@ -219,14 +223,31 @@ where
|
|||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_ {
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
let mut segment_routes = Vec::new();
|
||||
self.segments.generate_path(&mut segment_routes);
|
||||
let children = self.children.as_ref();
|
||||
let ssr_mode = self.ssr_mode;
|
||||
|
||||
match children {
|
||||
None => Either::Left(iter::once(segment_routes)),
|
||||
None => Either::Left(iter::once(GeneratedRouteData {
|
||||
segments: segment_routes,
|
||||
ssr_mode
|
||||
})),
|
||||
Some(children) => {
|
||||
Either::Right(children.generate_routes().into_iter())
|
||||
Either::Right(children.generate_routes().into_iter().map(move |child| {
|
||||
if child.ssr_mode > ssr_mode {
|
||||
GeneratedRouteData {
|
||||
segments: child.segments ,
|
||||
ssr_mode: child.ssr_mode,
|
||||
}
|
||||
} else {
|
||||
GeneratedRouteData {
|
||||
segments: child.segments ,
|
||||
ssr_mode,
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::{MatchInterface, MatchNestedRoutes, PathSegment, RouteMatchId};
|
||||
use crate::{ChooseView, MatchParams};
|
||||
use crate::{ChooseView, GeneratedRouteData, MatchParams};
|
||||
use core::iter;
|
||||
use either_of::*;
|
||||
use std::{any::Any, borrow::Cow};
|
||||
|
@ -55,8 +55,11 @@ where
|
|||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_ {
|
||||
iter::once(vec![PathSegment::Unit])
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
iter::once(GeneratedRouteData {
|
||||
segments: vec![PathSegment::Unit],
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +118,7 @@ where
|
|||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_ {
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
self.0.generate_routes()
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +210,7 @@ where
|
|||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_ {
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
let (A, B) = &self;
|
||||
|
@ -304,7 +307,7 @@ macro_rules! tuples {
|
|||
|
||||
fn generate_routes(
|
||||
&self,
|
||||
) -> impl IntoIterator<Item = Vec<PathSegment>> + '_ {
|
||||
) -> impl IntoIterator<Item = GeneratedRouteData> + '_ {
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
let ($($ty,)*) = &self;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::{
|
||||
location::{Location, Url},
|
||||
location::{Location, RequestUrl, Url},
|
||||
matching::Routes,
|
||||
params::ParamsMap,
|
||||
resolve_path::resolve_path,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, RouteMatchId,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, Method,
|
||||
PathSegment, RouteList, RouteListing, RouteMatchId,
|
||||
};
|
||||
use either_of::Either;
|
||||
use leptos::{component, oco::Oco, IntoView};
|
||||
|
@ -16,6 +17,7 @@ use reactive_graph::{
|
|||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
sync::{
|
||||
|
@ -25,10 +27,11 @@ use std::{
|
|||
};
|
||||
use tachys::{
|
||||
renderer::Renderer,
|
||||
ssr::StreamBuilder,
|
||||
view::{
|
||||
any_view::{AnyView, AnyViewState, IntoAny},
|
||||
either::EitherState,
|
||||
Mountable, Render, RenderHtml,
|
||||
Mountable, Position, Render, RenderHtml,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -150,12 +153,86 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
fn to_html_with_buf(
|
||||
fn to_html_with_buf(self, buf: &mut String, position: &mut Position) {
|
||||
// if this is being run on the server for the first time, generating all possible routes
|
||||
if RouteList::is_generating() {
|
||||
// add routes
|
||||
let (base, routes) = self.routes.generate_routes();
|
||||
let mut routes = routes
|
||||
.into_iter()
|
||||
.map(|data| {
|
||||
let path = base
|
||||
.into_iter()
|
||||
.flat_map(|base| {
|
||||
iter::once(PathSegment::Static(
|
||||
base.to_string().into(),
|
||||
))
|
||||
})
|
||||
.chain(data.segments)
|
||||
.collect::<Vec<_>>();
|
||||
RouteListing::new(
|
||||
path,
|
||||
data.ssr_mode,
|
||||
// TODO methods
|
||||
[Method::Get],
|
||||
// TODO static data
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
println!("routes = {routes:#?}");
|
||||
|
||||
// add fallback
|
||||
// TODO fix: causes overlapping route issues on Axum
|
||||
/*routes.push(RouteListing::new(
|
||||
[PathSegment::Static(
|
||||
base.unwrap_or_default().to_string().into(),
|
||||
)],
|
||||
SsrMode::Async,
|
||||
[
|
||||
Method::Get,
|
||||
Method::Post,
|
||||
Method::Put,
|
||||
Method::Patch,
|
||||
Method::Delete,
|
||||
],
|
||||
None,
|
||||
));*/
|
||||
|
||||
RouteList::register(RouteList::from(routes));
|
||||
} else {
|
||||
let outer_owner = Owner::current()
|
||||
.expect("creating Router, but no Owner was found");
|
||||
let url = use_context::<RequestUrl>()
|
||||
.expect("could not find request URL in context");
|
||||
// TODO base
|
||||
let url = if let Some(base) = &self.base {
|
||||
url.parse_with_base(base.as_ref())
|
||||
} else {
|
||||
url.parse()
|
||||
}
|
||||
.expect("could not parse URL");
|
||||
// TODO query params
|
||||
let new_match = self.routes.match_route(url.path());
|
||||
/*match new_match {
|
||||
Some(matched) => {
|
||||
Either::Left(NestedRouteView::new(&outer_owner, matched))
|
||||
}
|
||||
_ => Either::Right((self.fallback)()),
|
||||
}
|
||||
.to_html_with_buf(buf, position)*/
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut String,
|
||||
position: &mut tachys::view::Position,
|
||||
) {
|
||||
todo!()
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
}
|
||||
|
||||
fn hydrate<const FROM_SERVER: bool>(
|
||||
|
|
|
@ -212,7 +212,7 @@ where
|
|||
let (base, routes) = self.routes.generate_routes();
|
||||
let mut routes = routes
|
||||
.into_iter()
|
||||
.map(|segments| {
|
||||
.map(|data| {
|
||||
let path = base
|
||||
.into_iter()
|
||||
.flat_map(|base| {
|
||||
|
@ -220,13 +220,13 @@ where
|
|||
base.to_string().into(),
|
||||
))
|
||||
})
|
||||
.chain(segments)
|
||||
.chain(data.segments)
|
||||
.collect::<Vec<_>>();
|
||||
// TODO add non-defaults for mode, etc.
|
||||
RouteListing::new(
|
||||
path,
|
||||
SsrMode::OutOfOrder,
|
||||
[Method::Get],
|
||||
data.ssr_mode,
|
||||
data.methods,
|
||||
None,
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue