mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 19:32:37 -05:00
Compare commits
1 Commits
v0.3.0
...
script-ord
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5da13f3f4e |
28
Cargo.toml
28
Cargo.toml
@@ -25,22 +25,22 @@ members = [
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.0"
|
||||
version = "0.3.0-alpha"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", default-features = false, version = "0.3.0" }
|
||||
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.3.0" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.3.0" }
|
||||
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.3.0" }
|
||||
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.3.0" }
|
||||
leptos_server = { path = "./leptos_server", default-features = false, version = "0.3.0" }
|
||||
server_fn = { path = "./server_fn", default-features = false, version = "0.3.0" }
|
||||
server_fn_macro = { path = "./server_fn_macro", default-features = false, version = "0.3.0" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", default-features = false, version = "0.3.0" }
|
||||
leptos_config = { path = "./leptos_config", default-features = false, version = "0.3.0" }
|
||||
leptos_router = { path = "./router", version = "0.3.0" }
|
||||
leptos_meta = { path = "./meta", default-features = false, version = "0.3.0" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.3.0" }
|
||||
leptos = { path = "./leptos", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.3.0-alpha" }
|
||||
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_server = { path = "./leptos_server", default-features = false, version = "0.3.0-alpha" }
|
||||
server_fn = { path = "./server_fn", default-features = false, version = "0.3.0-alpha" }
|
||||
server_fn_macro = { path = "./server_fn_macro", default-features = false, version = "0.3.0-alpha" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_config = { path = "./leptos_config", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_router = { path = "./router", version = "0.3.0-alpha" }
|
||||
leptos_meta = { path = "./meta", default-features = false, version = "0.3.0-alpha" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.3.0-alpha" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -69,11 +69,7 @@ dependencies = [
|
||||
|
||||
[tasks.test]
|
||||
clear = true
|
||||
dependencies = [
|
||||
"test-all",
|
||||
"test-leptos_macro-example",
|
||||
"doc-leptos_macro-example",
|
||||
]
|
||||
dependencies = ["test-all", "test-leptos_macro-example", "doc-leptos_macro-example"]
|
||||
|
||||
[tasks.test-all]
|
||||
command = "cargo"
|
||||
@@ -106,15 +102,9 @@ cwd = "examples"
|
||||
command = "cargo"
|
||||
args = ["make", "verify-flow"]
|
||||
|
||||
[tasks.clean-examples]
|
||||
description = "Clean all example projects"
|
||||
cwd = "examples"
|
||||
command = "cargo"
|
||||
args = ["make", "clean-all"]
|
||||
|
||||
[env]
|
||||
RUSTFLAGS = ""
|
||||
LEPTOS_OUTPUT_NAME = "ci" # allows examples to check/build without cargo-leptos
|
||||
LEPTOS_OUTPUT_NAME="ci" # allows examples to check/build without cargo-leptos
|
||||
|
||||
[env.github-actions]
|
||||
RUSTFLAGS = "-D warnings"
|
||||
|
||||
@@ -31,7 +31,7 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
|
||||
|
||||
[tasks.verify-flow]
|
||||
description = "Provides pre and post hooks for verify"
|
||||
dependencies = ["pre-verify", "verify", "post-verify"]
|
||||
dependencies = ["pre-verify-flow", "verify", "post-verify-flow"]
|
||||
|
||||
[tasks.verify]
|
||||
description = "Run all quality checks and tests"
|
||||
@@ -41,17 +41,16 @@ dependencies = ["check-style", "test-unit-and-web"]
|
||||
description = "Run all unit and web tests"
|
||||
dependencies = ["test-flow", "web-test-flow"]
|
||||
|
||||
[tasks.pre-verify]
|
||||
[tasks.pre-verify-flow]
|
||||
|
||||
[tasks.post-verify]
|
||||
dependencies = ["clean-all"]
|
||||
[tasks.post-verify-flow]
|
||||
|
||||
[tasks.web-test-flow]
|
||||
description = "Provides pre and post hooks for web-test"
|
||||
dependencies = ["pre-web-test", "web-test", "post-web-test"]
|
||||
dependencies = ["pre-web-test-flow", "web-test", "post-web-test-flow"]
|
||||
|
||||
[tasks.pre-web-test]
|
||||
[tasks.pre-web-test-flow]
|
||||
|
||||
[tasks.web-test]
|
||||
|
||||
[tasks.post-web-test]
|
||||
[tasks.post-web-test-flow]
|
||||
|
||||
@@ -12,12 +12,3 @@ dependencies = ["check-style", "test-local"]
|
||||
[tasks.test-local]
|
||||
description = "Run all tests from an example directory"
|
||||
dependencies = ["test", "web-test"]
|
||||
|
||||
[tasks.clean-trunk]
|
||||
description = "Runs the trunk clean command."
|
||||
category = "Cleanup"
|
||||
command = "trunk"
|
||||
args = ["clean"]
|
||||
|
||||
[tasks.clean-all]
|
||||
dependencies = ["clean", "clean-trunk"]
|
||||
|
||||
@@ -10,12 +10,12 @@ crate-type = ["cdylib", "rlib"]
|
||||
console_log = "1.0.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
cfg-if = "1.0.0"
|
||||
leptos = { path = "../../leptos", default-features = false, features = [
|
||||
leptos = { path = "../../../leptos/leptos", default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
leptos_axum = { path = "../../../leptos/integrations/axum", default-features = false, optional = true }
|
||||
leptos_meta = { path = "../../../leptos/meta", default-features = false }
|
||||
leptos_router = { path = "../../../leptos/router", default-features = false }
|
||||
log = "0.4.17"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
simple_logger = "4.0.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ use cfg_if::cfg_if;
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::State,
|
||||
extract::Extension,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
@@ -14,7 +14,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use leptos::{LeptosOptions, view};
|
||||
use crate::landing::App;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
@@ -5,7 +5,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use crate::landing::*;
|
||||
use axum::body::Body as AxumBody;
|
||||
use axum::{
|
||||
extract::{State, Path},
|
||||
extract::{Extension, Path},
|
||||
http::Request,
|
||||
response::{IntoResponse, Response},
|
||||
routing::{get, post},
|
||||
@@ -21,7 +21,7 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
#[cfg(feature = "ssr")]
|
||||
async fn custom_handler(
|
||||
Path(id): Path<String>,
|
||||
State(options): State<Arc<LeptosOptions>>,
|
||||
Extension(options): Extension<Arc<LeptosOptions>>,
|
||||
req: Request<AxumBody>,
|
||||
) -> Response {
|
||||
let handler = leptos_axum::render_app_to_stream_with_context(
|
||||
@@ -44,7 +44,7 @@ async fn main() {
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = Arc::new(conf.leptos_options);
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||
|
||||
@@ -58,7 +58,7 @@ async fn main() {
|
||||
|cx| view! { cx, <App/> },
|
||||
)
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -4,7 +4,7 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::State,
|
||||
extract::Extension,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
@@ -15,13 +15,13 @@ if #[cfg(feature = "ssr")] {
|
||||
use leptos::{LeptosOptions};
|
||||
use crate::error_template::error_template;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
res.into_response()
|
||||
} else{
|
||||
let handler = leptos_axum::render_app_to_stream(options.to_owned(), |cx| error_template(cx, None));
|
||||
handler(req).await.into_response()
|
||||
|
||||
@@ -7,6 +7,7 @@ if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
extract::Extension,
|
||||
};
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use std::sync::Arc;
|
||||
@@ -17,7 +18,7 @@ if #[cfg(feature = "ssr")] {
|
||||
use hackernews_axum::*;
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let leptos_options = Arc::new(conf.leptos_options);
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||
|
||||
@@ -28,7 +29,7 @@ if #[cfg(feature = "ssr")] {
|
||||
.route("/favicon.ico", get(file_and_error_handler))
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> } )
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -4,7 +4,7 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::State,
|
||||
extract::Extension,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
@@ -16,13 +16,13 @@ if #[cfg(feature = "ssr")] {
|
||||
use crate::error_template::ErrorTemplate;
|
||||
use crate::errors::TodoAppError;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
res.into_response()
|
||||
} else{
|
||||
let mut errors = Errors::default();
|
||||
errors.insert_with_default_key(TodoAppError::NotFound);
|
||||
|
||||
@@ -6,7 +6,7 @@ if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
response::{Response, IntoResponse},
|
||||
routing::get,
|
||||
extract::{Path, State, Extension, RawQuery},
|
||||
extract::{Path, Extension, RawQuery},
|
||||
http::{Request, header::HeaderMap},
|
||||
body::Body as AxumBody,
|
||||
Router,
|
||||
@@ -33,7 +33,7 @@ if #[cfg(feature = "ssr")] {
|
||||
}, request).await
|
||||
}
|
||||
|
||||
async fn leptos_routes_handler(Extension(pool): Extension<SqlitePool>, auth_session: AuthSession, State(options): State<Arc<LeptosOptions>>, req: Request<AxumBody>) -> Response{
|
||||
async fn leptos_routes_handler(Extension(pool): Extension<SqlitePool>, auth_session: AuthSession, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<AxumBody>) -> Response{
|
||||
let handler = leptos_axum::render_app_to_stream_with_context((*options).clone(),
|
||||
move |cx| {
|
||||
provide_context(cx, auth_session.clone());
|
||||
@@ -68,7 +68,7 @@ if #[cfg(feature = "ssr")] {
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = Arc::new(conf.leptos_options);
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
||||
|
||||
@@ -80,8 +80,8 @@ if #[cfg(feature = "ssr")] {
|
||||
.layer(AuthSessionLayer::<User, i64, SessionSqlitePool, SqlitePool>::new(Some(pool.clone()))
|
||||
.with_config(auth_config))
|
||||
.layer(SessionLayer::new(session_store))
|
||||
.layer(Extension(pool))
|
||||
.with_state(leptos_options);
|
||||
.layer(Extension(Arc::new(leptos_options)))
|
||||
.layer(Extension(pool));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -3,7 +3,7 @@ use cfg_if::cfg_if;
|
||||
cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::State,
|
||||
extract::Extension,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
@@ -14,13 +14,13 @@ cfg_if! { if #[cfg(feature = "ssr")] {
|
||||
use leptos::{LeptosOptions, view};
|
||||
use crate::app::App;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
res.into_response()
|
||||
} else{
|
||||
let handler = leptos_axum::render_app_to_stream(
|
||||
options.to_owned(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::post, Router};
|
||||
use axum::{extract::Extension, routing::post, Router};
|
||||
use leptos::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use ssr_modes_axum::{app::*, fallback::file_and_error_handler};
|
||||
@@ -9,7 +9,7 @@ async fn main() {
|
||||
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let addr = conf.leptos_options.site_addr;
|
||||
let leptos_options = Arc::new(conf.leptos_options);
|
||||
let leptos_options = conf.leptos_options;
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||
|
||||
@@ -24,7 +24,7 @@ async fn main() {
|
||||
|cx| view! { cx, <App/> },
|
||||
)
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -5,7 +5,7 @@ This is a template demonstrating how to integrate [TailwindCSS](https://tailwind
|
||||
|
||||
Install Tailwind and build the CSS:
|
||||
|
||||
`Trunk.toml` is configured to build the CSS automatically.
|
||||
`npx tailwindcss -i ./input.css -o ./style/output.css --watch`
|
||||
|
||||
Install trunk to client side render this bundle.
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[[hooks]]
|
||||
stage = "pre_build"
|
||||
command = "sh"
|
||||
command_arguments = ["-c", "npx tailwindcss -i input.css -o style/output.css"]
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com
|
||||
! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -31,7 +31,6 @@
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
6. Use the user's configured `sans` font-variation-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
@@ -48,8 +47,6 @@ html {
|
||||
/* 4 */
|
||||
font-feature-settings: normal;
|
||||
/* 5 */
|
||||
font-variation-settings: normal;
|
||||
/* 6 */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -436,9 +433,6 @@ video {
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
@@ -486,9 +480,6 @@ video {
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-gradient-from-position: ;
|
||||
--tw-gradient-via-position: ;
|
||||
--tw-gradient-to-position: ;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
|
||||
@@ -4,7 +4,7 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::State,
|
||||
extract::Extension,
|
||||
response::IntoResponse,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
@@ -16,13 +16,13 @@ if #[cfg(feature = "ssr")] {
|
||||
use crate::error_template::ErrorTemplate;
|
||||
use crate::errors::TodoAppError;
|
||||
|
||||
pub async fn file_and_error_handler(uri: Uri, State(options): State<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
pub async fn file_and_error_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<Body>) -> AxumResponse {
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
res.into_response()
|
||||
} else{
|
||||
let mut errors = Errors::default();
|
||||
errors.insert_with_default_key(TodoAppError::NotFound);
|
||||
|
||||
@@ -5,7 +5,7 @@ cfg_if! {
|
||||
use leptos::*;
|
||||
use axum::{
|
||||
routing::{post, get},
|
||||
extract::{State, Path},
|
||||
extract::{Extension, Path},
|
||||
http::Request,
|
||||
response::{IntoResponse, Response},
|
||||
Router,
|
||||
@@ -18,7 +18,7 @@ cfg_if! {
|
||||
use std::sync::Arc;
|
||||
|
||||
//Define a handler to test extractor with state
|
||||
async fn custom_handler(Path(id): Path<String>, State(options): State<Arc<LeptosOptions>>, req: Request<AxumBody>) -> Response{
|
||||
async fn custom_handler(Path(id): Path<String>, Extension(options): Extension<Arc<LeptosOptions>>, req: Request<AxumBody>) -> Response{
|
||||
let handler = leptos_axum::render_app_to_stream_with_context((*options).clone(),
|
||||
move |cx| {
|
||||
provide_context(cx, id.clone());
|
||||
@@ -42,7 +42,7 @@ cfg_if! {
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = Arc::new(conf.leptos_options);
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
||||
|
||||
@@ -52,7 +52,7 @@ cfg_if! {
|
||||
.route("/special/:id", get(custom_handler))
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <TodoApp/> } )
|
||||
.fallback(file_and_error_handler)
|
||||
.with_state(leptos_options);
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -16,9 +16,9 @@ use actix_web::{
|
||||
use futures::{Stream, StreamExt};
|
||||
use http::StatusCode;
|
||||
use leptos::{
|
||||
leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context,
|
||||
leptos_server::{server_fn_by_path, Payload},
|
||||
server_fn::Encoding,
|
||||
ssr::render_to_stream_with_prefix_undisposed_with_context_and_block_replacement,
|
||||
*,
|
||||
};
|
||||
use leptos_integration_utils::{build_async_response, html_parts_separated};
|
||||
@@ -514,43 +514,6 @@ pub fn render_app_to_stream_with_context<IV>(
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
render_app_to_stream_with_context_and_replace_blocks(
|
||||
options,
|
||||
additional_context,
|
||||
app_fn,
|
||||
method,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns 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.
|
||||
///
|
||||
/// This function allows you to provide additional information to Leptos for your route.
|
||||
/// It could be used to pass in Path Info, Connection Info, or anything your heart desires.
|
||||
///
|
||||
/// `replace_blocks` additionally lets you specify whether `<Suspense/>` fragments that read
|
||||
/// from blocking resources should be retrojected into the HTML that's initially served, rather
|
||||
/// than dynamically inserting them with JavaScript on the client. This means you will have
|
||||
/// better support if JavaScript is not enabled, in exchange for a marginally slower response time.
|
||||
///
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
pub fn render_app_to_stream_with_context_and_replace_blocks<IV>(
|
||||
options: LeptosOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
method: Method,
|
||||
replace_blocks: bool,
|
||||
) -> Route
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
@@ -570,14 +533,7 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
stream_app(
|
||||
&options,
|
||||
app,
|
||||
res_options,
|
||||
additional_context,
|
||||
replace_blocks,
|
||||
)
|
||||
.await
|
||||
stream_app(&options, app, res_options, additional_context).await
|
||||
}
|
||||
};
|
||||
match method {
|
||||
@@ -697,6 +653,103 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns 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](leptos::ssr::render_to_stream), and
|
||||
/// includes everything described in the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{App, HttpServer};
|
||||
/// use leptos::*;
|
||||
/// use leptos_actix::DataResponse;
|
||||
/// use std::{env, net::SocketAddr};
|
||||
///
|
||||
/// #[component]
|
||||
/// fn MyApp(cx: Scope, data: &'static str) -> impl IntoView {
|
||||
/// 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<()> {
|
||||
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
/// let addr = conf.leptos_options.site_addr.clone();
|
||||
/// HttpServer::new(move || {
|
||||
/// let leptos_options = &conf.leptos_options;
|
||||
///
|
||||
/// 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_preloaded_data_app(
|
||||
/// leptos_options.to_owned(),
|
||||
/// |req| async move {
|
||||
/// Ok(DataResponse::Data(
|
||||
/// "async func that can preload data",
|
||||
/// ))
|
||||
/// },
|
||||
/// |cx, data| view! { cx, <MyApp data/> },
|
||||
/// ),
|
||||
/// )
|
||||
/// })
|
||||
/// .bind(&addr)?
|
||||
/// .run()
|
||||
/// .await
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[deprecated = "You can now use `render_app_async` with `create_resource` and \
|
||||
`<Suspense/>` to achieve async rendering without manually \
|
||||
preloading data."]
|
||||
pub fn render_preloaded_data_app<Data, Fut, IV>(
|
||||
options: LeptosOptions,
|
||||
data_fn: impl Fn(HttpRequest) -> Fut + Clone + 'static,
|
||||
app_fn: impl Fn(leptos::Scope, Data) -> IV + Clone + Send + 'static,
|
||||
) -> Route
|
||||
where
|
||||
Data: 'static,
|
||||
Fut: Future<Output = Result<DataResponse<Data>, actix_web::Error>>,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let data_fn = data_fn.clone();
|
||||
let res_options = ResponseOptions::default();
|
||||
|
||||
async move {
|
||||
let data = match data_fn(req.clone()).await {
|
||||
Err(e) => return HttpResponse::from_error(e),
|
||||
Ok(DataResponse::Response(r)) => return r.into(),
|
||||
Ok(DataResponse::Data(d)) => d,
|
||||
};
|
||||
|
||||
let app = {
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = res_options.clone();
|
||||
move |cx| {
|
||||
provide_contexts(cx, &req, res_options);
|
||||
(app_fn)(cx, data).into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
stream_app(&options, app, res_options, |_cx| {}).await
|
||||
}
|
||||
})
|
||||
}
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
fn provide_contexts(
|
||||
cx: leptos::Scope,
|
||||
@@ -728,14 +781,12 @@ async fn stream_app(
|
||||
app: impl FnOnce(leptos::Scope) -> View + 'static,
|
||||
res_options: ResponseOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
replace_blocks: bool,
|
||||
) -> HttpResponse<BoxBody> {
|
||||
let (stream, runtime, scope) =
|
||||
render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
render_to_stream_with_prefix_undisposed_with_context(
|
||||
app,
|
||||
move |cx| generate_head_metadata_separated(cx).1.into(),
|
||||
additional_context,
|
||||
replace_blocks
|
||||
);
|
||||
|
||||
build_stream_response(options, res_options, stream, runtime, scope).await
|
||||
@@ -934,6 +985,22 @@ pub trait LeptosRoutes {
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
|
||||
#[deprecated = "You can now use `leptos_routes` and a `<Route \
|
||||
mode=SsrMode::Async/>`
|
||||
to achieve async rendering without manually preloading \
|
||||
data."]
|
||||
fn leptos_preloaded_data_routes<Data, Fut, IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
data_fn: impl Fn(HttpRequest) -> Fut + Clone + 'static,
|
||||
app_fn: impl Fn(leptos::Scope, Data) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Data: 'static,
|
||||
Fut: Future<Output = Result<DataResponse<Data>, actix_web::Error>>,
|
||||
IV: IntoView + 'static;
|
||||
|
||||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
@@ -968,7 +1035,34 @@ where
|
||||
{
|
||||
self.leptos_routes_with_context(options, paths, |_| {}, app_fn)
|
||||
}
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
fn leptos_preloaded_data_routes<Data, Fut, IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
data_fn: impl Fn(HttpRequest) -> Fut + Clone + 'static,
|
||||
app_fn: impl Fn(leptos::Scope, Data) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Data: 'static,
|
||||
Fut: Future<Output = Result<DataResponse<Data>, actix_web::Error>>,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
|
||||
for path in paths.iter() {
|
||||
router = router.route(
|
||||
path,
|
||||
#[allow(deprecated)]
|
||||
render_preloaded_data_app(
|
||||
options.clone(),
|
||||
data_fn.clone(),
|
||||
app_fn.clone(),
|
||||
),
|
||||
);
|
||||
}
|
||||
router
|
||||
}
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
@@ -997,15 +1091,6 @@ where
|
||||
method,
|
||||
)
|
||||
}
|
||||
SsrMode::PartiallyBlocked => {
|
||||
render_app_to_stream_with_context_and_replace_blocks(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
method,
|
||||
true,
|
||||
)
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
|
||||
@@ -129,6 +129,23 @@ pub fn redirect(cx: leptos::Scope, path: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decomposes an HTTP request into its parts, allowing you to read its headers
|
||||
/// and other data without consuming the body.
|
||||
#[deprecated(note = "Replaced with generate_request_and_parts() to allow for \
|
||||
putting LeptosRequest in the Context")]
|
||||
pub async fn generate_request_parts(req: Request<Body>) -> RequestParts {
|
||||
// provide request headers as context in server scope
|
||||
let (parts, body) = req.into_parts();
|
||||
let body = body::to_bytes(body).await.unwrap_or_default();
|
||||
RequestParts {
|
||||
method: parts.method,
|
||||
uri: parts.uri,
|
||||
headers: parts.headers,
|
||||
version: parts.version,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Decomposes an HTTP request into its parts, allowing you to read its headers
|
||||
/// and other data without consuming the body. Creates a new Request from the
|
||||
/// original parts for further processing
|
||||
@@ -605,54 +622,6 @@ pub fn render_app_to_stream_with_context<IV>(
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
render_app_to_stream_with_context_and_replace_blocks(
|
||||
options,
|
||||
additional_context,
|
||||
app_fn,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This version allows us to pass Axum State/Extension/Extractor or other infro from Axum or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure.
|
||||
///
|
||||
/// `replace_blocks` additionally lets you specify whether `<Suspense/>` fragments that read
|
||||
/// from blocking resources should be retrojected into the HTML that's initially served, rather
|
||||
/// than dynamically inserting them with JavaScript on the client. This means you will have
|
||||
/// better support if JavaScript is not enabled, in exchange for a marginally slower response time.
|
||||
///
|
||||
/// Otherwise, this function is identical to [render_app_to_stream_with_context].
|
||||
///
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [RequestParts]
|
||||
/// - [ResponseOptions]
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[tracing::instrument(level = "info", fields(error), skip_all)]
|
||||
pub fn render_app_to_stream_with_context_and_replace_blocks<IV>(
|
||||
options: LeptosOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
replace_blocks: bool,
|
||||
) -> impl Fn(
|
||||
Request<Body>,
|
||||
) -> Pin<
|
||||
Box<
|
||||
dyn Future<Output = Response<StreamBody<PinnedHtmlStream>>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
> + Clone
|
||||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
move |req: Request<Body>| {
|
||||
Box::pin({
|
||||
@@ -682,11 +651,10 @@ where
|
||||
}
|
||||
};
|
||||
let (bundle, runtime, scope) =
|
||||
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
|
||||
app,
|
||||
|cx| generate_head_metadata_separated(cx).1.into(),
|
||||
add_context,
|
||||
replace_blocks
|
||||
);
|
||||
|
||||
forward_stream(&options, res_options2, bundle, runtime, scope, tx).await;
|
||||
@@ -695,7 +663,6 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", fields(error), skip_all)]
|
||||
async fn generate_response(
|
||||
res_options: ResponseOptions,
|
||||
@@ -1126,39 +1093,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait allows one to use your custom struct in Axum's router, provided it can provide the
|
||||
/// `LeptosOptions` to use for the `LeptosRoutes` trait functions.
|
||||
pub trait LeptosOptionProvider {
|
||||
fn options(&self) -> LeptosOptions;
|
||||
}
|
||||
|
||||
/// Implement `LeptosOptionProvider` trait for `LeptosOptions` itself.
|
||||
impl LeptosOptionProvider for LeptosOptions {
|
||||
fn options(&self) -> LeptosOptions {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `LeptosOptionProvider` trait for any type wrapped in an Arc, if that type implements
|
||||
/// `LeptosOptionProvider` as states in axum are often provided wrapped in an Arc.
|
||||
impl<T> LeptosOptionProvider for Arc<T>
|
||||
where
|
||||
T: LeptosOptionProvider,
|
||||
{
|
||||
fn options(&self) -> LeptosOptions {
|
||||
(**self).options()
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid
|
||||
/// having to use wildcards or manually define all routes in multiple places.
|
||||
pub trait LeptosRoutes<OP>
|
||||
where
|
||||
OP: LeptosOptionProvider,
|
||||
{
|
||||
pub trait LeptosRoutes {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: OP,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
@@ -1167,7 +1107,7 @@ where
|
||||
|
||||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: OP,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
@@ -1181,20 +1121,16 @@ where
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
H: axum::handler::Handler<T, OP, axum::body::Body>,
|
||||
H: axum::handler::Handler<T, (), axum::body::Body>,
|
||||
T: 'static;
|
||||
}
|
||||
|
||||
/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests
|
||||
/// to those paths to Leptos's renderer.
|
||||
impl<OP> LeptosRoutes<OP> for axum::Router<OP>
|
||||
where
|
||||
OP: LeptosOptionProvider + Clone + Send + Sync + 'static,
|
||||
{
|
||||
impl LeptosRoutes for axum::Router {
|
||||
#[tracing::instrument(level = "info", fields(error), skip_all)]
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: OP,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<RouteListing>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
@@ -1207,7 +1143,7 @@ where
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: OP,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<RouteListing>,
|
||||
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
@@ -1225,7 +1161,7 @@ where
|
||||
match listing.mode() {
|
||||
SsrMode::OutOfOrder => {
|
||||
let s = render_app_to_stream_with_context(
|
||||
options.options(),
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
@@ -1237,24 +1173,9 @@ where
|
||||
leptos_router::Method::Patch => patch(s),
|
||||
}
|
||||
}
|
||||
SsrMode::PartiallyBlocked => {
|
||||
let s = render_app_to_stream_with_context_and_replace_blocks(
|
||||
options.options(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
true
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => get(s),
|
||||
leptos_router::Method::Post => post(s),
|
||||
leptos_router::Method::Put => put(s),
|
||||
leptos_router::Method::Delete => delete(s),
|
||||
leptos_router::Method::Patch => patch(s),
|
||||
}
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
let s = render_app_to_stream_in_order_with_context(
|
||||
options.options(),
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
@@ -1268,7 +1189,7 @@ where
|
||||
}
|
||||
SsrMode::Async => {
|
||||
let s = render_app_async_with_context(
|
||||
options.options(),
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
);
|
||||
@@ -1294,7 +1215,7 @@ where
|
||||
handler: H,
|
||||
) -> Self
|
||||
where
|
||||
H: axum::handler::Handler<T, OP, axum::body::Body>,
|
||||
H: axum::handler::Handler<T, (), axum::body::Body>,
|
||||
T: 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
@@ -1317,7 +1238,6 @@ where
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", fields(error), skip_all)]
|
||||
fn get_leptos_pool() -> LocalPoolHandle {
|
||||
static LOCAL_POOL: OnceCell<LocalPoolHandle> = OnceCell::new();
|
||||
|
||||
@@ -506,48 +506,6 @@ pub fn render_app_to_stream_with_context<IV>(
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
render_app_to_stream_with_context_and_replace_blocks(
|
||||
options,
|
||||
additional_context,
|
||||
app_fn,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a Viz [Handler](viz::Handler) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], serving an HTML stream of your application.
|
||||
///
|
||||
/// This version allows us to pass Viz State/Extractor or other infro from Viz or network
|
||||
/// layers above Leptos itself. To use it, you'll need to write your own handler function that provides
|
||||
/// the data to leptos in a closure.
|
||||
///
|
||||
/// `replace_blocks` additionally lets you specify whether `<Suspense/>` fragments that read
|
||||
/// from blocking resources should be retrojected into the HTML that's initially served, rather
|
||||
/// than dynamically inserting them with JavaScript on the client. This means you will have
|
||||
/// better support if JavaScript is not enabled, in exchange for a marginally slower response time.
|
||||
///
|
||||
/// Otherwise, this function is identical to [render_app_to_stream_with_context].
|
||||
///
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [RequestParts]
|
||||
/// - [ResponseOptions]
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
pub fn render_app_to_stream_with_context_and_replace_blocks<IV>(
|
||||
options: LeptosOptions,
|
||||
additional_context: impl Fn(leptos::Scope) + Clone + Send + 'static,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
replace_blocks: bool,
|
||||
) -> impl Fn(
|
||||
Request,
|
||||
) -> Pin<Box<dyn Future<Output = Result<Response>> + Send + 'static>>
|
||||
+ Clone
|
||||
+ Send
|
||||
+ 'static
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
move |req: Request| {
|
||||
Box::pin({
|
||||
@@ -590,11 +548,10 @@ where
|
||||
};
|
||||
|
||||
let (bundle, runtime, scope) =
|
||||
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
leptos::leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context(
|
||||
app,
|
||||
|cx| generate_head_metadata_separated(cx).1.into(),
|
||||
add_context,
|
||||
replace_blocks
|
||||
);
|
||||
|
||||
forward_stream(&options, res_options2, bundle, runtime, scope, tx).await;
|
||||
@@ -1134,22 +1091,6 @@ impl LeptosRoutes for Router {
|
||||
leptos_router::Method::Patch => router.patch(path, s),
|
||||
}
|
||||
}
|
||||
SsrMode::PartiallyBlocked => {
|
||||
let s =
|
||||
render_app_to_stream_with_context_and_replace_blocks(
|
||||
options.clone(),
|
||||
additional_context.clone(),
|
||||
app_fn.clone(),
|
||||
true,
|
||||
);
|
||||
match method {
|
||||
leptos_router::Method::Get => router.get(path, s),
|
||||
leptos_router::Method::Post => router.post(path, s),
|
||||
leptos_router::Method::Put => router.put(path, s),
|
||||
leptos_router::Method::Delete => router.delete(path, s),
|
||||
leptos_router::Method::Patch => router.patch(path, s),
|
||||
}
|
||||
}
|
||||
SsrMode::InOrder => {
|
||||
let s = render_app_to_stream_in_order_with_context(
|
||||
options.clone(),
|
||||
|
||||
@@ -152,6 +152,7 @@ pub use leptos_config::{self, get_configuration, LeptosOptions};
|
||||
pub mod ssr {
|
||||
pub use leptos_dom::{ssr::*, ssr_in_order::*};
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
pub use leptos_dom::{
|
||||
self, create_node_ref, debug_warn, document, error, ev,
|
||||
helpers::{
|
||||
@@ -160,6 +161,7 @@ pub use leptos_dom::{
|
||||
request_idle_callback, request_idle_callback_with_handle, set_interval,
|
||||
set_interval_with_handle, set_timeout, set_timeout_with_handle,
|
||||
window_event_listener, window_event_listener_untyped,
|
||||
window_event_listener_with_precast,
|
||||
},
|
||||
html, log, math, mount_to, mount_to_body, svg, warn, window, Attribute,
|
||||
Class, CollectView, Errors, Fragment, HtmlElement, IntoAttribute,
|
||||
|
||||
@@ -56,17 +56,16 @@ use std::rc::Rc;
|
||||
tracing::instrument(level = "info", skip_all)
|
||||
)]
|
||||
#[component(transparent)]
|
||||
pub fn Suspense<F, E, V>(
|
||||
pub fn Suspense<F, E>(
|
||||
cx: Scope,
|
||||
/// Returns a fallback UI that will be shown while `async` [Resources](leptos_reactive::Resource) are still loading.
|
||||
fallback: F,
|
||||
/// Children will be displayed once all `async` [Resources](leptos_reactive::Resource) have resolved.
|
||||
children: Box<dyn Fn(Scope) -> V>,
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
F: Fn() -> E + 'static,
|
||||
E: IntoView,
|
||||
V: IntoView + 'static,
|
||||
{
|
||||
let context = SuspenseContext::new(cx);
|
||||
|
||||
@@ -75,6 +74,7 @@ where
|
||||
|
||||
let orig_child = Rc::new(children);
|
||||
|
||||
let before_me = HydrationCtx::peek();
|
||||
let current_id = HydrationCtx::next_component();
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
let prev_disposer = Rc::new(RefCell::new(None::<ScopeDisposer>));
|
||||
@@ -161,7 +161,7 @@ where
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
HydrationCtx::continue_from(current_id.clone());
|
||||
HydrationCtx::continue_from(before_me);
|
||||
|
||||
leptos_dom::View::Suspense(current_id, core_component)
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ where
|
||||
F: Fn() -> E + 'static,
|
||||
E: IntoView,
|
||||
{
|
||||
let prev_children = Rc::new(RefCell::new(None::<View>));
|
||||
let prev_children = Rc::new(RefCell::new(None::<Vec<View>>));
|
||||
|
||||
let first_run = Rc::new(std::cell::Cell::new(true));
|
||||
let child_runs = Cell::new(0);
|
||||
@@ -112,13 +112,13 @@ where
|
||||
}
|
||||
})
|
||||
.children(Box::new(move |cx| {
|
||||
let frag = children(cx).into_view(cx);
|
||||
let frag = children(cx);
|
||||
|
||||
let suspense_context = use_context::<SuspenseContext>(cx)
|
||||
.expect("there to be a SuspenseContext");
|
||||
|
||||
if cfg!(feature = "hydrate") || !first_run.get() {
|
||||
*prev_children.borrow_mut() = Some(frag.clone());
|
||||
*prev_children.borrow_mut() = Some(frag.nodes.clone());
|
||||
}
|
||||
if is_first_run(&first_run, &suspense_context) {
|
||||
let has_local_only = suspense_context.has_local_only()
|
||||
|
||||
@@ -332,8 +332,32 @@ impl IntervalHandle {
|
||||
any(debug_assertions, features = "ssr"),
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
|
||||
_ = set_interval_with_handle(cb, duration);
|
||||
#[deprecated = "use set_interval_with_handle() instead. In the future, \
|
||||
set_interval() will no longer return a handle, for consistency \
|
||||
with other timer helper functions."]
|
||||
pub fn set_interval(
|
||||
cb: impl Fn() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<IntervalHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
leptos_reactive::SpecialNonReactiveZone::enter();
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
leptos_reactive::SpecialNonReactiveZone::exit();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn Fn()>).into_js_value();
|
||||
let handle = window()
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
)?;
|
||||
Ok(IntervalHandle(handle))
|
||||
}
|
||||
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls,
|
||||
@@ -378,6 +402,24 @@ pub fn set_interval_with_handle(
|
||||
si(Box::new(cb), duration)
|
||||
}
|
||||
|
||||
/// Adds an event listener to the `Window`.
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, features = "ssr"),
|
||||
instrument(level = "trace", skip_all, fields(event_name = %event_name))
|
||||
)]
|
||||
#[inline(always)]
|
||||
#[deprecated = "In the next release, `window_event_listener` will become \
|
||||
typed. You can switch now to `window_event_listener_untyped` \
|
||||
for the current behavior or use \
|
||||
`window_event_listener_with_precast`, which will become the \
|
||||
new`window_event_listener`."]
|
||||
pub fn window_event_listener(
|
||||
event_name: &str,
|
||||
cb: impl Fn(web_sys::Event) + 'static,
|
||||
) {
|
||||
window_event_listener_untyped(event_name, cb)
|
||||
}
|
||||
|
||||
/// Adds an event listener to the `Window`, typed as a generic `Event`.
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
@@ -414,21 +456,8 @@ pub fn window_event_listener_untyped(
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a window event listener from a typed event.
|
||||
/// ```
|
||||
/// use leptos::{leptos_dom::helpers::window_event_listener, *};
|
||||
///
|
||||
/// #[component]
|
||||
/// fn App(cx: Scope) -> impl IntoView {
|
||||
/// window_event_listener(ev::keypress, |ev| {
|
||||
/// // ev is typed as KeyboardEvent automatically,
|
||||
/// // so .code() can be called
|
||||
/// let code = ev.code();
|
||||
/// log!("code = {code:?}");
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn window_event_listener<E: ev::EventDescriptor + 'static>(
|
||||
/// Creates a window event listener where the event in the callback is already appropriately cast.
|
||||
pub fn window_event_listener_with_precast<E: ev::EventDescriptor + 'static>(
|
||||
event: E,
|
||||
cb: impl Fn(E::EventType) + 'static,
|
||||
) where
|
||||
|
||||
@@ -382,6 +382,26 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
#[deprecated = "Use HtmlElement::from_chunks() instead."]
|
||||
pub fn from_html(
|
||||
cx: Scope,
|
||||
element: El,
|
||||
html: impl Into<Cow<'static, str>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
attrs: smallvec![],
|
||||
children: ElementChildren::Chunks(vec![StringOrView::String(
|
||||
html.into(),
|
||||
)]),
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
view_marker: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
pub fn from_chunks(
|
||||
|
||||
@@ -79,6 +79,12 @@ pub fn create_node_ref<T: ElementDescriptor + 'static>(
|
||||
}
|
||||
|
||||
impl<T: ElementDescriptor + 'static> NodeRef<T> {
|
||||
/// Creates an empty reference.
|
||||
#[deprecated = "Use `create_node_ref` instead of `NodeRef::new()`."]
|
||||
pub fn new(cx: Scope) -> Self {
|
||||
Self(create_rw_signal(cx, None))
|
||||
}
|
||||
|
||||
/// Gets the element that is currently stored in the reference.
|
||||
///
|
||||
/// This tracks reactively, so that node references can be used in effects.
|
||||
|
||||
@@ -146,44 +146,6 @@ pub fn render_to_stream_with_prefix_undisposed_with_context(
|
||||
view: impl FnOnce(Scope) -> View + 'static,
|
||||
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
|
||||
additional_context: impl FnOnce(Scope) + 'static,
|
||||
) -> (impl Stream<Item = String>, RuntimeId, ScopeId) {
|
||||
render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
view,
|
||||
prefix,
|
||||
additional_context,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Renders a function to a stream of HTML strings and returns the [Scope] and [RuntimeId] that were created, so
|
||||
/// they can be disposed when appropriate. After the `view` runs, the `prefix` will run with
|
||||
/// the same scope. This can be used to generate additional HTML that has access to the same `Scope`.
|
||||
///
|
||||
/// If `replace_blocks` is true, this will wait for any fragments with blocking resources and
|
||||
/// actually replace them in the initial HTML. This is slower to render (as it requires walking
|
||||
/// back over the HTML for string replacement) but has the advantage of never including those fallbacks
|
||||
/// in the HTML.
|
||||
///
|
||||
/// This renders:
|
||||
/// 1) the prefix
|
||||
/// 2) the application shell
|
||||
/// a) HTML for everything that is not under a `<Suspense/>`,
|
||||
/// b) the `fallback` for any `<Suspense/>` component that is not already resolved, and
|
||||
/// c) JavaScript necessary to receive streaming [Resource](leptos_reactive::Resource) data.
|
||||
/// 3) streaming [Resource](leptos_reactive::Resource) data. Resources begin loading on the
|
||||
/// server and are sent down to the browser to resolve. On the browser, if the app sees that
|
||||
/// it is waiting for a resource to resolve from the server, it doesn't run it initially.
|
||||
/// 4) HTML fragments to replace each `<Suspense/>` fallback with its actual data as the resources
|
||||
/// read under that `<Suspense/>` resolve.
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "info", skip_all,)
|
||||
)]
|
||||
pub fn render_to_stream_with_prefix_undisposed_with_context_and_block_replacement(
|
||||
view: impl FnOnce(Scope) -> View + 'static,
|
||||
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
|
||||
additional_context: impl FnOnce(Scope) + 'static,
|
||||
replace_blocks: bool,
|
||||
) -> (impl Stream<Item = String>, RuntimeId, ScopeId) {
|
||||
HydrationCtx::reset_id();
|
||||
|
||||
@@ -215,7 +177,7 @@ pub fn render_to_stream_with_prefix_undisposed_with_context_and_block_replacemen
|
||||
});
|
||||
let cx = Scope { runtime, id: scope };
|
||||
|
||||
let mut blocking_fragments = FuturesUnordered::new();
|
||||
let blocking_fragments = FuturesUnordered::new();
|
||||
let fragments = FuturesUnordered::new();
|
||||
|
||||
for (fragment_id, data) in pending_fragments {
|
||||
@@ -236,46 +198,24 @@ pub fn render_to_stream_with_prefix_undisposed_with_context_and_block_replacemen
|
||||
|
||||
// HTML for the view function and script to store resources
|
||||
let stream = futures::stream::once(async move {
|
||||
let resolvers = format!(
|
||||
"<script>__LEPTOS_PENDING_RESOURCES = \
|
||||
{pending_resources};__LEPTOS_RESOLVED_RESOURCES = new \
|
||||
Map();__LEPTOS_RESOURCE_RESOLVERS = new Map();</script>"
|
||||
);
|
||||
|
||||
if replace_blocks {
|
||||
let mut blocks = Vec::with_capacity(blocking_fragments.len());
|
||||
while let Some((blocked_id, blocked_fragment)) =
|
||||
blocking_fragments.next().await
|
||||
{
|
||||
blocks.push((blocked_id, blocked_fragment));
|
||||
}
|
||||
|
||||
let prefix = prefix(cx);
|
||||
|
||||
let mut shell = shell;
|
||||
|
||||
for (blocked_id, blocked_fragment) in blocks {
|
||||
let open = format!("<!--suspense-open-{blocked_id}-->");
|
||||
let close = format!("<!--suspense-close-{blocked_id}-->");
|
||||
let (first, rest) = shell.split_once(&open).unwrap_or_default();
|
||||
let (_fallback, rest) =
|
||||
rest.split_once(&close).unwrap_or_default();
|
||||
|
||||
shell = format!("{first}{blocked_fragment}{rest}").into();
|
||||
}
|
||||
|
||||
format!("{prefix}{shell}{resolvers}")
|
||||
} else {
|
||||
let mut blocking = String::new();
|
||||
let mut blocking_fragments =
|
||||
fragments_to_chunks(blocking_fragments);
|
||||
|
||||
while let Some(fragment) = blocking_fragments.next().await {
|
||||
blocking.push_str(&fragment);
|
||||
}
|
||||
let prefix = prefix(cx);
|
||||
format!("{prefix}{shell}{resolvers}{blocking}")
|
||||
let mut blocking = String::new();
|
||||
let mut blocking_fragments = fragments_to_chunks(blocking_fragments);
|
||||
while let Some(fragment) = blocking_fragments.next().await {
|
||||
blocking.push_str(&fragment);
|
||||
}
|
||||
let prefix = prefix(cx);
|
||||
format!(
|
||||
r#"
|
||||
{prefix}
|
||||
{shell}
|
||||
<script>
|
||||
__LEPTOS_PENDING_RESOURCES = {pending_resources};
|
||||
__LEPTOS_RESOLVED_RESOURCES = new Map();
|
||||
__LEPTOS_RESOURCE_RESOLVERS = new Map();
|
||||
</script>
|
||||
{blocking}
|
||||
"#
|
||||
)
|
||||
})
|
||||
// TODO these should be combined again in a way that chains them appropriately
|
||||
// such that individual resources can resolve before all fragments are done
|
||||
@@ -289,7 +229,6 @@ pub fn render_to_stream_with_prefix_undisposed_with_context_and_block_replacemen
|
||||
|
||||
(stream, runtime, scope)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
|
||||
@@ -301,12 +301,10 @@ fn root_element_to_tokens_ssr(
|
||||
let chunks = chunks.into_iter().map(|chunk| match chunk {
|
||||
SsrElementChunks::String { template, holes } => {
|
||||
if holes.is_empty() {
|
||||
let template = template.replace("\\{", "{").replace("\\}", "}");
|
||||
quote! {
|
||||
leptos::leptos_dom::html::StringOrView::String(#template.into())
|
||||
}
|
||||
} else {
|
||||
let template = template.replace("\\{", "{{").replace("\\}", "}}");
|
||||
quote! {
|
||||
leptos::leptos_dom::html::StringOrView::String(
|
||||
format!(
|
||||
@@ -492,8 +490,8 @@ fn element_to_tokens_ssr(
|
||||
};
|
||||
template.push_str(
|
||||
&value
|
||||
.replace('{', "\\{")
|
||||
.replace('}', "\\}"),
|
||||
.replace('{', "{{")
|
||||
.replace('}', "}}"),
|
||||
);
|
||||
} else {
|
||||
template.push_str("{}");
|
||||
|
||||
@@ -167,6 +167,18 @@ pub trait SignalUpdate<T> {
|
||||
#[track_caller]
|
||||
fn update(&self, f: impl FnOnce(&mut T));
|
||||
|
||||
/// Applies a function to the current value to mutate it in place
|
||||
/// and notifies subscribers that the signal has changed. Returns
|
||||
/// [`Some(O)`] if the signal is still valid, [`None`] otherwise.
|
||||
///
|
||||
/// **Note:** `update()` does not auto-memoize, i.e., it will notify subscribers
|
||||
/// even if the value has not actually changed.
|
||||
#[deprecated = "Please use `try_update` instead. This method will be \
|
||||
removed in a future version of this crate"]
|
||||
fn update_returning<O>(&self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
|
||||
self.try_update(f)
|
||||
}
|
||||
|
||||
/// Applies a function to the current value to mutate it in place
|
||||
/// and notifies subscribers that the signal has changed. Returns
|
||||
/// [`Some(O)`] if the signal is still valid, [`None`] otherwise.
|
||||
@@ -237,6 +249,19 @@ pub trait SignalUpdateUntracked<T> {
|
||||
#[track_caller]
|
||||
fn update_untracked(&self, f: impl FnOnce(&mut T));
|
||||
|
||||
/// Runs the provided closure with a mutable reference to the current
|
||||
/// value without notifying dependents and returns
|
||||
/// the value the closure returned.
|
||||
#[deprecated = "Please use `try_update_untracked` instead. This method \
|
||||
will be removed in a future version of `leptos`"]
|
||||
#[inline(always)]
|
||||
fn update_returning_untracked<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
self.try_update_untracked(f)
|
||||
}
|
||||
|
||||
/// Runs the provided closure with a mutable reference to the current
|
||||
/// value without notifying dependents and returns
|
||||
/// the value the closure returned.
|
||||
@@ -905,6 +930,27 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
|
||||
self.id.update_with_no_effect(self.runtime, f);
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "WriteSignal::update_returning_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update_returning_untracked<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
self.id.update_with_no_effect(self.runtime, f)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn try_update_untracked<O>(
|
||||
&self,
|
||||
@@ -1299,6 +1345,27 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
|
||||
self.id.update_with_no_effect(self.runtime, f);
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, features="ssr"),
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "RwSignal::update_returning_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[inline(always)]
|
||||
fn update_returning_untracked<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
self.id.update_with_no_effect(self.runtime, f)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(
|
||||
|
||||
@@ -68,41 +68,16 @@ use crate::{
|
||||
/// // setting name only causes name to log, not count
|
||||
/// set_name("Bob".into());
|
||||
/// ```
|
||||
pub fn create_slice<T, O, S>(
|
||||
pub fn create_slice<T, O>(
|
||||
cx: Scope,
|
||||
signal: RwSignal<T>,
|
||||
getter: impl Fn(&T) -> O + Clone + Copy + 'static,
|
||||
setter: impl Fn(&mut T, S) + Clone + Copy + 'static,
|
||||
) -> (Signal<O>, SignalSetter<S>)
|
||||
where
|
||||
O: PartialEq,
|
||||
{
|
||||
(
|
||||
create_read_slice(cx, signal, getter),
|
||||
create_write_slice(cx, signal, setter),
|
||||
)
|
||||
}
|
||||
|
||||
/// Takes a memoized, read-only slice of a signal. This is equivalent to the
|
||||
/// read-only half of [`create_slice`].
|
||||
pub fn create_read_slice<T, O>(
|
||||
cx: Scope,
|
||||
signal: RwSignal<T>,
|
||||
getter: impl Fn(&T) -> O + Clone + Copy + 'static,
|
||||
) -> Signal<O>
|
||||
where
|
||||
O: PartialEq,
|
||||
{
|
||||
create_memo(cx, move |_| signal.with(getter)).into()
|
||||
}
|
||||
|
||||
/// Creates a setter to access one slice of a signal. This is equivalent to the
|
||||
/// write-only half of [`create_slice`].
|
||||
pub fn create_write_slice<T, O>(
|
||||
cx: Scope,
|
||||
signal: RwSignal<T>,
|
||||
setter: impl Fn(&mut T, O) + Clone + Copy + 'static,
|
||||
) -> SignalSetter<O> {
|
||||
) -> (Signal<O>, SignalSetter<O>)
|
||||
where
|
||||
O: PartialEq,
|
||||
{
|
||||
let getter = create_memo(cx, move |_| signal.with(getter));
|
||||
let setter = move |value| signal.update(|x| setter(x, value));
|
||||
setter.mapped_signal_setter(cx)
|
||||
(getter.into(), setter.mapped_signal_setter(cx))
|
||||
}
|
||||
|
||||
@@ -56,8 +56,42 @@ impl<T> StoredValue<T> {
|
||||
/// }
|
||||
/// let data = store_value(cx, MyCloneableData { value: "a".into() });
|
||||
///
|
||||
/// // calling .get_value() clones and returns the value
|
||||
/// assert_eq!(data.get_value().value, "a");
|
||||
/// // calling .get() clones and returns the value
|
||||
/// assert_eq!(data.get().value, "a");
|
||||
/// // there's a short-hand getter form
|
||||
/// assert_eq!(data().value, "a");
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[deprecated = "Please use `get_value` instead, as this method does not \
|
||||
track the stored value. This method will also be removed \
|
||||
in a future version of `leptos`"]
|
||||
pub fn get(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.get_value()
|
||||
}
|
||||
|
||||
/// Returns a clone of the signals current value, subscribing the effect
|
||||
/// to this signal.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value stored in a [`Scope`] that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// pub struct MyCloneableData {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
/// let data = store_value(cx, MyCloneableData { value: "a".into() });
|
||||
///
|
||||
/// // calling .get() clones and returns the value
|
||||
/// assert_eq!(data.get().value, "a");
|
||||
/// // there's a short-hand getter form
|
||||
/// assert_eq!(data().value, "a");
|
||||
/// # });
|
||||
@@ -70,6 +104,18 @@ impl<T> StoredValue<T> {
|
||||
self.try_get_value().expect("could not get stored value")
|
||||
}
|
||||
|
||||
/// Same as [`StoredValue::get`] but will not panic by default.
|
||||
#[track_caller]
|
||||
#[deprecated = "Please use `try_get_value` instead, as this method does \
|
||||
not track the stored value. This method will also be \
|
||||
removed in a future version of `leptos`"]
|
||||
pub fn try_get(&self) -> Option<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.try_get_value()
|
||||
}
|
||||
|
||||
/// Same as [`StoredValue::get`] but will not panic by default.
|
||||
#[track_caller]
|
||||
pub fn try_get_value(&self) -> Option<T>
|
||||
@@ -79,6 +125,33 @@ impl<T> StoredValue<T> {
|
||||
self.try_with_value(T::clone)
|
||||
}
|
||||
|
||||
/// Applies a function to the current stored value.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a value stored in a [`Scope`] that has been disposed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String
|
||||
/// }
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
///
|
||||
/// // calling .with() to extract the value
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "a");
|
||||
/// });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[deprecated = "Please use `with_value` instead, as this method does not \
|
||||
track the stored value. This method will also be removed \
|
||||
in a future version of `leptos`"]
|
||||
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
|
||||
self.with_value(f)
|
||||
}
|
||||
|
||||
/// Applies a function to the current stored value.
|
||||
///
|
||||
/// # Panics
|
||||
@@ -94,8 +167,8 @@ impl<T> StoredValue<T> {
|
||||
/// }
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
///
|
||||
/// // calling .with_value() to extract the value
|
||||
/// assert_eq!(data.with_value(|data| data.value.clone()), "a");
|
||||
/// // calling .with() to extract the value
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "a");
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
@@ -105,6 +178,15 @@ impl<T> StoredValue<T> {
|
||||
self.try_with_value(f).expect("could not get stored value")
|
||||
}
|
||||
|
||||
/// Same as [`StoredValue::with`] but returns [`Some(O)]` only if
|
||||
/// the signal is still valid. [`None`] otherwise.
|
||||
#[deprecated = "Please use `try_with_value` instead, as this method does \
|
||||
not track the stored value. This method will also be \
|
||||
removed in a future version of `leptos`"]
|
||||
pub fn try_with<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
self.try_with_value(f)
|
||||
}
|
||||
|
||||
/// Same as [`StoredValue::with`] but returns [`Some(O)]` only if
|
||||
/// the signal is still valid. [`None`] otherwise.
|
||||
pub fn try_with_value<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
|
||||
@@ -132,8 +214,8 @@ impl<T> StoredValue<T> {
|
||||
/// pub value: String
|
||||
/// }
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// data.update_value(|data| data.value = "b".into());
|
||||
/// assert_eq!(data.with_value(|data| data.value.clone()), "b");
|
||||
/// data.update(|data| data.value = "b".into());
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "b");
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
@@ -146,12 +228,54 @@ impl<T> StoredValue<T> {
|
||||
/// }
|
||||
///
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// let updated = data.try_update_value(|data| {
|
||||
/// let updated = data.update_returning(|data| {
|
||||
/// data.value = "b".into();
|
||||
/// data.value.clone()
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(data.with_value(|data| data.value.clone()), "b");
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "b");
|
||||
/// assert_eq!(updated, Some(String::from("b")));
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[deprecated = "Please use `update_value` instead, as this method does not \
|
||||
track the stored value. This method will also be removed \
|
||||
in a future version of `leptos`"]
|
||||
pub fn update(&self, f: impl FnOnce(&mut T)) {
|
||||
self.update_value(f);
|
||||
}
|
||||
|
||||
/// Updates the stored value.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String
|
||||
/// }
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// data.update(|data| data.value = "b".into());
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "b");
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
///
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// let updated = data.update_returning(|data| {
|
||||
/// data.value = "b".into();
|
||||
/// data.value.clone()
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "b");
|
||||
/// assert_eq!(updated, Some(String::from("b")));
|
||||
/// # });
|
||||
/// ```
|
||||
@@ -161,6 +285,18 @@ impl<T> StoredValue<T> {
|
||||
.expect("could not set stored value");
|
||||
}
|
||||
|
||||
/// Updates the stored value.
|
||||
#[track_caller]
|
||||
#[deprecated = "Please use `try_update_value` instead, as this method does \
|
||||
not track the stored value. This method will also be \
|
||||
removed in a future version of `leptos`"]
|
||||
pub fn update_returning<U>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut T) -> U,
|
||||
) -> Option<U> {
|
||||
self.try_update_value(f)
|
||||
}
|
||||
|
||||
/// Same as [`Self::update`], but returns [`Some(O)`] if the
|
||||
/// signal is still valid, [`None`] otherwise.
|
||||
pub fn try_update_value<O>(self, f: impl FnOnce(&mut T) -> O) -> Option<O> {
|
||||
@@ -175,6 +311,29 @@ impl<T> StoredValue<T> {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Sets the stored value.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # create_scope(create_runtime(), |cx| {
|
||||
///
|
||||
/// pub struct MyUncloneableData {
|
||||
/// pub value: String
|
||||
/// }
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// data.set(MyUncloneableData { value: "b".into() });
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "b");
|
||||
/// });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[deprecated = "Please use `set_value` instead, as this method does not \
|
||||
track the stored value. This method will also be removed \
|
||||
in a future version of `leptos`"]
|
||||
pub fn set(&self, value: T) {
|
||||
self.set_value(value);
|
||||
}
|
||||
|
||||
/// Sets the stored value.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -186,8 +345,8 @@ impl<T> StoredValue<T> {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// data.set_value(MyUncloneableData { value: "b".into() });
|
||||
/// assert_eq!(data.with_value(|data| data.value.clone()), "b");
|
||||
/// data.set(MyUncloneableData { value: "b".into() });
|
||||
/// assert_eq!(data.with(|data| data.value.clone()), "b");
|
||||
/// # });
|
||||
/// ```
|
||||
#[track_caller]
|
||||
@@ -244,10 +403,10 @@ impl<T> StoredValue<T> {
|
||||
/// pub value: String,
|
||||
/// }
|
||||
///
|
||||
/// // ✅ you can move the `StoredValue` and access it with .with_value()
|
||||
/// // ✅ you can move the `StoredValue` and access it with .with()
|
||||
/// let data = store_value(cx, MyUncloneableData { value: "a".into() });
|
||||
/// let callback_a = move || data.with_value(|data| data.value == "a");
|
||||
/// let callback_b = move || data.with_value(|data| data.value == "b");
|
||||
/// let callback_a = move || data.with(|data| data.value == "a");
|
||||
/// let callback_b = move || data.with(|data| data.value == "b");
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
#[track_caller]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.3.0"
|
||||
version = "0.3.0-alpha"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.3.0"
|
||||
version = "0.3.0-alpha"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -28,7 +28,6 @@ js-sys = { version = "0.3" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
lru = { version = "0.10", optional = true }
|
||||
serde_json = "1.0.96"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{use_navigate, use_resolved_path, ToHref, Url};
|
||||
use leptos::{html::form, *};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{error::Error, rc::Rc};
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
@@ -241,7 +240,7 @@ pub fn ActionForm<I, O>(
|
||||
) -> impl IntoView
|
||||
where
|
||||
I: Clone + ServerFn + 'static,
|
||||
O: Clone + Serialize + DeserializeOwned + 'static,
|
||||
O: Clone + Serializable + 'static,
|
||||
{
|
||||
let action_url = if let Some(url) = action.url() {
|
||||
url
|
||||
@@ -269,6 +268,7 @@ where
|
||||
|
||||
let on_response = Rc::new(move |resp: &web_sys::Response| {
|
||||
let resp = resp.clone().expect("couldn't get Response");
|
||||
let status = resp.status();
|
||||
spawn_local(async move {
|
||||
let redirected = resp.redirected();
|
||||
|
||||
@@ -277,33 +277,23 @@ where
|
||||
resp.text().expect("couldn't get .text() from Response"),
|
||||
)
|
||||
.await;
|
||||
let status = resp.status();
|
||||
match body {
|
||||
Ok(json) => {
|
||||
let json = json
|
||||
.as_string()
|
||||
.expect("couldn't get String from JsString");
|
||||
if (500..=599).contains(&status) {
|
||||
match serde_json::from_str::<ServerFnError>(&json) {
|
||||
Ok(res) => {
|
||||
value.try_set(Some(Err(res)));
|
||||
if let Some(error) = error {
|
||||
error.try_set(None);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
value.try_set(Some(Err(
|
||||
ServerFnError::Deserialization(
|
||||
e.to_string(),
|
||||
),
|
||||
)));
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(e)));
|
||||
}
|
||||
}
|
||||
// 500 just returns text of error, not JSON
|
||||
if status == 500 {
|
||||
let err = ServerFnError::ServerError(
|
||||
json.as_string().unwrap_or_default(),
|
||||
);
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(err.clone())));
|
||||
}
|
||||
value.try_set(Some(Err(err)));
|
||||
} else {
|
||||
match serde_json::from_str::<O>(&json) {
|
||||
match O::de(
|
||||
&json.as_string().expect(
|
||||
"couldn't get String from JsString",
|
||||
),
|
||||
) {
|
||||
Ok(res) => {
|
||||
value.try_set(Some(Ok(res)));
|
||||
if let Some(error) = error {
|
||||
|
||||
@@ -135,14 +135,14 @@ impl History for BrowserIntegration {
|
||||
|
||||
/// The wrapper type that the [Router](crate::Router) uses to interact with a [History].
|
||||
/// This is automatically provided in the browser. For the server, it should be provided
|
||||
/// as a context. Be sure that it can survive conversion to a URL in the browser.
|
||||
/// as a context.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_router::*;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let integration = ServerIntegration {
|
||||
/// path: "http://leptos.rs/".to_string(),
|
||||
/// path: "insert/current/path/here".to_string(),
|
||||
/// };
|
||||
/// provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
/// # });
|
||||
@@ -167,24 +167,7 @@ impl History for RouterIntegrationContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic router integration for the server side.
|
||||
///
|
||||
/// This should match what the browser history will show.
|
||||
///
|
||||
/// Generally, this will already be provided if you are using the leptos
|
||||
/// server integrations.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_router::*;
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// let integration = ServerIntegration {
|
||||
/// // Swap out with your URL if integrating manually.
|
||||
/// path: "http://leptos.rs/".to_string(),
|
||||
/// };
|
||||
/// provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
/// # });
|
||||
/// ```
|
||||
/// A generic router integration for the server side. All its need is the current path.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServerIntegration {
|
||||
pub path: String,
|
||||
|
||||
@@ -8,14 +8,11 @@
|
||||
/// 2. **Out-of-order streaming**: Serve an HTML shell that includes `fallback` for any `Suspense`. Load data on the **server**, streaming it down to the client as it resolves, and streaming down HTML for `Suspense` nodes.
|
||||
/// - *Pros*: Combines the best of **synchronous** and **`async`**, with a very fast shell and resources that begin loading on the server.
|
||||
/// - *Cons*: Requires JS for suspended fragments to appear in correct order. Weaker meta tag support when it depends on data that's under suspense (has already streamed down `<head>`)
|
||||
/// 3. **Partially-blocked out-of-order streaming**: Using `create_blocking_resource` with out-of-order streaming still sends fallbacks and relies on JavaScript to fill them in with the fragments. Partially-blocked streaming does this replacement on the server, making for a slower response but requiring no JavaScript to show blocking resources.
|
||||
/// - *Pros*: Works better if JS is disabled.
|
||||
/// - *Cons*: Slower initial response because of additional string manipulation on server.
|
||||
/// 4. **In-order streaming**: Walk through the tree, returning HTML synchronously as in synchronous rendering and out-of-order streaming until you hit a `Suspense`. At that point, wait for all its data to load, then render it, then the rest of the tree.
|
||||
/// 3. **In-order streaming**: Walk through the tree, returning HTML synchronously as in synchronous rendering and out-of-order streaming until you hit a `Suspense`. At that point, wait for all its data to load, then render it, then the rest of the tree.
|
||||
/// - *Pros*: Does not require JS for HTML to appear in correct order.
|
||||
/// - *Cons*: Loads the shell more slowly than out-of-order streaming or synchronous rendering because it needs to pause at every `Suspense`. Cannot begin hydration until the entire page has loaded, so earlier pieces
|
||||
/// of the page will not be interactive until the suspended chunks have loaded.
|
||||
/// 5. **`async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep.
|
||||
/// 4. **`async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep.
|
||||
/// - *Pros*: Better handling for meta tags (because you know async data even before you render the `<head>`). Faster complete load than **synchronous** because async resources begin loading on server.
|
||||
/// - *Cons*: Slower load time/TTFB: you need to wait for all async resources to load before displaying anything on the client.
|
||||
///
|
||||
@@ -26,7 +23,6 @@
|
||||
pub enum SsrMode {
|
||||
#[default]
|
||||
OutOfOrder,
|
||||
PartiallyBlocked,
|
||||
InOrder,
|
||||
Async,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user