Skip to content

Commit

Permalink
Merge branch 'main' into pr/119
Browse files Browse the repository at this point in the history
  • Loading branch information
gbj committed Dec 28, 2022
2 parents 50ba796 + a68d276 commit 63f680f
Show file tree
Hide file tree
Showing 83 changed files with 1,467 additions and 1,568 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ blob.rs
Cargo.lock
**/*.rs.bk
.DS_Store
.leptos.kdl
29 changes: 28 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ members = [
# core
"leptos",
"leptos_dom",
"leptos_core",
"leptos_config",
"leptos_macro",
"leptos_reactive",
"leptos_server",
Expand Down Expand Up @@ -42,4 +44,29 @@ lto = true
opt-level = 'z'

[workspace.metadata.cargo-all-features]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
skip_feature_sets = [
[
"csr",
"ssr",
],
[
"csr",
"hydrate",
],
[
"ssr",
"hydrate",
],
[
"serde",
"serde-lite",
],
[
"serde-lite",
"miniserde",
],
[
"serde",
"miniserde",
],
]
48 changes: 35 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ 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 signal’s 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:
Expand All @@ -65,7 +76,18 @@ Here are some resources for learning more about Leptos:

## `nightly` Note

Most of the examples assume you’re using `nightly` Rust. If you’re on stable, note the following:
Most of the examples assume you’re 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 you’re on stable, note the following:

1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.0", features = ["stable"] }`
2. `nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
Expand Down Expand Up @@ -136,17 +158,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);

// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
```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) { ... }
```

- **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.
38 changes: 19 additions & 19 deletions benchmarks/src/todomvc/leptos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -227,10 +227,10 @@ pub fn TodoMVC(cx: Scope,todos: Todos) -> impl IntoView {
}

#[component]
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);
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;

let save = move |value: &str| {
let value = value.trim();
Expand Down
2 changes: 1 addition & 1 deletion docs/COMMON_BUGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_a) = create_signal(cx, false);
let (b, set_b) = create_signal(cx, false);

create_effect(cx, move |_| {
if a() > 5 {
Expand Down
2 changes: 1 addition & 1 deletion docs/book/project/ch02_getting_started/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"

[dependencies]
leptos = "0.0.18"
leptos = { path = "../../../../leptos" }
2 changes: 1 addition & 1 deletion docs/book/project/ch03_building_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"

[dependencies]
leptos = "0.0.18"
leptos = { path = "../../../../leptos" }
2 changes: 1 addition & 1 deletion docs/book/project/ch03_building_ui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fn main() {
mount_to_body(|cx| {
let name = "gbj";
let userid = 0;
let _input_element: Element;
let _input_element = NodeRef::new(cx);

view! {
cx,
Expand Down
2 changes: 1 addition & 1 deletion docs/book/project/ch04_reactivity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"

[dependencies]
leptos = "0.0.18"
leptos = { path = "../../../../leptos" }
2 changes: 1 addition & 1 deletion examples/counter-isomorphic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions examples/counter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
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/)
10 changes: 10 additions & 0 deletions examples/counters-stable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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/)
10 changes: 10 additions & 0 deletions examples/counters/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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/)
2 changes: 1 addition & 1 deletion examples/fetch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

10 changes: 10 additions & 0 deletions examples/fetch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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/)
54 changes: 35 additions & 19 deletions examples/fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::time::Duration;

use gloo_timers::future::TimeoutFuture;
use leptos::*;
use serde::{Deserialize, Serialize};

Expand All @@ -7,6 +10,10 @@ 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={}",
Expand All @@ -30,8 +37,9 @@ 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?"
Expand All @@ -43,25 +51,33 @@ pub fn fetch_example(cx: Scope) -> impl IntoView {
}
/>
</label>
<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),
})
{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>
</Transition>
</div>
</div>
}
}
8 changes: 8 additions & 0 deletions examples/gtk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Leptos in a GTK App

This example creates a basic GTK app that uses Leptos’s 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.
2 changes: 1 addition & 1 deletion examples/gtk/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(|cx| {
_ = create_scope(create_runtime(), |cx| {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

Expand Down
19 changes: 14 additions & 5 deletions examples/hackernews-axum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@
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 CRS bundle

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/)
## 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 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.

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.

```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!
Loading

0 comments on commit 63f680f

Please sign in to comment.