Revert "Merge branch 'main' into pr/119"

This reverts commit 63f680f37d, reversing
changes made to 50ba796f49.
This commit is contained in:
Greg Johnston 2022-12-28 15:06:46 -05:00
parent fd2a2bd5f4
commit 4b1fce4c9c
70 changed files with 1628 additions and 797 deletions

1
.gitignore vendored
View file

@ -6,4 +6,3 @@ blob.rs
Cargo.lock
**/*.rs.bk
.DS_Store
.leptos.kdl

View file

@ -43,29 +43,4 @@ lto = true
opt-level = 'z'
[workspace.metadata.cargo-all-features]
skip_feature_sets = [
[
"csr",
"ssr",
],
[
"csr",
"hydrate",
],
[
"ssr",
"hydrate",
],
[
"serde",
"serde-lite",
],
[
"serde-lite",
"miniserde",
],
[
"serde",
"miniserde",
],
]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View file

@ -52,17 +52,6 @@ Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained re
- **Fine-grained reactivity**: The entire framework is build from reactive primitives. This allows for extremely performant code with minimal overhead: when a reactive signals value changes, it can update a single text node, toggle a single class, or remove an element from the DOM without any other code running. (_So, no virtual DOM!_)
- **Declarative**: Tell Leptos how you want the page to look, and let the framework tell the browser how to do it.
## Getting Started
The best way to get started with a Leptos project right now is to use the [`cargo-leptos`](https://github.com/akesson/cargo-leptos) build tool and our [starter template](https://github.com/leptos-rs/start).
```bash
cargo install cargo-leptos
cargo leptos new --git https://github.com/leptos-rs/start
cd [your project name]
cargo leptos watch
```
## Learn more
Here are some resources for learning more about Leptos:
@ -74,18 +63,7 @@ Here are some resources for learning more about Leptos:
## `nightly` Note
Most of the examples assume youre using `nightly` Rust.
To set up your rustup toolchain using nightly and
add the ability to compile Rust to WebAssembly:
```
rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-unknown
```
If youre on stable, note the following:
Most of the examples assume youre using `nightly` Rust. If youre on stable, note the following:
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.1.0-alpha", features = ["stable"] }`
2. `nightly` enables the function call syntax for accessing and setting signals. If youre using `stable`,
@ -126,17 +104,17 @@ There are some practical differences that make a significant difference:
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrapper for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.

View file

@ -121,21 +121,21 @@ pub fn TodoMVC(cx: Scope,todos: Todos) -> impl IntoView {
set_mode(new_mode);
});
let add_todo = move |ev: web_sys::KeyboardEvent| {
let target = event_target::<HtmlInputElement>(&ev);
ev.stop_propagation();
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
if key_code == ENTER_KEY {
let title = event_target_value(&ev);
let title = title.trim();
if !title.is_empty() {
let new = Todo::new(cx, next_id, title.to_string());
set_todos.update(|t| t.add(new));
next_id += 1;
target.set_value("");
}
}
};
let add_todo = move |ev: web_sys::KeyboardEvent| {
let target = event_target::<HtmlInputElement>(&ev);
ev.stop_propagation();
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
if key_code == ENTER_KEY {
let title = event_target_value(&ev);
let title = title.trim();
if !title.is_empty() {
let new = Todo::new(cx, next_id, title.to_string());
set_todos.update(|t| t.add(new));
next_id += 1;
target.set_value("");
}
}
};
let filtered_todos = create_memo::<Vec<Todo>>(cx, move |_| {
todos.with(|todos| match mode.get() {
@ -227,10 +227,10 @@ pub fn TodoMVC(cx: Scope,todos: Todos) -> impl IntoView {
}
#[component]
pub fn Todo(cx: Scope, todo: Todo) -> Element {
let (editing, set_editing) = create_signal(cx, false);
let set_todos = use_context::<WriteSignal<Todos>>(cx).unwrap();
let input: Element;
pub fn Todo(cx: Scope, todo: Todo) -> impl IntoView {
let (editing, set_editing) = create_signal(cx, false);
let set_todos = use_context::<WriteSignal<Todos>>(cx).unwrap();
//let input = NodeRef::new(cx);
let save = move |value: &str| {
let value = value.trim();

View file

@ -10,7 +10,7 @@ This document is intended as a running list of common issues, with example code
```rust
let (a, set_a) = create_signal(cx, 0);
let (b, set_b) = create_signal(cx, false);
let (b, set_a) = create_signal(cx, false);
create_effect(cx, move |_| {
if a() > 5 {

View file

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }
leptos = "0.0.18"

View file

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }
leptos = "0.0.18"

View file

@ -4,7 +4,7 @@ fn main() {
mount_to_body(|cx| {
let name = "gbj";
let userid = 0;
let _input_element = NodeRef::new(cx);
let _input_element: Element;
view! {
cx,

View file

@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }
leptos = "0.0.18"

View file

@ -10,7 +10,7 @@ To run it as a server side app with hydration, first you should run
wasm-pack build --target=web --debug --no-default-features --features=hydrate
```
to generate the WebAssembly to provide hydration features for the server.
to generate the Webassembly to provide hydration features for the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash

View file

@ -3,5 +3,3 @@
This example creates a simple counter in a client side rendered app with Rust and WASM!
To run it, just issue the `trunk serve --open` command in the example root. This will build the app, run it, and open a new browser to serve it.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -1,10 +0,0 @@
# Leptos Counters Example on Rust Stable
This example showcases a basic Leptos app with many counters. It is a good example of how to setup a basic reactive app with signals and effects, and how to interact with browser events. Unlike the other counters example, it will compile on Rust stable, because it has the `stable` feature enabled.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -1,10 +0,0 @@
# Leptos Counters Example
This example showcases a basic Leptos app with many counters. It is a good example of how to set up a basic reactive app with signals and effects, and how to interact with browser events.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -11,7 +11,7 @@ serde = { version = "1", features = ["derive"] }
log = "0.4"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
gloo-timers = { version = "0.2", features = ["futures"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View file

@ -1,10 +0,0 @@
# Client Side Fetch
This example shows how to fetch data from the client in WebAssembly.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -1,6 +1,3 @@
use std::time::Duration;
use gloo_timers::future::TimeoutFuture;
use leptos::*;
use serde::{Deserialize, Serialize};
@ -10,10 +7,6 @@ pub struct Cat {
}
async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
// artificial delay
// the cat API is too fast to show the transition
TimeoutFuture::new(500).await;
if count > 0 {
let res = reqwasm::http::Request::get(&format!(
"https://api.thecatapi.com/v1/images/search?limit={}",
@ -37,9 +30,8 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
pub fn fetch_example(cx: Scope) -> impl IntoView {
let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
let (pending, set_pending) = create_signal(cx, false);
view! { cx,
view! { cx,
<div>
<label>
"How many cats would you like?"
@ -51,33 +43,25 @@ pub fn fetch_example(cx: Scope) -> impl IntoView {
}
/>
</label>
{move || pending().then(|| view! { cx, <p>"Loading more cats..."</p> })}
<div>
// <Transition/> holds the previous value while new async data is being loaded
// Switch the <Transition/> to <Suspense/> to fall back to "Loading..." every time
<Transition
fallback={"Loading (Suspense Fallback)...".to_string()}
set_pending
>
{move || {
cats.read().map(|data| match data {
Err(_) => view! { cx, <pre>"Error"</pre> },
Ok(cats) => view! { cx,
<div>{
cats.iter()
.map(|src| {
view! { cx,
<img src={src}/>
}
})
.collect::<Vec<_>>()
}</div>
},
})
}
<Transition fallback=move || view! { cx, <div>"Loading (Suspense Fallback)..."</div>}>
{move || {
cats.read().map(|data| match data {
Err(_) => view! { cx, <pre>"Error"</pre> }.into_view(cx),
Ok(cats) => view! { cx,
<div>{
cats.iter()
.map(|src| {
view! { cx,
<img src={src}/>
}
})
.collect::<Vec<_>>()
}</div>
}.into_view(cx),
})
}
</Transition>
</div>
}
</Transition>
</div>
}
}

View file

@ -1,8 +0,0 @@
# Leptos in a GTK App
This example creates a basic GTK app that uses Leptoss reactive primitives.
## Build and Run
Unlike the other examples, this has a variety of build prerequisites that are out of scope of this crate. More detail on that can be found [here](https://gtk-rs.org/gtk4-rs/stable/latest/book/installation.html). The example comes from [here](https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html) and should be
runnable with `cargo run` if you have the GTK prerequisites installed.

View file

@ -6,7 +6,7 @@ const APP_ID: &str = "dev.leptos.Counter";
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
fn main() {
_ = create_scope(create_runtime(), |cx| {
_ = create_scope(|cx| {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

View file

@ -3,27 +3,18 @@
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. This repo differs from the main Hacker News example by using Axum as it's server.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CRS bundle
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --debug --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
to generate the Webassembly to provide hydration features for the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View file

@ -0,0 +1,66 @@
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use axum::{
body::{boxed, Body, BoxBody},
http::{Request, Response, StatusCode, Uri},
};
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_handler(uri: Uri) -> Result<Response<BoxBody>, (StatusCode, String)> {
let res = get_static_file(uri.clone(), "/pkg").await?;
println!("FIRST URI{:?}", uri);
if res.status() == StatusCode::NOT_FOUND {
// try with `.html`
// TODO: handle if the Uri has query parameters
match format!("{}.html", uri).parse() {
Ok(uri_html) => get_static_file(uri_html, "/pkg").await,
Err(_) => Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string())),
}
} else {
Ok(res)
}
}
pub async fn get_static_file_handler(uri: Uri) -> Result<Response<BoxBody>, (StatusCode, String)> {
let res = get_static_file(uri.clone(), "/static").await?;
println!("FIRST URI{:?}", uri);
if res.status() == StatusCode::NOT_FOUND {
Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string()))
} else {
Ok(res)
}
}
async fn get_static_file(uri: Uri, base: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
let req = Request::builder().uri(&uri).body(Body::empty()).unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// When run normally, the root should be the crate root
println!("Base: {:#?}", base);
if base == "/static" {
match ServeDir::new("./static").oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
))
}
} else if base == "/pkg" {
match ServeDir::new("./pkg").oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)),
}
} else{
Err((StatusCode::NOT_FOUND, "Not Found".to_string()))
}
}
}
}

View file

@ -3,6 +3,7 @@ use leptos::{component, Scope, IntoView, provide_context, view};
use leptos_meta::*;
use leptos_router::*;
mod api;
pub mod handlers;
mod routes;
use routes::nav::*;
use routes::stories::*;

View file

@ -17,7 +17,7 @@ if #[cfg(feature = "ssr")] {
#[tokio::main]
async fn main() {
use leptos_hackernews_axum::*;
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let addr = SocketAddr::from(([127, 0, 0, 1], 3002));
log::debug!("serving at {addr}");

View file

@ -14,11 +14,9 @@ console_log = "0.2"
console_error_panic_hook = "0.1"
futures = "0.3"
cfg-if = "1"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos = { path = "../../leptos", default-features = false, features = ["serde"] }
leptos_meta = { path = "../../meta", default-features = false }
leptos_actix = { path = "../../integrations/actix", default-features = false, optional = true }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
simple_logger = "2"
@ -27,7 +25,9 @@ serde_json = "1"
gloo-net = { version = "0.2", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
# openssl = { version = "0.10", features = ["v110"] }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
tracing = "0.1"
[features]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]

View file

@ -1,29 +1,20 @@
# Leptos Hacker News Example
This example creates a basic clone of the Hacker News site. It showcases Leptoss ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. It uses Actix as its backend.
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CRS bundle
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
to generate the Webassembly to provide hydration features for the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View file

@ -6,7 +6,7 @@ pub fn Nav(cx: Scope) -> impl IntoView {
view! { cx,
<header class="header">
<nav class="inner">
<A href="/" class="home".to_string()>
<A href="/">
<strong>"HN"</strong>
</A>
<A href="/new">

View file

@ -33,14 +33,10 @@ pub fn Stories(cx: Scope) -> impl IntoView {
move |(page, story_type)| async move {
let path = format!("{}?page={}", category(&story_type), page);
api::fetch_api::<Vec<api::Story>>(cx, &api::story(&path)).await
api::fetch_api::<Vec<api::Story>>(cx, &api::story(&path)).await
},
);
let (pending, set_pending) = create_signal(cx, false);
let (pending, set_pending) = create_signal(cx, false);
let hide_more_link =
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
let hide_more_link =
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;

View file

@ -1,7 +1,6 @@
use crate::api;
use leptos::*;
use leptos_meta::*;
use leptos_meta::*;
use leptos_router::*;
#[component]

View file

@ -1,17 +0,0 @@
# Parent Child Example
This example highlights four different ways that child components can communicate with their parent:
1. <ButtonA/>: passing a WriteSignal as one of the child component props,
for the child component to write into and the parent to read
2. <ButtonB/>: passing a closure as one of the child component props, for
the child component to call
3. <ButtonC/>: adding a simple event listener on the child component itself
4. <ButtonD/>: providing a context that is used in the component (rather than prop drilling)
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -7,7 +7,7 @@ edition = "2021"
console_log = "0.2"
log = "0.4"
leptos = { path = "../../leptos" }
leptos_router = { path = "../../router", features = ["csr"] }
leptos_router = { path = "../../router", features=["csr"] }
serde = { version = "1", features = ["derive"] }
futures = "0.3"
console_error_panic_hook = "0.1.7"

View file

@ -1,11 +1,8 @@
# Leptos Router Example
This example demonstrates how Leptoss router for client side routing.
## Build and Run it
This example demonstrates how Leptos' router works
## Run it
```bash
trunk serve --open
```
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -144,7 +144,7 @@ pub fn Settings(cx: Scope) -> impl IntoView {
<fieldset>
<legend>"Name"</legend>
<input type="text" name="first_name" placeholder="First"/>
<input type="text" name="last_name" placeholder="Last"/>
<input type="text" name="first_name" placeholder="Last"/>
</fieldset>
<pre>"This page is just a placeholder."</pre>
</form>

View file

@ -59,4 +59,66 @@ denylist = [
"sqlx",
"leptos_axum",
]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package]
name = "todo-app-sqlite-axum"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.66"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../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 }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
serde_json = "1.0.89"
gloo-net = { version = "0.2.5", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6.1", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
tokio = { version = "1.22.0", features = ["full"], optional = true }
http = { version = "0.2.8" }
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:sqlx",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"leptos_axum",
]
[package.metadata.cargo-all-features]
denylist = [
"axum",
"tower",
"tower-http",
"tokio",
"sqlx",
"leptos_axum",
]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View file

@ -1,3 +1,4 @@
CREATE TABLE IF NOT EXISTS todos
(
id INTEGER NOT NULL PRIMARY KEY,

View file

@ -24,10 +24,10 @@ if #[cfg(feature = "ssr")] {
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
let mut conn = db().await.expect("couldn't connect to DB");
sqlx::migrate!()
/* sqlx::migrate!()
.run(&mut conn)
.await
.expect("could not run SQLx migrations");
.expect("could not run SQLx migrations"); */
crate::todo::register_server_functions();

View file

@ -14,9 +14,9 @@ cfg_if! {
}
pub fn register_server_functions() {
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
GetTodos::register();
AddTodo::register();
DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]

View file

@ -25,16 +25,23 @@ leptos_router = { path = "../../router", default-features = false }
log = "0.4"
simple_logger = "2"
gloo = { git = "https://github.com/rustwasm/gloo" }
sqlx = { version = "0.6.2", features = [
sqlx = { version = "0.6", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
http = "0.2.8"
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = ["dep:actix-files", "dep:actix-web", "dep:sqlx", "leptos/ssr", "leptos_actix", "leptos_meta/ssr", "leptos_router/ssr"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:sqlx",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"]

View file

@ -1,6 +1,6 @@
# Leptos Todo App Sqlite
# Leptos Counter Isomorphic Example
This example creates a basic todo app with an Actix backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server
This example demonstrates how to use a server functions and multi-actions to build a simple todo app.
## Server Side Rendering With Hydration
@ -10,12 +10,11 @@ To run it as a server side app with hydration, first you should run
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
to generate the Webassembly to provide hydration features for the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
cargo run
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above

View file

@ -1,4 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
pub mod todo;
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
@ -6,7 +7,6 @@ cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
use crate::todo::*;
use leptos::*;
#[wasm_bindgen]
pub fn hydrate() {

View file

@ -1,5 +1,3 @@
use std::net::SocketAddr;
use cfg_if::cfg_if;
use leptos::*;
mod todo;

View file

@ -7,17 +7,15 @@ use serde::{Deserialize, Serialize};
cfg_if! {
if #[cfg(feature = "ssr")] {
use sqlx::{Connection, SqliteConnection};
use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode};
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
}
pub fn register_server_functions() {
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
GetTodos::register();
AddTodo::register();
DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
@ -41,16 +39,12 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
// this is just an example of how to access server context injected in the handlers
let req =
use_context::<actix_web::HttpRequest>(cx).expect("couldn't get HttpRequest from context");
println!("\ncalling server fn");
println!(" req.path = {:?}", req.path());
println!("req.path = {:?}", req.path());
use futures::TryStreamExt;
let mut conn = db().await?;
// fake API delay
std::thread::sleep(std::time::Duration::from_millis(350));
let mut todos = Vec::new();
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
@ -69,14 +63,16 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
let mut conn = db().await?;
// fake API delay
std::thread::sleep(std::time::Duration::from_millis(350));
std::thread::sleep(std::time::Duration::from_millis(1250));
sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)")
match sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)")
.bind(title)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
{
Ok(row) => Ok(()),
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
}
}
#[server(DeleteTodo, "/api")]

View file

@ -1,10 +0,0 @@
# Leptos TodoMVC
This is a Leptos implementation of the TodoMVC example common to many frameworks. This is a relatively-simple application but shows off features like interaction between components and state management.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -1,16 +1,12 @@
use axum::{
body::{Body, Bytes, Full, StreamBody},
body::{Body, Bytes, Full, StreamBody},
extract::Path,
http::{HeaderMap, HeaderValue, Request, StatusCode},
response::IntoResponse,
response::IntoResponse,
};
use futures::{Future, SinkExt, Stream, StreamExt};
use http::{method::Method, uri::Uri, version::Version, Response};
use hyper::body;
use http::{method::Method, uri::Uri, version::Version, Response};
use hyper::body;
use leptos::*;
use leptos_meta::MetaContext;
use leptos_router::*;
@ -49,52 +45,6 @@ impl ResponseOptions {
}
}
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: body.clone(),
}
}
use tokio::{sync::RwLock, task::spawn_blocking};
/// A struct to hold the parts of the incoming Request. Since `http::Request` isn't cloneable, we're forced
/// to construct this for Leptos to use in Axum
#[derive(Debug, Clone)]
pub struct RequestParts {
pub version: Version,
pub method: Method,
pub uri: Uri,
pub headers: HeaderMap<HeaderValue>,
pub body: Bytes,
}
/// This struct lets you define headers and override the status of the Response from an Element or a Server Function
/// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses.
#[derive(Debug, Clone, Default)]
pub struct ResponseParts {
pub status: Option<StatusCode>,
pub headers: HeaderMap,
}
/// Adding this Struct to your Scope inside of a Server Fn or Elements will allow you to override details of the Response
/// like StatusCode and add Headers/Cookies. Because Elements and Server Fns are lower in the tree than the Response generation
/// code, it needs to be wrapped in an `Arc<RwLock<>>` so that it can be surfaced
#[derive(Debug, Clone, Default)]
pub struct ResponseOptions(pub Arc<RwLock<ResponseParts>>);
impl ResponseOptions {
/// A less boilerplatey way to overwrite the default contents of `ResponseOptions` with a new `ResponseParts`
pub async fn overwrite(&self, parts: ResponseParts) {
let mut writable = self.0.write().await;
*writable = parts
}
}
pub async fn generate_request_parts(req: Request<Body>) -> RequestParts {
// provide request headers as context in server scope
let (parts, body) = req.into_parts();
@ -374,7 +324,6 @@ where IV: IntoView
let (mut tx, rx) = futures::channel::mpsc::channel(8);
spawn_blocking({
spawn_blocking({
let app_fn = app_fn.clone();
move || {

View file

@ -1,12 +0,0 @@
use rustc_version::{version, version_meta, Channel};
fn main() {
assert!(version().unwrap().major >= 1);
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=feature=\"stable\"")
}
_ => {}
}
}

View file

@ -27,12 +27,8 @@
//! the [examples](https://github.com/gbj/leptos/tree/main/examples):
//! - [`counter`](https://github.com/gbj/leptos/tree/main/examples/counter) is the classic
//! counter example, showing the basics of client-side rendering and reactive DOM updates
//! - [`counter-isomorphic`](https://github.com/gbj/leptos/tree/main/examples/counter-isomorphic) is the classic
//! counter example run on the server using an isomorphic function, showing the basics of client-side rendering and reactive DOM updates
//! - [`counters`](https://github.com/gbj/leptos/tree/main/examples/counters) introduces parent-child
//! - [`counters`](https://github.com/gbj/leptos/tree/main/examples/counter) introduces parent-child
//! communication via contexts, and the `<For/>` component for efficient keyed list updates.
//! - [`counters-stable`](https://github.com/gbj/leptos/tree/main/examples/counters-stable) introduces parent-child
//! communication via contexts, and the `<For/>` component for efficient keyed list updates. Unlike counters, this compiles in Rust stable.
//! - [`parent-child`](https://github.com/gbj/leptos/tree/main/examples/parent-child) shows four different
//! ways a parent component can communicate with a child, including passing a closure, context, and more
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) implements the classic to-do
@ -51,13 +47,6 @@
//! - [`hackernews`](https://github.com/gbj/leptos/tree/main/examples/hackernews) pulls everything together.
//! It integrates calls to a real external REST API, routing, server-side rendering and hydration to create
//! a fully-functional PEMPA that works as intended even before WASM has loaded and begun to run.
//! - [`hackernews-axum`](https://github.com/gbj/leptos/tree/main/examples/hackernews-axum) pulls everything together.
//! It integrates calls to a real external REST API, routing, server-side rendering and hydration to create
//! a fully-functional PEMPA that works as intended even before WASM has loaded and begun to run. This one uses Axum as it's backend.
//! - [`todo-app-sqlite`](https://github.com/gbj/leptos/tree/main/examples/todo-app-sqlite) is a simple todo app, showcasing the use of
//! functions that run only on the server, but are called from client side function calls
//! - [`todo-app-sqlite-axum`](https://github.com/gbj/leptos/tree/main/examples/todo-app-sqlite-axum) is a simple todo app, showcasing the use of
//! functions that run only on the server, but are called from client side function calls. Now with Axum backend
//!
//! (The SPA examples can be run using `trunk serve`. For information about Trunk,
//! [see here]((https://trunkrs.dev/)).)
@ -88,10 +77,6 @@
//! from the server to the client.
//! - `miniserde` In SSR/hydrate mode, uses [miniserde](https://docs.rs/miniserde/latest/miniserde/) to serialize resources and send them
//! from the server to the client.
//! - `interning` (*Default*) When client-side rendering, Leptos uses [`wasm_bindgen::intern`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/fn.intern.html)
//! to reduce the cost of copying class names, attribute names, attribute values, and properties through JavaScript to the DOM. This feature
//! (included by default) makes DOM updates marginally faster and WASM binary size marginally larger. Disabling the feature makes binary sizes
//! marginally smaller at the cost of a small decrease in speed.
//!
//! **Important Note:** You must enable one of `csr`, `hydrate`, or `ssr` to tell Leptos
//! which mode your app is operating in.

View file

@ -1,12 +0,0 @@
use rustc_version::{version, version_meta, Channel};
fn main() {
assert!(version().unwrap().major >= 1);
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=feature=\"stable\"")
}
_ => {}
}
}

View file

@ -1,10 +0,0 @@
# Leptos View Tests
This is a collection of mostly internal view tests for Leptos. Feel free to look if curious to see a variety of ways you can build identical views!
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View file

@ -26,7 +26,7 @@ fn Tests(cx: Scope) -> Element {
view! {
cx,
<div>
//<div><SelfUpdatingEffect/></div>
<div><SelfUpdatingEffect/></div>
<div><BlockOrders/></div>
//<div><TemplateConsumer/></div>
</div>

View file

@ -1,12 +0,0 @@
use rustc_version::{version, version_meta, Channel};
fn main() {
assert!(version().unwrap().major >= 1);
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=feature=\"stable\"")
}
_ => {}
}
}

View file

@ -6,7 +6,6 @@ use syn::{
ItemFn, LitStr, Meta, MetaList, MetaNameValue, NestedMeta, Pat, PatIdent, Path, PathArguments,
ReturnType, Type, TypePath, Visibility,
};
use itertools::Itertools;
pub struct Model {
is_transparent: bool,
@ -70,24 +69,6 @@ impl Parse for Model {
);
}
let doc_comment = attrs.iter().filter_map(|attr| if attr.path.segments[0].ident == "doc" {
Some(attr.clone().tokens.into_iter().filter_map(|token| if let TokenTree::Literal(_) = token {
// remove quotes
let chars = token.to_string();
let mut chars = chars.chars();
chars.next();
chars.next_back();
Some(chars.as_str().to_string())
} else {
None
}).collect::<String>())
} else {
None
})
.intersperse_with(|| "\n".to_string())
.collect();
Ok(Self {
is_transparent: false,
docs,
@ -114,45 +95,6 @@ impl ToTokens for Model {
ret,
} = self;
let field_docs: HashMap<String, String> = {
let mut map = HashMap::new();
let mut pieces = doc_comment.split("# Props");
pieces.next();
let rest = pieces.next().unwrap_or_default();
let mut current_field_name = String::new();
let mut current_field_value = String::new();
for line in rest.split('\n') {
if let Some(line) = line.strip_prefix(" - ") {
let mut pieces = line.split("**");
pieces.next();
let field_name = pieces.next();
let field_value = pieces.next().unwrap_or_default();
let field_value = if let Some((_ty, desc)) = field_value.split_once('-') {
desc
} else {
field_value
};
if let Some(field_name) = field_name {
if !current_field_name.is_empty() {
map.insert(current_field_name.clone(), current_field_value.clone());
}
current_field_name = field_name.to_string();
current_field_value = String::new();
current_field_value.push_str(field_value);
} else {
current_field_value.push_str(field_value);
}
} else {
current_field_value.push_str(line);
}
}
if !current_field_name.is_empty() {
map.insert(current_field_name, current_field_value.clone());
}
map
};
let mut body = body.to_owned();
body.sig.ident = format_ident!("__{}", body.sig.ident);
@ -283,10 +225,6 @@ impl Prop {
if acc.intersection(&cur.1).next().is_some() {
abort!(typed.attrs[cur.0], "`#[prop]` options are repeated");
}
} else {
quote! { #vis #f }
}
});
acc.extend(cur.1);

View file

@ -7,6 +7,7 @@ extern crate proc_macro_error;
use proc_macro::{TokenStream, TokenTree};
use quote::ToTokens;
use server::server_macro_impl;
use syn::{parse_macro_input, DeriveInput};
use syn_rsx::{parse, NodeElement};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -29,6 +30,7 @@ mod params;
mod view;
use view::render_view;
mod component;
mod props;
mod server;
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
@ -177,15 +179,12 @@ mod server;
///
/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a
/// [NodeRef](leptos_reactive::NodeRef) to use later.
/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a
/// [NodeRef](leptos_reactive::NodeRef) to use later.
/// ```rust
/// # use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
/// let (value, set_value) = create_signal(cx, 0);
/// let my_input = NodeRef::new(cx);
/// let my_input = NodeRef::new(cx);
/// view! { cx, <input type="text" _ref=my_input/> }
/// // `my_input` now contains an `Element` that we can use anywhere
/// # ;
@ -414,54 +413,6 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
.into()
}
/// Declares that a function is a [server function](leptos_server). This means that
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
///
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
/// are enabled), it will instead make a network request to the server.
///
/// You can specify one, two, or three arguments to the server function:
/// 1. **Required**: A type name that will be used to identify and register the server function
/// (e.g., `MyServerFn`).
/// 2. *Optional*: A URL prefix at which the function will be mounted when its registered
/// (e.g., `"/api"`). Defaults to `"/"`.
/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
/// serialization) or `"Url"` (specifying that it should be use a URL-encoded form-data string).
/// Defaults to `"Url"`. If you want to use this server function to power an
/// [ActionForm](leptos_router::ActionForm) the encoding must be `"Url"`.
///
/// The server function itself can take any number of arguments, each of which should be serializable
/// and deserializable with `serde`. Optionally, its first argument can be a Leptos [Scope](leptos::Scope),
/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
/// server-side context into the server function.
///
/// ```
/// # use leptos::*; use serde::{Serialize, Deserialize};
/// # #[derive(Serialize, Deserialize)]
/// # pub struct Post { }
/// #[server(ReadPosts, "/api")]
/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
/// // do some work on the server to access the database
/// todo!()
/// }
/// ```
///
/// Note the following:
/// - **Server functions must be `async`.** Even if the work being done inside the function body
/// can run synchronously on the server, from the clients perspective it involves an asynchronous
/// function call.
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
/// inside the function body cant fail, the processes of serialization/deserialization and the
/// network call are fallible.
/// - **Return types must be [Serializable](leptos_reactive::Serializable).**
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
/// need to deserialize the result to return it to the client.
/// - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded`
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
/// - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function
/// can be a Leptos [Scope](leptos_reactive::Scope). This scope can be used to inject dependencies like the HTTP request
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
#[proc_macro_attribute]
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
match server_macro_impl(args, s.into()) {
@ -470,6 +421,15 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
}
}
#[proc_macro_derive(Props, attributes(builder))]
pub fn derive_prop(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
props::impl_derive_prop(&input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
// Derive Params trait for routing
#[proc_macro_derive(Params, attributes(params))]
pub fn params_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

1270
leptos_macro/src/props.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -702,34 +702,14 @@ fn component_to_tokens(cx: &Ident, node: &NodeElement) -> TokenStream {
}
};
if other_attrs.peek().is_none() {
quote_spanned! {
span => create_component(#cx, move || {
#initialize_children
#component_name(
#cx,
#component_props_name::builder()
#(#props)*
#children
.build(),
)
})
}
} else {
quote_spanned! {
span => create_component(#cx, move || {
#initialize_children
let #component_name = #component_name(
#cx,
#component_props_name::builder()
#(#props)*
#children
.build(),
);
#(#other_attrs);*;
#component_name
})
}
quote! {
#name(
#cx,
#component_props_name::builder()
#(#props)*
#children
.build(),
)
}
}

View file

@ -30,7 +30,6 @@ web-sys = { version = "0.3", features = [
"Window",
] }
cfg-if = "1.0.0"
rmp-serde = "1.1.1"
[dev-dependencies]
tokio-test = "0.4"
@ -46,34 +45,5 @@ serde = []
serde-lite = ["dep:serde-lite"]
miniserde = ["dep:miniserde"]
[build-dependencies]
rustc_version = "0.4"
[package.metadata.cargo-all-features]
denylist = ["stable"]
skip_feature_sets = [
[
"csr",
"ssr",
],
[
"csr",
"hydrate",
],
[
"ssr",
"hydrate",
],
[
"serde",
"serde-lite",
],
[
"serde-lite",
"miniserde",
],
[
"serde",
"miniserde",
],
]

View file

@ -1,12 +0,0 @@
use rustc_version::{version, version_meta, Channel};
fn main() {
assert!(version().unwrap().major >= 1);
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=feature=\"stable\"")
}
_ => {}
}
}

View file

@ -49,7 +49,7 @@ use std::fmt::Debug;
/// ```
pub fn create_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: 'static,
T: Debug + 'static,
{
cfg_if! {
if #[cfg(not(feature = "ssr"))] {
@ -90,7 +90,7 @@ where
/// # }).dispose();
pub fn create_isomorphic_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: 'static,
T: Debug + 'static,
{
let e = cx.runtime.create_effect(f);
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
@ -99,7 +99,7 @@ where
#[doc(hidden)]
pub fn create_render_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
where
T: 'static,
T: Debug + 'static,
{
create_effect(cx, f);
}

View file

@ -215,7 +215,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for Memo<T>
where
T: Clone,
T: Debug + Clone,
{
type Output = T;
@ -227,7 +227,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for Memo<T>
where
T: Clone,
T: Debug + Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
@ -237,7 +237,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for Memo<T>
where
T: Clone,
T: Debug + Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()

View file

@ -66,10 +66,9 @@ pub fn create_resource<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Serializable + 'static,
T: Debug + Serializable + 'static,
Fu: Future<Output = T> + 'static,
{
// can't check this on the server without running the future
// can't check this on the server without running the future
let initial_value = None;
@ -92,7 +91,7 @@ pub fn create_resource_with_initial_value<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: Serializable + 'static,
T: Debug + Serializable + 'static,
Fu: Future<Output = T> + 'static,
{
let resolved = initial_value.is_some();
@ -174,7 +173,7 @@ pub fn create_local_resource<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: 'static,
T: Debug + 'static,
Fu: Future<Output = T> + 'static,
{
let initial_value = None;
@ -196,7 +195,7 @@ pub fn create_local_resource_with_initial_value<S, T, Fu>(
) -> Resource<S, T>
where
S: PartialEq + Debug + Clone + 'static,
T: 'static,
T: Debug + 'static,
Fu: Future<Output = T> + 'static,
{
let resolved = initial_value.is_some();
@ -245,7 +244,7 @@ where
fn load_resource<S, T>(_cx: Scope, _id: ResourceId, r: Rc<ResourceState<S, T>>)
where
S: PartialEq + Debug + Clone + 'static,
T: 'static,
T: Debug + 'static,
{
r.load(false)
}
@ -254,7 +253,7 @@ where
fn load_resource<S, T>(cx: Scope, id: ResourceId, r: Rc<ResourceState<S, T>>)
where
S: PartialEq + Debug + Clone + 'static,
T: Serializable + 'static,
T: Debug + Serializable + 'static,
{
use wasm_bindgen::{JsCast, UnwrapThrowExt};
@ -328,7 +327,7 @@ where
impl<S, T> Resource<S, T>
where
S: Debug + Clone + 'static,
T: 'static,
T: Debug + 'static,
{
/// Clones and returns the current value of the resource ([Option::None] if the
/// resource is still pending). Also subscribes the running effect to this
@ -435,7 +434,7 @@ where
pub struct Resource<S, T>
where
S: Debug + 'static,
T: 'static,
T: Debug + 'static,
{
runtime: RuntimeId,
pub(crate) id: ResourceId,
@ -452,7 +451,7 @@ slotmap::new_key_type! {
impl<S, T> Clone for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Clone + 'static,
T: Debug + Clone + 'static,
{
fn clone(&self) -> Self {
Self {
@ -467,7 +466,7 @@ where
impl<S, T> Copy for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Clone + 'static,
T: Debug + Clone + 'static,
{
}
@ -475,7 +474,7 @@ where
impl<S, T> FnOnce<()> for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Clone + 'static,
T: Debug + Clone + 'static,
{
type Output = Option<T>;
@ -488,7 +487,7 @@ where
impl<S, T> FnMut<()> for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Clone + 'static,
T: Debug + Clone + 'static,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.read()
@ -499,7 +498,7 @@ where
impl<S, T> Fn<()> for Resource<S, T>
where
S: Debug + Clone + 'static,
T: Clone + 'static,
T: Debug + Clone + 'static,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.read()
@ -510,7 +509,7 @@ where
pub(crate) struct ResourceState<S, T>
where
S: 'static,
T: 'static,
T: Debug + 'static,
{
scope: Scope,
value: ReadSignal<Option<T>>,
@ -528,7 +527,7 @@ where
impl<S, T> ResourceState<S, T>
where
S: Debug + Clone + 'static,
T: 'static,
T: Debug + 'static,
{
pub fn read(&self) -> Option<T>
where
@ -647,7 +646,6 @@ where
});
Box::pin(async move {
rx.next().await.expect("failed while trying to resolve Resource serializer")
rx.next().await.expect("failed while trying to resolve Resource serializer")
})
}
}
@ -669,7 +667,7 @@ pub(crate) trait SerializableResource {
impl<S, T> SerializableResource for ResourceState<S, T>
where
S: Debug + Clone,
T: Serializable,
T: Debug + Serializable,
{
fn as_any(&self) -> &dyn Any {
self
@ -689,6 +687,9 @@ pub(crate) trait UnserializableResource {
}
impl<S, T> UnserializableResource for ResourceState<S, T>
where
S: Debug,
T: Debug,
{
fn as_any(&self) -> &dyn Any {
self

View file

@ -226,16 +226,7 @@ impl Debug for Runtime {
impl Runtime {
pub fn new() -> Self {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydration"))] {
Self::default()
} else {
Runtime {
shared_context: RefCell::new(Some(Default::default())),
..Self::default()
}
}
}
Self::default()
}
pub(crate) fn create_unserializable_resource<S, T>(
@ -244,7 +235,7 @@ impl Runtime {
) -> ResourceId
where
S: Debug + Clone + 'static,
T: 'static,
T: Debug + 'static,
{
self.resources
.borrow_mut()
@ -257,7 +248,7 @@ impl Runtime {
) -> ResourceId
where
S: Debug + Clone + 'static,
T: Serializable + 'static,
T: Debug + Serializable + 'static,
{
self.resources
.borrow_mut()
@ -271,7 +262,7 @@ impl Runtime {
) -> U
where
S: Debug + 'static,
T: 'static,
T: Debug + 'static,
{
let resources = self.resources.borrow();
let res = resources.get(id);

View file

@ -228,7 +228,7 @@ impl<T> Copy for ReadSignal<T> {}
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for ReadSignal<T>
where
T: Clone,
T: Debug + Clone,
{
type Output = T;
@ -240,7 +240,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for ReadSignal<T>
where
T: Clone,
T: Debug + Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
@ -250,7 +250,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for ReadSignal<T>
where
T: Clone,
T: Debug + Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()
@ -734,7 +734,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnOnce<()> for RwSignal<T>
where
T: Clone,
T: Debug + Clone,
{
type Output = T;
@ -746,7 +746,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> FnMut<()> for RwSignal<T>
where
T: Clone,
T: Debug + Clone,
{
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
self.get()
@ -756,7 +756,7 @@ where
#[cfg(not(feature = "stable"))]
impl<T> Fn<()> for RwSignal<T>
where
T: Clone,
T: Debug + Clone,
{
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
self.get()

View file

@ -21,7 +21,3 @@ default = ["csr"]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = ["leptos/ssr"]
stable = ["leptos/stable"]
[package.metadata.cargo-all-features]
denylist = ["stable"]

View file

@ -57,8 +57,7 @@ default = ["csr"]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = ["leptos/ssr", "dep:url", "dep:regex"]
stable = ["leptos/stable"]
[package.metadata.cargo-all-features]
# No need to test optional dependencies as they are enabled by the ssr feature
denylist = ["url", "regex", "stable"]
denylist = ["url", "regex"]

View file

@ -1,7 +1,5 @@
use crate::{use_navigate, use_resolved_path, TextProp};
use cfg_if::cfg_if;
use crate::{use_navigate, use_resolved_path, ToHref};
use leptos::*;
use leptos::typed_builder::*;
use std::{error::Error, rc::Rc};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
@ -41,10 +39,10 @@ pub fn Form<A>(
children: Box<dyn Fn(Scope) -> Fragment>,
) -> impl IntoView
where
A: TextProp + 'static,
A: ToHref + 'static,
{
let action_version = version;
let action = use_resolved_path(cx, move || action.to_value()());
let action = use_resolved_path(cx, move || action.to_href()());
let on_submit = move |ev: web_sys::SubmitEvent| {
if ev.default_prevented() {

View file

@ -6,31 +6,31 @@ use crate::{use_location, use_resolved_path, State};
/// Describes a value that is either a static or a reactive URL, i.e.,
/// a [String], a [&str], or a reactive `Fn() -> String`.
pub trait TextProp {
pub trait ToHref {
/// Converts the (static or reactive) URL into a function that can be called to
/// return the URL.
fn to_value(&self) -> Box<dyn Fn() -> String + '_>;
fn to_href(&self) -> Box<dyn Fn() -> String + '_>;
}
impl TextProp for &str {
fn to_value(&self) -> Box<dyn Fn() -> String> {
impl ToHref for &str {
fn to_href(&self) -> Box<dyn Fn() -> String> {
let s = self.to_string();
Box::new(move || s.clone())
}
}
impl TextProp for String {
fn to_value(&self) -> Box<dyn Fn() -> String> {
impl ToHref for String {
fn to_href(&self) -> Box<dyn Fn() -> String> {
let s = self.clone();
Box::new(move || s.clone())
}
}
impl<F> TextProp for F
impl<F> ToHref for F
where
F: Fn() -> String + 'static,
{
fn to_value(&self) -> Box<dyn Fn() -> String + '_> {
fn to_href(&self) -> Box<dyn Fn() -> String + '_> {
Box::new(self)
}
}
@ -92,7 +92,6 @@ where
prop:replace={replace}
aria-current=move || if is_active.get() { Some("page") } else { None }
class=move || class.as_ref().map(|class| class.get())
class=move || class.as_ref().map(|class| class.get())
>
{children(cx)}
</a>

View file

@ -1,13 +1,10 @@
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, rc::Rc};
use crate::use_route;
use leptos::*;
/// Displays the child route nested in a parent route, allowing you to control exactly where
/// that child route is displayed. Renders nothing if there is no nested child.
#[component]
pub fn Outlet(cx: Scope) -> impl IntoView {
let route = use_route(cx);

View file

@ -97,11 +97,6 @@ impl RouteContext {
self.inner.cx
}
/// Returns the URL path of the current route,
/// including param values in their places.
///
/// e.g., this will return `/article/0` rather than `/article/:id`.
/// For the opposite behavior, see [RouteContext::original_path].
/// Returns the URL path of the current route,
/// including param values in their places.
///

View file

@ -2,7 +2,6 @@ use cfg_if::cfg_if;
use std::{cell::RefCell, rc::Rc};
use leptos::*;
use leptos::typed_builder::*;
use thiserror::Error;
#[cfg(not(feature = "ssr"))]
@ -65,6 +64,7 @@ impl std::fmt::Debug for RouterContextInner {
f.debug_struct("RouterContextInner")
.field("location", &self.location)
.field("base", &self.base)
.field("history", &std::any::type_name_of_val(&self.history))
.field("cx", &self.cx)
.field("reference", &self.reference)
.field("set_reference", &self.set_reference)
@ -86,10 +86,7 @@ impl RouterContext {
let history = use_context::<RouterIntegrationContext>(cx)
.unwrap_or_else(|| RouterIntegrationContext(Rc::new(crate::BrowserIntegration {})));
} else {
let history = use_context::<RouterIntegrationContext>(cx).expect("You must call provide_context::<RouterIntegrationContext>(cx, ...) somewhere above the <Router/>.\n\n \
If you are using `leptos_actix` or `leptos_axum` and seeing this message, it is a bug: \n \
1. Please check to make sure you're on the latest versions of `leptos_actix` or `leptos_axum` and of `leptos_router`. \n
2. If you're on the latest versions, please open an issue at https://github.com/gbj/leptos/issues");
let history = use_context::<RouterIntegrationContext>(cx).expect("You must call provide_context::<RouterIntegrationContext>(cx, ...) somewhere above the <Router/>.");
}
};
@ -102,16 +99,14 @@ impl RouterContext {
let base = base.unwrap_or_default();
let base_path = resolve_path("", base, None);
if let Some(base_path) = &base_path {
if source.with(|s| s.value.is_empty()) {
history.navigate(&LocationChange {
value: base_path.to_string(),
replace: true,
scroll: false,
state: State(None),
});
}
}
if let Some(base_path) = &base_path && source.with(|s| s.value.is_empty()) {
history.navigate(&LocationChange {
value: base_path.to_string(),
replace: true,
scroll: false,
state: State(None)
});
}
// the current URL
let (reference, set_reference) = create_signal(cx, source.with(|s| s.value.clone()));

View file

@ -4,12 +4,6 @@ use std::{
ops::IndexMut,
rc::Rc,
};
use std::{
cell::{Cell, RefCell},
cmp::Reverse,
ops::IndexMut,
rc::Rc,
};
use leptos::*;
@ -21,8 +15,6 @@ use crate::{
RouteContext, RouterContext,
};
/// Contains route definitions and manages the actual routing process.
///
/// Contains route definitions and manages the actual routing process.
///
/// You should locate the `<Routes/>` component wherever on the page you want the routes to appear.
@ -92,22 +84,6 @@ pub fn Routes(
let prev_match = prev_matches.and_then(|p| p.get(i));
let next_match = next_matches.get(i).unwrap();
match (prev_routes, prev_match) {
(Some(prev), Some(prev_match))
if next_match.route.key == prev_match.route.key =>
{
let prev_one = { prev.borrow()[i].clone() };
if i >= next.borrow().len() {
next.borrow_mut().push(prev_one);
} else {
*(next.borrow_mut().index_mut(i)) = prev_one;
}
}
_ => {
equal = false;
if i == 0 {
root_equal.set(false);
}
match (prev_routes, prev_match) {
(Some(prev), Some(prev_match))
if next_match.route.key == prev_match.route.key =>
@ -125,31 +101,6 @@ pub fn Routes(
root_equal.set(false);
}
let disposer = cx.child_scope({
let next = next.clone();
let router = Rc::clone(&router.inner);
move |cx| {
let next = next.clone();
let next_ctx = RouteContext::new(
cx,
&RouterContext { inner: router },
{
let next = next.clone();
move || {
if let Some(route_states) =
use_context::<Memo<RouterState>>(cx)
{
route_states.with(|route_states| {
let routes = route_states.routes.borrow();
routes.get(i + 1).cloned()
})
} else {
next.borrow().get(i + 1).cloned()
}
}
},
move || matches.with(|m| m.get(i).cloned()),
);
let disposer = cx.child_scope({
let next = next.clone();
let router = Rc::clone(&router.inner);
@ -185,15 +136,6 @@ pub fn Routes(
}
}
});
if let Some(next_ctx) = next_ctx {
if next.borrow().len() > i + 1 {
next.borrow_mut()[i] = next_ctx;
} else {
next.borrow_mut().push(next_ctx);
}
}
}
});
if disposers.borrow().len() > i + 1 {
let mut disposers = disposers.borrow_mut();
@ -205,23 +147,7 @@ pub fn Routes(
}
}
}
if disposers.borrow().len() > i + 1 {
let mut disposers = disposers.borrow_mut();
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
old_route_disposer.dispose();
} else {
disposers.borrow_mut().push(disposer);
}
}
}
}
if disposers.borrow().len() > next_matches.len() {
let surplus_disposers = disposers.borrow_mut().split_off(next_matches.len() + 1);
for disposer in surplus_disposers {
disposer.dispose();
}
}
if disposers.borrow().len() > next_matches.len() {
let surplus_disposers = disposers.borrow_mut().split_off(next_matches.len() + 1);
for disposer in surplus_disposers {
@ -229,20 +155,6 @@ pub fn Routes(
}
}
if let Some(prev) = &prev {
if equal {
RouterState {
matches: next_matches.to_vec(),
routes: prev_routes.cloned().unwrap_or_default(),
root: prev.root.clone(),
}
} else {
let root = next.borrow().get(0).cloned();
RouterState {
matches: next_matches.to_vec(),
routes: Rc::new(RefCell::new(next.borrow().to_vec())),
root,
}
if let Some(prev) = &prev {
if equal {
RouterState {

View file

@ -107,54 +107,39 @@ where
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError>;
}
cfg_if::cfg_if! {
if #[cfg(not(feature = "stable"))] {
auto trait NotOption {}
impl<T> !NotOption for Option<T> {}
impl<T> IntoParam for T
where
T: FromStr + NotOption,
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
}
}
impl<T> IntoParam for Option<T>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + 'static,
{
fn into_param(value: Option<&str>, _name: &str) -> Result<Self, ParamsError> {
match value {
None => Ok(None),
Some(value) => match T::from_str(value) {
Ok(value) => Ok(Some(value)),
Err(e) => {
eprintln!("{}", e);
Err(ParamsError::Params(Rc::new(e)))
}
},
impl<T> IntoParam for Option<T>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + 'static,
{
fn into_param(value: Option<&str>, _name: &str) -> Result<Self, ParamsError> {
match value {
None => Ok(None),
Some(value) => match T::from_str(value) {
Ok(value) => Ok(Some(value)),
Err(e) => {
eprintln!("{}", e);
Err(ParamsError::Params(Rc::new(e)))
}
}
}
} else {
impl<T> IntoParam for T
where
T: FromStr,
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
}
},
}
}
}
auto trait NotOption {}
impl<T> !NotOption for Option<T> {}
impl<T> IntoParam for T
where
T: FromStr + NotOption,
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
}
}
/// Errors that can occur while parsing params using [Params](crate::Params).
#[derive(Error, Debug, Clone)]
pub enum ParamsError {

View file

@ -32,9 +32,6 @@
//! them with server-side rendering (with or without hydration), they just work,
//! whether JS/WASM have loaded or not.
//!
//! Note as well that client-side routing works with ordinary `<a>` tags, as well,
//! so you do not even need to use the `<A/>` component in most cases.
//!
//! ## Example
//!
//! ```rust
@ -139,9 +136,10 @@
//!
//! ```
#![cfg_attr(not(feature = "stable"), feature(auto_traits))]
#![cfg_attr(not(feature = "stable"), feature(negative_impls))]
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
#![feature(auto_traits)]
#![feature(let_chains)]
#![feature(negative_impls)]
#![feature(type_name_of_val)]
mod components;
mod history;

View file

@ -80,15 +80,13 @@ impl Matcher {
path.push_str(loc_segment);
}
if let Some(splat) = &self.splat {
if !splat.is_empty() {
let value = if len_diff > 0 {
loc_segments[self.len..].join("/")
} else {
"".into()
};
params.insert(splat.into(), value);
}
if let Some(splat) = &self.splat && !splat.is_empty() {
let value = if len_diff > 0 {
loc_segments[self.len..].join("/")
} else {
"".into()
};
params.insert(splat.into(), value);
}
Some(PathMatch { path, params })