Compare commits

..

4 Commits

Author SHA1 Message Date
Greg Johnston
1adddce0ed fix todomvc 2023-04-21 11:10:47 -04:00
Greg Johnston
c8e5d0518a fix todomvc 2023-04-21 11:10:43 -04:00
Greg Johnston
c334c576ab fix example exclusion sets 2023-04-21 11:04:52 -04:00
Greg Johnston
e14b970291 docs: compile error when using csr + ssr etc. (breaks <Suspense/> among others) 2023-04-20 17:00:32 -04:00
110 changed files with 572 additions and 3039 deletions

2
.gitignore vendored
View File

@@ -7,5 +7,3 @@ Cargo.lock
**/*.rs.bk
.DS_Store
.idea
.direnv
.envrc

View File

@@ -49,7 +49,6 @@ dependencies = [
{ name = "check", path = "examples/parent_child" },
{ name = "check", path = "examples/router" },
{ name = "check", path = "examples/session_auth_axum" },
{ name = "check", path = "examples/slots" },
{ name = "check", path = "examples/ssr_modes" },
{ name = "check", path = "examples/ssr_modes_axum" },
{ name = "check", path = "examples/tailwind" },
@@ -76,21 +75,8 @@ command = "cargo"
args = ["+nightly", "test-all-features"]
install_crate = "cargo-all-features"
[tasks.test-examples]
description = "Run all unit and web tests for examples"
cwd = "examples"
command = "cargo"
args = ["make", "test-unit-and-web"]
[tasks.verify-examples]
description = "Run all quality checks and tests for examples"
cwd = "examples"
command = "cargo"
args = ["make", "verify-flow"]
[env]
RUSTFLAGS = ""
LEPTOS_OUTPUT_NAME="ci" # allows examples to check/build without cargo-leptos
RUSTFLAGS=""
[env.github-actions]
RUSTFLAGS = "-D warnings"
RUSTFLAGS="-D warnings"

View File

@@ -28,52 +28,6 @@ let (a, set_a) = create_signal(cx, 0);
let b = move || a () > 5;
```
### Nested signal updates/reads triggering panic
Sometimes you have nested signals: for example, hash-map that can change over time, each of whose values can also change over time:
```rust
#[component]
pub fn App(cx: Scope) -> impl IntoView {
let resources = create_rw_signal(cx, HashMap::new());
let update = move |id: usize| {
resources.update(|resources| {
resources
.entry(id)
.or_insert_with(|| create_rw_signal(cx, 0))
.update(|amount| *amount += 1)
})
};
view! { cx,
<div>
<pre>{move || format!("{:#?}", resources.get().into_iter().map(|(id, resource)| (id, resource.get())).collect::<Vec<_>>())}</pre>
<button on:click=move |_| update(1)>"+"</button>
</div>
}
}
```
Clicking the button twice will cause a panic, because of the nested signal *read*. Calling the `update` function on `resources` immediately takes out a mutable borrow on `resources`, then updates the `resource` signal—which re-runs the effect that reads from the signals, which tries to immutably access `resources` and panics. It's the nested update here which causes a problem, because the inner update triggers and effect that tries to read both signals while the outer is still updating.
You can fix this fairly easily by using the [`Scope::batch()`](https://docs.rs/leptos/latest/leptos/struct.Scope.html#method.batch) method:
```rust
let update = move |id: usize| {
cx.batch(move || {
resources.update(|resources| {
resources
.entry(id)
.or_insert_with(|| create_rw_signal(cx, 0))
.update(|amount| *amount += 1)
})
});
};
```
This delays running any effects until after both updates are made, preventing the conflict entirely without requiring any other restructuring.
## Templates and the DOM
### `<input value=...>` doesn't update or stops updating

View File

@@ -19,7 +19,6 @@
- [Suspense](./async/11_suspense.md)
- [Transition](./async/12_transition.md)
- [Actions](./async/13_actions.md)
- [Interlude: Projecting Children](./interlude_projecting_children.md)
- [Responding to Changes with `create_effect`](./14_create_effect.md)
- [Global State Management](./15_global_state.md)
- [Router](./router/README.md)
@@ -31,21 +30,20 @@
- [Interlude: Styling](./interlude_styling.md)
- [Metadata]()
- [Server Side Rendering](./ssr/README.md)
- [`cargo-leptos`](./ssr/21_cargo_leptos.md)
- [The Life of a Page Load](./ssr/22_life_cycle.md)
- [Async Rendering and SSR “Modes”](./ssr/23_ssr_modes.md)
- [Hydration Footguns](./ssr/24_hydration_bugs.md)
- [Server Functions]()
- [`cargo-leptos`]()
- [The Life of a Page Load](./ssr/21_life_cycle.md)
- [Async Rendering and SSR “Modes”](./ssr/22_ssr_modes.md)
- [Hydration Footguns]()
- [Request/Response]()
- [Extractors]()
- [Axum]()
- [Actix]()
- [Headers]()
- [Cookies]()
- [Building Full-Stack Apps]()
- [Server Functions]()
- [Actions]()
- [Forms]()
- [`<ActionForm/>`s]()
- [Turning off WebAssembly]()
- [Advanced Reactivity]()
- [Appendix: Optimizing WASM Binary Size](./appendix_binary_size.md)
- [Appendix: Optimizing WASM Binary Size]()

View File

@@ -1,58 +0,0 @@
# Appendix: Optimizing WASM Binary Size
One of the primary downsides of deploying a Rust/WebAssembly frontend app is that splitting a WASM file into smaller chunks to be dynamically loaded is significantly more difficult than splitting a JavaScript bundle. There have been experiments like [`wasm-split`](https://emscripten.org/docs/optimizing/Module-Splitting.html) in the Emscripten ecosystem but at present theres no way to split and dynamically load a Rust/`wasm-bindgen` binary. This means that the whole WASM binary needs to be loaded before your app becomes interactive. Because the WASM format is designed for streaming compilation, WASM files are much faster to compile per kilobyte than JavaScript files. (For a deeper look, you can [read this great article from the Mozilla team](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/) on streaming WASM compilation.)
Still, its important to ship the smallest WASM binary to users that you can, as it will reduce their network usage and make your app interactive as quickly as possible.
So what are some practical steps?
## Things to Do
1. Make sure youre looking at a release build. (Debug builds are much, much larger.)
2. Add a release profile for WASM that optimizes for size, not speed.
For a `cargo-leptos` project, for example, you can add this to your `Cargo.toml`:
```toml
[profile.wasm-release]
inherits = "release"
opt-level = 'z'
lto = true
codegen-units = 1
# ....
[package.metadata.leptos]
# ....
lib-profile-release = "wasm-release"
```
This will hyper-optimize the WASM for your release build for size, while keeping your server build optimized for speed. (For a pure client-rendered app without server considerations, just use the `[profile.wasm-release]` block as your `[profile.release]`.)
3. Always serve compressed WASM in production. WASM tends to compress very well, typically shrinking to less than 50% its uncompressed size, and its trivial to enable compression for static files being served from Actix or Axum.
4. If youre using nightly Rust, you can rebuild the standard library with this same profile rather than the prebuilt standard library thats distributed with the `wasm32-unknown-unknown` target.
To do this, create a file in your project at `.cargo/config.toml`
```toml
[unstable]
build-std = ["std", "panic_abort", "core", "alloc"]
build-std-features = ["panic_immediate_abort"]
```
5. One of the sources of binary size in WASM binaries can be `serde` serialization/deserialization code. Leptos uses `serde` by default to serialize and deserialize resources created with `create_resource`. You might try experimenting with the `miniserde` and `serde-lite` features, which allow you to use those crates for serialization and deserialization instead; each only implements a subset of `serde`s functionality, but typically optimizes for size over speed.
## Things to Avoid
There are certain crates that tend to inflate binary sizes. For example, the `regex` crate with its default features adds about 500kb to a WASM binary (largely because it has to pull in Unicode table data!) In a size-conscious setting, you might consider avoiding regexes in general, or even dropping down and calling browser APIs to use the built-in regex engine instead. (This is what `leptos_router` does on the few occasions it needs a regular expression.)
In general, Rusts commitment to runtime performance is sometimes at odds with a commitment to a small binary. For example, Rust monomorphizes generic functions, meaning it creates a distinct copy of the function for each generic type its called with. This is significantly faster than dynamic dispatch, but increases binary size. Leptos tries to balance runtime performance with binary size considerations pretty carefully; but you might find that writing code that uses many generics tends to increase binary size. For example, if you have a generic component with a lot of code in its body and call it with four different types, remember that the compiler could include four copies of that same code. Refactoring to use a concrete inner function or helper can often maintain performance and ergonomics while reducing binary size.
## A Final Thought
Remember that in a server-rendered app, JS bundle size/WASM binary size affects only _one_ thing: time to interactivity on the first load. This is very important to a good user experience—nobody wants to click a button three times and have it do nothing because the interactive code is still loading—but it is not the only important measure.
Its especially worth remembering that streaming in a single WASM binary means all subsequent navigations are nearly instantaneous, depending only on any additional data loading. Precisely because your WASM binary is _not_ bundle split, navigating to a new route does not require loading additional JS/WASM, as it does in nearly every JavaScript framework. Is this copium? Maybe. Or maybe its just an honest trade-off between the two approaches!
Always take the opportunity to optimize the low-hanging fruit in your application. And always test your app under real circumstances with real user network speeds and devices before making any heroic efforts.

View File

@@ -1,7 +1,7 @@
# Working with `async`
So far weve only been working with synchronous users interfaces: You provide some input,
the app immediately processes it and updates the interface. This is great, but is a tiny
the app immediately process it and updates the interface. This is great, but is a tiny
subset of what web applications do. In particular, most web apps have to deal with some kind
of asynchronous data loading, usually loading something from an API.

View File

@@ -1,177 +0,0 @@
# Projecting Children
As you build components you may occasionally find yourself wanting to “project” children through multiple layers of components.
## The Problem
Consider the following:
```rust
pub fn LoggedIn<F, IV>(cx: Scope, fallback: F, children: ChildrenFn) -> impl IntoView
where
F: Fn(Scope) -> IV + 'static,
IV: IntoView,
{
view! { cx,
<Suspense
fallback=|| ()
>
<Show
// check whether user is verified
// by reading from the resource
when=move || todo!()
fallback=fallback
>
{children(cx)}
</Show>
</Suspense>
}
}
```
This is pretty straightforward: when the user is logged in, we want to show `children`. Until if the user is not logged in, we want to show `fallback`. And while were waiting to find out, we just render `()`, i.e., nothing.
In other words, we want to pass the children of `<WhenLoaded>/` _through_ the `<Suspense/>` component to become the children of the `<Show/>`. This is what I mean by “projection.”
This wont compile.
```
error[E0507]: cannot move out of `fallback`, a captured variable in an `Fn` closure
error[E0507]: cannot move out of `children`, a captured variable in an `Fn` closure
```
The problem here is that both `<Suspense/>` and `<Show/>` need to be able to construct their `children` multiple names. The first time you construct `<Suspense/>`s children, it would take ownership of `fallback` and `children` to move them into the invocation of `<Show/>`, but then they're not available for future `<Suspense/>` children construction.
## The Details
> Feel free to skip ahead to the solution.
If you want to really understand the issue here, it may help to look at the expanded `view` macro. Heres a cleaned-up version:
```rust
Suspense(
cx,
::leptos::component_props_builder(&Suspense)
.fallback(|| ())
.children({
// fallback and children are moved into this closure
Box::new(move |cx| {
{
// fallback and children captured here
leptos::Fragment::lazy(|| {
vec![
(Show(
cx,
::leptos::component_props_builder(&Show)
.when(|| true)
// but fallback is moved into Show here
.fallback(fallback)
// and children is moved into Show here
.children(children)
.build(),
)
.into_view(cx)),
]
})
}
})
})
.build(),
)
```
All components own their props; so the `<Show/>` in this case cant be called, because it only has captured references to `fallback` and `children`.
## Solution
However, both `<Suspense/>` and `<Show/>` take `ChildrenFn`, i.e., their `children` should implement the `Fn` type so they can be called multiple times with only an immutable reference. This means we dont need to own `children` or `fallback`; we just need to be able to pass `'static` references to them.
We can solve this problem by using the [`store_value`](https://docs.rs/leptos/latest/leptos/fn.store_value.html) primitive. This essentially stores a value in the reactive system, handing ownership off to the framework in exchange for a reference that is, like signals, `Copy` and `'static`, and which we can access or modify through certain methods.
In this case, its really simple:
```rust
pub fn LoggedIn<F, IV>(cx: Scope, fallback: F, children: ChildrenFn) -> impl IntoView
where
F: Fn(Scope) -> IV + 'static,
IV: IntoView,
{
let fallback = store_value(cx, fallback);
let children = store_value(cx, children);
view! { cx,
<Suspense
fallback=|| ()
>
<Show
when=|| todo!()
fallback=move |cx| fallback.with_value(|fallback| fallback(cx))
>
{children.with_value(|children| children(cx))}
</Show>
</Suspense>
}
}
```
At the top level, we store both `fallback` and `children` in the reactive scope owned by `LoggedIn`. Now we can simply move those references down through the other layers into the `<Show/>` component, and call them there.
## A Final Note
Note that this works because `<Show/>` and `<Suspense/>` only need an immutable reference to their children (which `.with_value` can give it), not ownership.
In other cases, you may need to project owned props through a function that takes `ChildrenFn` and therefore needs to be called more than once. In this case, you may find the `clone:` helper in the`view` macro helpful.
Consider this example
```rust
#[component]
pub fn App(cx: Scope) -> impl IntoView {
let name = "Alice".to_string();
view! { cx,
<Outer>
<Inner>
<Inmost name=name.clone()/>
</Inner>
</Outer>
}
}
#[component]
pub fn Outer(cx: Scope, children: ChildrenFn) -> impl IntoView {
children(cx)
}
#[component]
pub fn Inner(cx: Scope, children: ChildrenFn) -> impl IntoView {
children(cx)
}
#[component]
pub fn Inmost(cx: Scope, name: String) -> impl IntoView {
view! { cx,
<p>{name}</p>
}
}
```
Even with `name=name.clone()`, this gives the error
```
cannot move out of `name`, a captured variable in an `Fn` closure
```
Its captured through multiple levels of children that need to run more than once, and theres no obvious way to clone it _into_ the children.
In this case, the `clone:` syntax comes in handy. Calling `clone:name` will clone `name` _before_ moving it into `<Inner/>`s children, which solves our ownership issue.
```rust
view! { cx,
<Outer>
<Inner clone:name>
<Inmost name=name.clone()/>
</Inner>
</Outer>
}
```
These issues can be a little tricky to understand or debug, because of the opacity of the `view` macro. But in general, they can always be solved.

View File

@@ -109,4 +109,4 @@ pub fn MyComponent(cx: Scope) -> impl IntoView {
## Contributions Welcome
Leptos has no opinions on how you style your website or app, but were very happy to provide support to any tools youre trying to create to make it easier. If youre working on a CSS or styling approach that youd like to add to this list, please let us know!
Leptos no opinions on how you style your website or app, but were very happy to provide support to any tools youre trying to create to make it easier. If youre working on a CSS or styling approach that youd like to add to this list, please let us know!

View File

@@ -23,6 +23,8 @@ async fn fetch_results() {
// some async function to fetch our search results
}
#[component]
pub fn Search(cx: Scope) -> impl IntoView {
#[component]
pub fn FormExample(cx: Scope) -> impl IntoView {
// reactive access to URL query strings

View File

@@ -1,37 +0,0 @@
# Introducing `cargo-leptos`
So far, weve just been running code in the browser and using Trunk to coordinate the build process and run a local development process. If were going to add server-side rendering, well need to run our application code on the server as well. This means well need to build two separate binaries, one compiled to native code and running the server, the other compiled to WebAssembly (WASM) and running in the users browser. Additionally, the server needs to know how to serve this WASM version (and the JavaScript required to initialize it) to the browser.
This is not an insurmountable task but it adds some complication. For convenience and an easier developer experience, we built the [`cargo-leptos`](https://github.com/leptos-rs/cargo-leptos) build tool. `cargo-leptos` basically exists to coordinate the build process for your app, handling recompiling the server and client halves when you make changes, and adding some built-in support for things like Tailwind, SASS, and testing.
Getting started is pretty easy. Just run
```bash
cargo install cargo-leptos
```
And then to create a new project, you can run either
```bash
# for an Actix template
cargo leptos new --git leptos-rs/start
```
or
```bash
# for an Axum template
cargo leptos new --git leptos-rs/start-axum
```
Now `cd` into the directory youve created and run
```bash
cargo leptos watch
```
Once your app has compiled you can open up your browser to [`http://localhost:3000`](http://localhost:3000) to see it.
`cargo-leptos` has lots of additional features and built in tools. You can learn more [in its `README`](https://github.com/leptos-rs/leptos/blob/main/examples/hackernews/src/api.rs).
But what exactly is happening when you open our browser to `localhost:3000`? Well, read on to find out.

View File

@@ -1,148 +0,0 @@
# Hydration Bugs _(and how to avoid them)_
## A Thought Experiment
Lets try an experiment to test your intuitions. Open up an app youre server-rendering with `cargo-leptos`. (If youve just been using `trunk` so far to play with examples, go [clone a `cargo-leptos` template](./21_cargo_leptos.md) just for the sake of this exercise.)
Put a log somewhere in your root component. (I usually call mine `<App/>`, but anything will do.)
```rust
#[component]
pub fn App(cx: Scope) -> impl IntoView {
leptos::log!("where do I run?");
// ... whatever
}
```
And lets fire it up
```bash
cargo leptos watch
```
Where do you expect `where do I run?` to log?
- In the command line where youre running the server?
- In the browser console when you load the page?
- Neither?
- Both?
Try it out.
...
...
...
Okay, consider the spoiler alerted.
Youll notice of course that it logs in both places, assuming everything goes according to plan. In fact on the server it logs twice—first during the initial server startup, when Leptos renders your app once to extract the route tree, then a second time when you make a request. Each time you reload the page, `where do I run?` should log once on the server and once on the client.
If you think about the description in the last couple sections, hopefully this makes sense. Your application runs once on the server, where it builds up a tree of HTML which is sent to the client. During this initial render, `where do I run?` logs on the server.
Once the WASM binary has loaded in the browser, your application runs a second time, walking over the same user interface tree and adding interactivity.
> Does that sound like a waste? It is, in a sense. But reducing that waste is a genuinely hard problem. Its what some JS frameworks like Qwik are intended to solve, although its probably too early to tell whether its a net performance gain as opposed to other approaches.
## The Potential for Bugs
Okay, hopefully all of that made sense. But what does it have to do with the title of this chapter, which is “Hydration bugs (and how to avoid them)”?
Remember that the application needs to run on both the server and the client. This generates a few different sets of potential issues you need to know how to avoid.
### Mismatches between server and client code
One way to create a bug is by creating a mismatch between the HTML thats sent down by the server and whats rendered on the client. Its actually fairly hard to do this unintentionally, I think (at least judging by the bug reports I get from people.) But imagine I do something like this
```rust
#[component]
pub fn App(cx: Scope) -> impl IntoView {
let data = if cfg!(target_arch = "wasm32") {
vec![0, 1, 2]
} else {
vec![]
};
data.into_iter()
.map(|value| view! { cx, <span>{value}</span> })
.collect::<Vec<_>>()
}
```
In other words, if this is being compiled to WASM, it has three items; otherwise its empty.
When I load the page in the browser, I see nothing. If I open the console I see a bunch of warnings:
```
element with id 0-0-1 not found, ignoring it for hydration
element with id 0-0-2 not found, ignoring it for hydration
element with id 0-0-3 not found, ignoring it for hydration
component with id _0-0-4c not found, ignoring it for hydration
component with id _0-0-4o not found, ignoring it for hydration
```
The WASM version of your app, running in the browser, expects to find three items; but the HTML has none.
#### Solution
Its pretty rare that you do this intentionally, but it could happen from somehow running different logic on the server and in the browser. If youre seeing warnings like this and you dont think its your fault, its much more likely that its a bug with `<Suspense/>` or something. Feel free to go ahead and open an [issue](https://github.com/leptos-rs/leptos/issues) or [discussion](https://github.com/leptos-rs/leptos/discussions) on GitHub for help.
### Not all client code can run on the server
Imagine you happily import a dependency like `gloo-net` that youve been used to using to make requests in the browser, and use it in a `create_resource` in a server-rendered app.
Youll probably instantly see the dreaded message
```
panicked at 'cannot call wasm-bindgen imported functions on non-wasm targets'
```
Uh-oh.
But of course this makes sense. Weve just said that your app needs to run on the client and the server.
#### Solution
There are a few ways to avoid this:
1. Only use libraries that can run on both the server and the client. `reqwest`, for example, works for making HTTP requests in both settings.
2. Use different libraries on the server and the client, and gate them using the `#[cfg]` macro. ([Click here for an example](https://github.com/leptos-rs/leptos/blob/main/examples/hackernews/src/api.rs).)
3. Wrap client-only code in `create_effect`. Because `create_effect` only runs on the client, this can be an effective way to access browser APIs that are not needed for initial rendering.
For example, say that I want to store something in the browsers `localStorage` whenever a signal changes.
```rust
#[component]
pub fn App(cx: Scope) -> impl IntoView {
use gloo_storage::Storage;
let storage = gloo_storage::LocalStorage::raw();
leptos::log!("{storage:?}");
}
```
This panics because I cant access `LocalStorage` during server rendering.
But if I wrap it in an effect...
```rust
#[component]
pub fn App(cx: Scope) -> impl IntoView {
use gloo_storage::Storage;
create_effect(cx, move |_| {
let storage = gloo_storage::LocalStorage::raw();
leptos::log!("{storage:?}");
});
}
```
Its fine! This will render appropriately on the server, ignoring the client-only code, and then access the storage and log a message on the browser.
### Not all server code can run on the client
WebAssembly running in the browser is a pretty limited environment. You dont have access to a file-system or to many of the other things the standard library may be used to having. Not every crate can even be compiled to WASM, let alone run in a WASM environment.
In particular, youll sometimes see errors about the crate `mio` or missing things from `core`. This is generally a sign that you are trying to compile something to WASM that cant be compiled to WASM. If youre adding server-only dependencies, youll want to mark them `optional = true` in your `Cargo.toml` and then enable them in the `ssr` feature definition. (Check out one of the template `Cargo.toml` files to see more details.)
You can use `create_effect` to specify that something should only run on the client, and not in the server. Is there a way to specify that something should run only on the server, and not the client?
In fact, there is. The next chapter will cover the topic of server functions in some detail. (In the meantime, you can check out their docs [here](https://docs.rs/leptos_server/0.2.5/leptos_server/index.html).)

View File

@@ -52,12 +52,6 @@ reactively update when the signal changes.
Now every time I click the button, the text should toggle between red and black as
the number switches between even and odd.
> If youre following along, make sure you go into your `index.html` and add something like this:
>
> ```html
> <style>.red { color: red; }</style>
> ```
## Dynamic Attributes
The same applies to plain attributes. Passing a plain string or primitive value to

View File

@@ -1,62 +0,0 @@
[env]
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CARGO_BUILD_TEST_FLAGS = ""
# Emulate workspace
CARGO_MAKE_WORKSPACE_EMULATION = true
CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
"counter",
"counter_isomorphic",
"counters",
"counters_stable",
"counter_without_macros",
"error_boundary",
"errors_axum",
"fetch",
"hackernews",
"hackernews_axum",
"login_with_token_csr_only",
"parent_child",
"router",
"session_auth_axum",
"ssr_modes",
"ssr_modes_axum",
"tailwind",
"tailwind_csr_trunk",
"timer",
"todo_app_sqlite",
"todo_app_sqlite_axum",
"todo_app_sqlite_viz",
"todomvc",
]
[tasks.verify-flow]
description = "Provides pre and post hooks for verify"
dependencies = ["pre-verify-flow", "verify", "post-verify-flow"]
[tasks.verify]
description = "Run all quality checks and tests"
dependencies = ["check-style", "test-unit-and-web"]
[tasks.test-unit-and-web]
description = "Run all unit and web tests"
dependencies = ["test-flow", "web-test-flow"]
[tasks.check-style]
description = "Check for style violations"
dependencies = ["check-format-flow", "clippy-flow"]
[tasks.pre-verify-flow]
[tasks.post-verify-flow]
[tasks.web-test-flow]
description = "Provides pre and post hooks for web-test"
dependencies = ["pre-web-test-flow", "web-test", "post-web-test-flow"]
[tasks.pre-web-test-flow]
[tasks.web-test]
[tasks.post-web-test-flow]

View File

@@ -17,7 +17,6 @@ console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.84"
wasm-bindgen-test = "0.3.34"
pretty_assertions = "1.3.0"
rstest = "0.17.0"
[dev-dependencies.web-sys]
features = ["HtmlElement", "XPathResult"]

View File

@@ -1,10 +1,7 @@
[env]
CARGO_MAKE_WASM_TEST_ARGS = "--headless --chrome"
[tasks.test-all]
dependencies = ["test", "web-test"]
[tasks.web-test]
[tasks.post-test]
command = "cargo"
args = ["make", "wasm-pack-test"]

View File

@@ -2,8 +2,8 @@ use leptos::{ev, html::*, *};
/// A simple counter view.
// A component is really just a function call: it runs once to create the DOM and reactive system
pub fn counter(cx: Scope, initial_value: i32, step: u32) -> impl IntoView {
let (count, set_count) = create_signal(cx, Count::new(initial_value, step));
pub fn counter(cx: Scope, initial_value: i32, step: i32) -> impl IntoView {
let (value, set_value) = create_signal(cx, initial_value);
// elements are created by calling a function with a Scope argument
// the function name is the same as the HTML tag name
@@ -16,13 +16,13 @@ pub fn counter(cx: Scope, initial_value: i32, step: u32) -> impl IntoView {
// typed events found in leptos::ev
// 1) prevent typos in event names
// 2) allow for correct type inference in callbacks
.on(ev::click, move |_| set_count.update(|count| count.clear()))
.on(ev::click, move |_| set_value.update(|value| *value = 0))
.child("Clear"),
)
.child(
button(cx)
.on(ev::click, move |_| {
set_count.update(|count| count.decrease())
set_value.update(|value| *value -= step)
})
.child("-1"),
)
@@ -31,45 +31,14 @@ pub fn counter(cx: Scope, initial_value: i32, step: u32) -> impl IntoView {
.child("Value: ")
// reactive values are passed to .child() as a tuple
// (Scope, [child function]) so an effect can be created
.child(move || count.get().value())
.child((cx, move || value.get()))
.child("!"),
)
.child(
button(cx)
.on(ev::click, move |_| {
set_count.update(|count| count.increase())
set_value.update(|value| *value += step)
})
.child("+1"),
)
}
#[derive(Debug, Clone)]
pub struct Count {
value: i32,
step: i32,
}
impl Count {
pub fn new(value: i32, step: u32) -> Self {
Count {
value,
step: step as i32,
}
}
pub fn value(&self) -> i32 {
self.value
}
pub fn increase(&mut self) {
self.value += self.step;
}
pub fn decrease(&mut self) {
self.value += -self.step;
}
pub fn clear(&mut self) {
self.value = 0;
}
}

View File

@@ -1,49 +0,0 @@
mod count {
use counter_without_macros::Count;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
#[case(-2, 1)]
#[case(-1, 1)]
#[case(0, 1)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
fn should_increase_count(#[case] initial_value: i32, #[case] step: u32) {
let mut count = Count::new(initial_value, step);
count.increase();
assert_eq!(count.value(), initial_value + step as i32);
}
#[rstest]
#[case(-2, 1)]
#[case(-1, 1)]
#[case(0, 1)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
#[trace]
fn should_decrease_count(#[case] initial_value: i32, #[case] step: u32) {
let mut count = Count::new(initial_value, step);
count.decrease();
assert_eq!(count.value(), initial_value - step as i32);
}
#[rstest]
#[case(-2, 1)]
#[case(-1, 1)]
#[case(0, 1)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
#[trace]
fn should_clear_count(#[case] initial_value: i32, #[case] step: u32) {
let mut count = Count::new(initial_value, step);
count.clear();
assert_eq!(count.value(), 0);
}
}

View File

@@ -11,5 +11,4 @@ console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = "0.3"

View File

@@ -38,7 +38,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
};
view! { cx,
<div>
<>
<button on:click=add_counter>
"Add Counter"
</button>
@@ -72,7 +72,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
}
/>
</ul>
</div>
</>
}
}

View File

@@ -1,11 +1,10 @@
use wasm_bindgen_test::*;
use wasm_bindgen::JsCast;
wasm_bindgen_test_configure!(run_in_browser);
use leptos::*;
use web_sys::HtmlElement;
use counters::Counters;
use counters::{Counters, CountersProps};
#[wasm_bindgen_test]
fn inc() {
@@ -25,7 +24,7 @@ fn inc() {
add_counter.click();
// check HTML
assert_eq!(div.inner_html(), "<button>Add Counter</button><button>Add 1000 Counters</button><button>Clear Counters</button><p>Total: <span><!-- <DynChild> -->0<!-- </DynChild> --></span> from <span><!-- <DynChild> -->3<!-- </DynChild> --></span> counters.</p><ul><!-- <Each> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->0<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->0<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->0<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- </Each> --></ul>");
assert_eq!(div.inner_html(), "<button>Add Counter</button><button>Add 1000 Counters</button><button>Clear Counters</button><p>Total: <span>0</span> from <span>3</span> counters.</p><ul><li><button>-1</button><input type=\"text\"><span>0</span><button>+1</button><button>x</button></li><li><button>-1</button><input type=\"text\"><span>0</span><button>+1</button><button>x</button></li><li><button>-1</button><input type=\"text\"><span>0</span><button>+1</button><button>x</button></li></ul>");
let counters = div
.query_selector("ul")
@@ -53,7 +52,7 @@ fn inc() {
}
}
assert_eq!(div.inner_html(), "<button>Add Counter</button><button>Add 1000 Counters</button><button>Clear Counters</button><p>Total: <span><!-- <DynChild> -->6<!-- </DynChild> --></span> from <span><!-- <DynChild> -->3<!-- </DynChild> --></span> counters.</p><ul><!-- <Each> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->1<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->2<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->3<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- </Each> --></ul>");
assert_eq!(div.inner_html(), "<button>Add Counter</button><button>Add 1000 Counters</button><button>Clear Counters</button><p>Total: <span>6</span> from <span>3</span> counters.</p><ul><li><button>-1</button><input type=\"text\"><span>1</span><button>+1</button><button>x</button></li><li><button>-1</button><input type=\"text\"><span>2</span><button>+1</button><button>x</button></li><li><button>-1</button><input type=\"text\"><span>3</span><button>+1</button><button>x</button></li></ul>");
// remove the first counter
counters
@@ -64,5 +63,51 @@ fn inc() {
.unchecked_into::<HtmlElement>()
.click();
assert_eq!(div.inner_html(), "<button>Add Counter</button><button>Add 1000 Counters</button><button>Clear Counters</button><p>Total: <span><!-- <DynChild> -->5<!-- </DynChild> --></span> from <span><!-- <DynChild> -->2<!-- </DynChild> --></span> counters.</p><ul><!-- <Each> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->2<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- <EachItem> --><!-- <Counter> --><li><button>-1</button><input type=\"text\"><span><!-- <DynChild> -->3<!-- </DynChild> --></span><button>+1</button><button>x</button></li><!-- </Counter> --><!-- </EachItem> --><!-- </Each> --></ul>");
assert_eq!(div.inner_html(), "<button>Add Counter</button><button>Add 1000 Counters</button><button>Clear Counters</button><p>Total: <span>5</span> from <span>2</span> counters.</p><ul><li><button>-1</button><input type=\"text\"><span>2</span><button>+1</button><button>x</button></li><li><button>-1</button><input type=\"text\"><span>3</span><button>+1</button><button>x</button></li></ul>");
// decrement all by 1
for idx in 0..counters.length() {
let counter = counters.item(idx).unwrap();
let dec_button = counter
.first_child()
.unwrap()
.unchecked_into::<HtmlElement>();
dec_button.click();
}
run_scope(create_runtime(), move |cx| {
// we can use RSX in test comparisons!
// note that if RSX template creation is bugged, this probably won't catch it
// (because the same bug will be reproduced in both sides of the assertion)
// so I use HTML tests for most internal testing like this
// but in user-land testing, RSX comparanda are cool
assert_eq!(
div.outer_html(),
view! { cx,
<div>
<button>"Add Counter"</button>
<button>"Add 1000 Counters"</button>
<button>"Clear Counters"</button>
<p>"Total: "<span>"3"</span>" from "<span>"2"</span>" counters."</p>
<ul>
<li>
<button>"-1"</button>
<input type="text"/>
<span>"1"</span>
<button>"+1"</button>
<button>"x"</button>
</li>
<li>
<button>"-1"</button>
<input type="text"/>
<span>"2"</span>
<button>"+1"</button>
<button>"x"</button>
</li>
</ul>
</div>
}
.outer_html()
);
});
}

View File

@@ -41,11 +41,11 @@ a[aria-current] {
}
.slideIn {
animation: 0.25s slideIn forwards;
animation: 0.125s slideIn forwards;
}
.slideOut {
animation: 0.25s slideOut forwards;
animation: 0.125s slideOut forwards;
}
@keyframes slideIn {
@@ -67,11 +67,11 @@ a[aria-current] {
}
.slideInBack {
animation: 0.25s slideInBack forwards;
animation: 0.125s slideInBack forwards;
}
.slideOutBack {
animation: 0.25s slideOutBack forwards;
animation: 0.125s slideOutBack forwards;
}
@keyframes slideInBack {

View File

@@ -1,14 +0,0 @@
[package]
name = "slots"
version = "0.1.0"
edition = "2021"
[profile.release]
codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -1,9 +0,0 @@
[tasks.build]
command = "cargo"
args = ["+nightly", "build-all-features"]
install_crate = "cargo-all-features"
[tasks.check]
command = "cargo"
args = ["+nightly", "check-all-features"]
install_crate = "cargo-all-features"

View File

@@ -1,7 +0,0 @@
# Leptos `<Component slot/>` Example
This example shows how to use Slots in Leptos.
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,8 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z"/>
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
</head>
<body></body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,63 +0,0 @@
use leptos::*;
// Slots are created in simillar manner to components, except that they use the #[slot] macro.
#[slot]
struct Then {
children: ChildrenFn,
}
// Props work just like component props, for example, you can specify a prop as optional by prefixing
// the type with Option<...> and marking the option as #[prop(optional)].
#[slot]
struct ElseIf {
cond: MaybeSignal<bool>,
children: ChildrenFn,
}
#[slot]
struct Fallback {
children: ChildrenFn,
}
// Slots are added to components like any other prop.
#[component]
fn SlotIf(
cx: Scope,
cond: MaybeSignal<bool>,
then: Then,
#[prop(default=vec![])] else_if: Vec<ElseIf>,
#[prop(optional)] fallback: Option<Fallback>,
) -> impl IntoView {
move || {
if cond() {
(then.children)(cx).into_view(cx)
} else if let Some(else_if) = else_if.iter().find(|i| (i.cond)()) {
(else_if.children)(cx).into_view(cx)
} else if let Some(fallback) = &fallback {
(fallback.children)(cx).into_view(cx)
} else {
().into_view(cx)
}
}
}
#[component]
pub fn App(cx: Scope) -> impl IntoView {
let (count, set_count) = create_signal(cx, 0);
let is_even = MaybeSignal::derive(cx, move || count() % 2 == 0);
let is_div5 = MaybeSignal::derive(cx, move || count() % 5 == 0);
let is_div7 = MaybeSignal::derive(cx, move || count() % 7 == 0);
view! { cx,
<button on:click=move |_| set_count.update(|value| *value += 1)>"+1"</button>
" "{count}" is "
<SlotIf cond=is_even>
// The slot name can be emitted if it would match the slot struct name (in snake case).
<Then slot>"even"</Then>
// Props are passed just like on normal components.
<ElseIf slot cond=is_div5>"divisible by 5"</ElseIf>
<ElseIf slot cond=is_div7>"divisible by 7"</ElseIf>
<Fallback slot>"odd"</Fallback>
</SlotIf>
}
}

View File

@@ -1,12 +0,0 @@
use leptos::*;
use slots::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| {
view! { cx,
<App/>
}
})
}

View File

@@ -1,15 +1,12 @@
#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() {
use axum::{
extract::{Extension, Path},
routing::{get, post},
Router,
};
async fn main(){
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use ssr_modes_axum::{app::*, fallback::file_and_error_handler};
use axum::{extract::{Extension, Path}, Router, routing::{get, post}};
use std::sync::Arc;
use ssr_modes_axum::fallback::file_and_error_handler;
use ssr_modes_axum::app::*;
let conf = get_configuration(None).await.unwrap();
let addr = conf.leptos_options.site_addr;
@@ -22,11 +19,7 @@ async fn main() {
let app = Router::new()
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.leptos_routes(
leptos_options.clone(),
routes,
|cx| view! { cx, <App/> },
)
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> })
.fallback(file_and_error_handler)
.layer(Extension(Arc::new(leptos_options)));

View File

@@ -1,24 +0,0 @@
[package]
name = "timer"
version = "0.1.0"
edition = "2021"
[profile.release]
codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
features = [
"Window",
]
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View File

@@ -1,9 +0,0 @@
[tasks.build]
command = "cargo"
args = ["+nightly", "build-all-features"]
install_crate = "cargo-all-features"
[tasks.check]
command = "cargo"
args = ["+nightly", "check-all-features"]
install_crate = "cargo-all-features"

View File

@@ -1,7 +0,0 @@
# Leptos Timer Example
This example creates a simple timer based on `setInterval` 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,8 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z"/>
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
</head>
<body></body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "nightly"

View File

@@ -1,61 +0,0 @@
use leptos::{leptos_dom::helpers::IntervalHandle, *};
use std::time::Duration;
/// Timer example, demonstrating the use of `use_interval`.
#[component]
pub fn TimerDemo(cx: Scope) -> impl IntoView {
// count_a updates with a fixed interval of 1000 ms, whereas count_b has a dynamic
// update interval.
let (count_a, set_count_a) = create_signal(cx, 0_i32);
let (count_b, set_count_b) = create_signal(cx, 0_i32);
let (interval, set_interval) = create_signal(cx, 1000);
use_interval(cx, 1000, move || {
set_count_a.update(|c| *c = *c + 1);
});
use_interval(cx, interval, move || {
set_count_b.update(|c| *c = *c + 1);
});
view! { cx,
<div>
<div>"Count A (fixed interval of 1000 ms)"</div>
<div>{count_a}</div>
<div>"Count B (dynamic interval, currently " {interval} " ms)"</div>
<div>{count_b}</div>
<input prop:value=interval on:input=move |ev| {
if let Ok(value) = event_target_value(&ev).parse::<u64>() {
set_interval(value);
}
}/>
</div>
}
}
/// Hook to wrap the underlying `setInterval` call and make it reactive w.r.t.
/// possible changes of the timer interval.
pub fn use_interval<T, F>(cx: Scope, interval_millis: T, f: F)
where
F: Fn() + Clone + 'static,
T: Into<MaybeSignal<u64>> + 'static,
{
let interval_millis = interval_millis.into();
create_effect(cx, move |prev_handle: Option<IntervalHandle>| {
// effects get their previous return value as an argument
// each time the effect runs, it will return the interval handle
// so if we have a previous one, we cancel it
if let Some(prev_handle) = prev_handle {
prev_handle.clear();
};
// here, we return the handle
set_interval_with_handle(
f.clone(),
// this is the only reactive access, so this effect will only
// re-run when the interval changes
Duration::from_millis(interval_millis.get()),
)
.expect("could not create interval")
});
}

View File

@@ -1,12 +0,0 @@
use leptos::*;
use timer::TimerDemo;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| {
view! { cx,
<TimerDemo />
}
})
}

View File

@@ -156,11 +156,11 @@ pub fn Todos(cx: Scope) -> impl IntoView {
todos.read(cx)
.map(move |todos| match todos {
Err(e) => {
view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_view(cx)
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
}
Ok(todos) => {
if todos.is_empty() {
view! { cx, <p>"No tasks were found."</p> }.into_view(cx)
vec![view! { cx, <p>"No tasks were found."</p> }.into_any()]
} else {
todos
.into_iter()
@@ -175,9 +175,9 @@ pub fn Todos(cx: Scope) -> impl IntoView {
</ActionForm>
</li>
}
.into_any()
})
.collect::<Vec<_>>()
.into_view(cx)
}
}
})

View File

@@ -13,9 +13,7 @@ impl TodoAppError {
pub fn status_code(&self) -> StatusCode {
match self {
TodoAppError::NotFound => StatusCode::NOT_FOUND,
TodoAppError::InternalServerError => {
StatusCode::INTERNAL_SERVER_ERROR
}
TodoAppError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

View File

@@ -52,8 +52,7 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
let mut conn = db().await?;
let mut todos = Vec::new();
let mut rows =
sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
@@ -110,25 +109,19 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FormData {
hi: String,
hi: String
}
#[server(FormDataHandler, "/api")]
pub async fn form_data(cx: Scope) -> Result<FormData, ServerFnError> {
use axum::extract::FromRequest;
let req = use_context::<leptos_axum::LeptosRequest<axum::body::Body>>(cx)
.and_then(|req| req.take_request())
.unwrap();
let req = use_context::<leptos_axum::LeptosRequest<axum::body::Body>>(cx).and_then(|req| req.take_request()).unwrap();
if req.method() == http::Method::POST {
let form = axum::Form::from_request(req, &())
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
let form = axum::Form::from_request(req, &()).await.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
Ok(form.0)
} else {
Err(ServerFnError::ServerError(
"wrong form fields submitted".to_string(),
))
Err(ServerFnError::ServerError("wrong form fields submitted".to_string()))
}
}
@@ -153,7 +146,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
</ErrorBoundary>
}/> //Route
<Route path="weird" methods=&[Method::Get, Method::Post]
ssr=SsrMode::Async
ssr=SsrMode::Async
view=|cx| {
let res = create_resource(cx, || (), move |_| async move {
form_data(cx).await
@@ -209,11 +202,11 @@ pub fn Todos(cx: Scope) -> impl IntoView {
todos.read(cx)
.map(move |todos| match todos {
Err(e) => {
view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_view(cx)
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
}
Ok(todos) => {
if todos.is_empty() {
view! { cx, <p>"No tasks were found."</p> }.into_view(cx)
vec![view! { cx, <p>"No tasks were found."</p> }.into_any()]
} else {
todos
.into_iter()
@@ -228,9 +221,9 @@ pub fn Todos(cx: Scope) -> impl IntoView {
</ActionForm>
</li>
}
.into_any()
})
.collect::<Vec<_>>()
.into_view(cx)
}
}
})

View File

@@ -163,11 +163,11 @@ pub fn Todos(cx: Scope) -> impl IntoView {
todos.read(cx)
.map(move |todos| match todos {
Err(e) => {
view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_view(cx)
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
}
Ok(todos) => {
if todos.is_empty() {
view! { cx, <p>"No tasks were found."</p> }.into_view(cx)
vec![view! { cx, <p>"No tasks were found."</p> }.into_any()]
} else {
todos
.into_iter()
@@ -182,9 +182,9 @@ pub fn Todos(cx: Scope) -> impl IntoView {
</ActionForm>
</li>
}
.into_any()
})
.collect::<Vec<_>>()
.into_view(cx)
}
}
})

View File

@@ -202,19 +202,6 @@ pub fn TodoMVC(cx: Scope) -> impl IntoView {
}
});
// focus the main input on load
create_effect(cx, move |_| {
if let Some(input) = input_ref.get() {
// We use request_animation_frame here because the NodeRef
// is filled when the element is created, but before it's mounted
// to the DOM. Calling .focus() before it's mounted does nothing.
// So inside, we wait a tick for the browser to mount it, then .focus()
request_animation_frame(move || {
input.focus();
});
}
});
view! { cx,
<main>
<section class="todoapp">

130
flake.lock generated
View File

@@ -1,130 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1681920287,
"narHash": "sha256-+/d6XQQfhhXVfqfLROJoqj3TuG38CAeoT6jO1g9r1k0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "645bc49f34fa8eff95479f0345ff57e55b53437e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1682043560,
"narHash": "sha256-ZsF4Yee9pQbvLtwSVGgYux+az4yFSLXrxPyGHm3ptJM=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "48037a6f8faeee138ede96bf607bc95c9dab9aec",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,36 +0,0 @@
{
description = "A basic Rust devshell for NixOS users developing Leptos";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
in
with pkgs;
{
devShells.default = mkShell {
buildInputs = [
openssl
pkg-config
cacert
(rust-bin.selectLatestNightlyWith( toolchain: toolchain.default.override {
extensions= [ "rust-src" "rust-analyzer" ];
targets = [ "wasm32-unknown-unknown" ];
}))
];
shellHook = ''
'';
};
}
);
}

View File

@@ -17,4 +17,3 @@ leptos_integration_utils = { workspace = true }
serde_json = "1"
parking_lot = "0.12.1"
regex = "1.7.0"
tracing = "0.1.37"

View File

@@ -13,7 +13,7 @@ use actix_web::{
web::Bytes,
*,
};
use futures::{Stream, StreamExt};
use futures::{Future, Stream, StreamExt};
use http::StatusCode;
use leptos::{
leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context,
@@ -26,8 +26,8 @@ use leptos_meta::*;
use leptos_router::*;
use parking_lot::RwLock;
use regex::Regex;
use std::{fmt::Display, future::Future, sync::Arc};
use tracing::instrument;
use std::sync::Arc;
/// 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)]
@@ -98,7 +98,6 @@ impl ResponseOptions {
/// Provides an easy way to redirect the user from within a server function. Mimicking the Remix `redirect()`,
/// it sets a [StatusCode] of 302 and a [LOCATION](header::LOCATION) header with the provided value.
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead.
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn redirect(cx: leptos::Scope, path: &str) {
if let Some(response_options) = use_context::<ResponseOptions>(cx) {
response_options.set_status(StatusCode::FOUND);
@@ -148,7 +147,6 @@ pub fn redirect(cx: leptos::Scope, path: &str) {
/// This function always provides context values including the following types:
/// - [ResponseOptions]
/// - [HttpRequest](actix_web::HttpRequest)
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn handle_server_fns() -> Route {
handle_server_fns_with_context(|_cx| {})
}
@@ -168,7 +166,6 @@ pub fn handle_server_fns() -> Route {
/// This function always provides context values including the following types:
/// - [ResponseOptions]
/// - [HttpRequest](actix_web::HttpRequest)
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn handle_server_fns_with_context(
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
) -> Route {
@@ -342,7 +339,6 @@ pub fn handle_server_fns_with_context(
/// - [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<IV>(
options: LeptosOptions,
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
@@ -411,7 +407,6 @@ where
/// - [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_in_order<IV>(
options: LeptosOptions,
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
@@ -483,7 +478,6 @@ where
/// - [HttpRequest](actix_web::HttpRequest)
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn render_app_async<IV>(
options: LeptosOptions,
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
@@ -507,7 +501,6 @@ where
/// - [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<IV>(
options: LeptosOptions,
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
@@ -557,7 +550,6 @@ where
/// - [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_in_order_with_context<IV>(
options: LeptosOptions,
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
@@ -609,7 +601,6 @@ where
/// - [HttpRequest](actix_web::HttpRequest)
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn render_app_async_with_context<IV>(
options: LeptosOptions,
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
@@ -750,7 +741,7 @@ where
}
})
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn provide_contexts(
cx: leptos::Scope,
req: &HttpRequest,
@@ -775,7 +766,7 @@ fn leptos_corrected_path(req: &HttpRequest) -> String {
"http://leptos".to_string() + path + "?" + query
}
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
async fn stream_app(
options: &LeptosOptions,
app: impl FnOnce(leptos::Scope) -> View + 'static,
@@ -791,10 +782,7 @@ async fn stream_app(
build_stream_response(options, res_options, stream, runtime, scope).await
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
async fn stream_app_in_order(
options: &LeptosOptions,
app: impl FnOnce(leptos::Scope) -> View + 'static,
@@ -812,7 +800,7 @@ async fn stream_app_in_order(
build_stream_response(options, res_options, stream, runtime, scope).await
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
async fn build_stream_response(
options: &LeptosOptions,
res_options: ResponseOptions,
@@ -862,7 +850,7 @@ async fn build_stream_response(
// Return the response
res
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
async fn render_app_async_helper(
options: &LeptosOptions,
app: impl FnOnce(leptos::Scope) -> View + 'static,
@@ -905,20 +893,6 @@ async fn render_app_async_helper(
pub fn generate_route_list<IV>(
app_fn: impl FnOnce(leptos::Scope) -> IV + 'static,
) -> Vec<RouteListing>
where
IV: IntoView + 'static,
{
generate_route_list_with_exclusions(app_fn, None)
}
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
/// create routes in Actix's App without having to use wildcard matching or fallbacks. Takes in your root app Element
/// as an argument so it can walk you app tree. This version is tailored to generated Actix compatible paths. Adding excluded_routes
/// to this function will stop `.leptos_routes()` from generating a route for it, allowing a custom handler. These need to be in Actix path format
pub fn generate_route_list_with_exclusions<IV>(
app_fn: impl FnOnce(leptos::Scope) -> IV + 'static,
excluded_routes: Option<Vec<String>>,
) -> Vec<RouteListing>
where
IV: IntoView + 'static,
{
@@ -946,7 +920,7 @@ where
// Match `:some_word` but only capture `some_word` in the groups to replace with `{some_word}`
let capture_re = Regex::new(r":((?:[^.,/]+)+)[^/]?").unwrap();
let mut routes = routes
let routes = routes
.into_iter()
.map(|listing| {
let path = wildcard_re
@@ -960,10 +934,6 @@ where
if routes.is_empty() {
vec![RouteListing::new("/", Default::default(), [Method::Get])]
} else {
// Routes to exclude from auto generation
if let Some(excluded_routes) = excluded_routes {
routes.retain(|p| !excluded_routes.iter().any(|e| e == p.path()))
}
routes
}
}
@@ -1023,7 +993,6 @@ where
InitError = (),
>,
{
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn leptos_routes<IV>(
self,
options: LeptosOptions,
@@ -1035,7 +1004,7 @@ 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,
@@ -1063,7 +1032,7 @@ where
}
router
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn leptos_routes_with_context<IV>(
self,
options: LeptosOptions,
@@ -1112,94 +1081,3 @@ where
router
}
}
/// A helper to make it easier to use Axum extractors in server functions. This takes
/// a handler function as its argument. The handler follows similar rules to an Actix
/// [Handler](actix_web::Handler): it is an async function that receives arguments that
/// will be extracted from the request and returns some value.
///
/// ```rust,ignore
/// use leptos::*;
/// use serde::Deserialize;
/// #[derive(Deserialize)]
/// struct Search {
/// q: String,
/// }
///
/// #[server(ExtractoServerFn, "/api")]
/// pub async fn extractor_server_fn(cx: Scope) -> Result<String, ServerFnError> {
/// use actix_web::dev::ConnectionInfo;
/// use actix_web::web::{Data, Query};
///
/// extract(
/// cx,
/// |data: Data<String>, search: Query<Search>, connection: ConnectionInfo| async move {
/// format!(
/// "data = {}\nsearch = {}\nconnection = {:?}",
/// data.into_inner(),
/// search.q,
/// connection
/// )
/// },
/// )
/// .await
/// }
/// ```
pub async fn extract<F, E>(
cx: leptos::Scope,
f: F,
) -> Result<<<F as Extractor<E>>::Future as Future>::Output, ServerFnError>
where
F: Extractor<E>,
E: actix_web::FromRequest,
<E as actix_web::FromRequest>::Error: Display,
<F as Extractor<E>>::Future: Future,
{
let req = use_context::<actix_web::HttpRequest>(cx)
.expect("HttpRequest should have been provided via context");
let input = E::extract(&req)
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
Ok(f.call(input).await)
}
// Drawn from the Actix Handler implementation
// https://github.com/actix/actix-web/blob/19c9d858f25e8262e14546f430d713addb397e96/actix-web/src/handler.rs#L124
pub trait Extractor<T> {
type Future;
fn call(&self, args: T) -> Self::Future;
}
macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, Fut, $($param,)*> Extractor<($($param,)*)> for Func
where
Func: Fn($($param),*) -> Fut + Clone + 'static,
Fut: Future,
{
type Future = Fut;
#[inline]
#[allow(non_snake_case)]
fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
(self)($($param,)*)
}
}
});
factory_tuple! {}
factory_tuple! { A }
factory_tuple! { A B }
factory_tuple! { A B C }
factory_tuple! { A B C D }
factory_tuple! { A B C D E }
factory_tuple! { A B C D E F }
factory_tuple! { A B C D E F G }
factory_tuple! { A B C D E F G H }
factory_tuple! { A B C D E F G H I }
factory_tuple! { A B C D E F G H I J }
factory_tuple! { A B C D E F G H I J K }
factory_tuple! { A B C D E F G H I J K L }
factory_tuple! { A B C D E F G H I J K L M }
factory_tuple! { A B C D E F G H I J K L M N }
factory_tuple! { A B C D E F G H I J K L M N O }
factory_tuple! { A B C D E F G H I J K L M N O P }

View File

@@ -20,5 +20,4 @@ serde_json = "1"
tokio = { version = "1", features = ["full"] }
parking_lot = "0.12.1"
tokio-util = {version = "0.7.7", features = ["rt"] }
tracing = "0.1.37"
once_cell = "1.17"

View File

@@ -1,4 +1,5 @@
#![forbid(unsafe_code)]
//! Provides functions to easily integrate Leptos with Axum.
//!
//! For more details on how to use the integrations, see the
@@ -19,10 +20,7 @@ use futures::{
channel::mpsc::{Receiver, Sender},
Future, SinkExt, Stream, StreamExt,
};
use http::{
header, method::Method, request::Parts, uri::Uri, version::Version,
Response,
};
use http::{header, method::Method, uri::Uri, version::Version, Response};
use hyper::body;
use leptos::{
leptos_server::{server_fn_by_path, Payload},
@@ -38,7 +36,7 @@ use parking_lot::RwLock;
use std::{io, pin::Pin, sync::Arc, thread::available_parallelism};
use tokio::task::LocalSet;
use tokio_util::task::LocalPoolHandle;
use tracing::Instrument;
/// 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)]
@@ -49,21 +47,6 @@ pub struct RequestParts {
pub headers: HeaderMap<HeaderValue>,
pub body: Bytes,
}
/// Convert http::Parts to RequestParts(and vice versa). Body and Extensions will
/// be lost in the conversion
impl From<Parts> for RequestParts {
fn from(parts: Parts) -> Self {
Self {
version: parts.version,
method: parts.method,
uri: parts.uri,
headers: parts.headers,
body: Bytes::default(),
}
}
}
/// 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)]
@@ -265,7 +248,6 @@ where
/// This function always provides context values including the following types:
/// - [RequestParts]
/// - [ResponseOptions]
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn handle_server_fns(
Path(fn_name): Path<String>,
headers: HeaderMap,
@@ -289,7 +271,6 @@ pub async fn handle_server_fns(
/// This function always provides context values including the following types:
/// - [RequestParts]
/// - [ResponseOptions]
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn handle_server_fns_with_context(
Path(fn_name): Path<String>,
headers: HeaderMap,
@@ -300,7 +281,7 @@ pub async fn handle_server_fns_with_context(
handle_server_fns_inner(fn_name, headers, query, additional_context, req)
.await
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
async fn handle_server_fns_inner(
fn_name: String,
headers: HeaderMap,
@@ -484,7 +465,6 @@ pub type PinnedHtmlStream =
/// - [ResponseOptions]
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "info", fields(error), skip_all)]
pub fn render_app_to_stream<IV>(
options: LeptosOptions,
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
@@ -558,7 +538,6 @@ where
/// - [ResponseOptions]
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "info", fields(error), skip_all)]
pub fn render_app_to_stream_in_order<IV>(
options: LeptosOptions,
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
@@ -604,7 +583,6 @@ where
/// - [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<IV>(
options: LeptosOptions,
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
@@ -633,8 +611,6 @@ where
let res_options3 = default_res_options.clone();
let local_pool = get_leptos_pool();
let (tx, rx) = futures::channel::mpsc::channel(8);
let current_span = tracing::Span::current();
local_pool.spawn_pinned(move || async move {
let app = {
// Need to get the path and query string of the Request
@@ -658,12 +634,12 @@ where
);
forward_stream(&options, res_options2, bundle, runtime, scope, tx).await;
}.instrument(current_span));
});
async move { generate_response(res_options3, rx).await }
})
}
}
#[tracing::instrument(level = "info", fields(error), skip_all)]
async fn generate_response(
res_options: ResponseOptions,
rx: Receiver<String>,
@@ -693,7 +669,7 @@ async fn generate_response(
res
}
#[tracing::instrument(level = "info", fields(error), skip_all)]
async fn forward_stream(
options: &LeptosOptions,
res_options2: ResponseOptions,
@@ -753,7 +729,6 @@ async fn forward_stream(
/// - [ResponseOptions]
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "info", fields(error), skip_all)]
pub fn render_app_to_stream_in_order_with_context<IV>(
options: LeptosOptions,
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
@@ -791,8 +766,7 @@ where
let (tx, rx) = futures::channel::mpsc::channel(8);
let local_pool = get_leptos_pool();
let current_span = tracing::Span::current();
local_pool.spawn_pinned(|| async move {
local_pool.spawn_pinned(move || async move {
let app = {
let full_path = full_path.clone();
let (req, req_parts) = generate_request_and_parts(req).await;
@@ -811,14 +785,14 @@ where
);
forward_stream(&options, res_options2, bundle, runtime, scope, tx).await;
}.instrument(current_span));
});
generate_response(res_options3, rx).await
}
})
}
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn provide_contexts<B: 'static + std::fmt::Debug + std::default::Default>(
cx: Scope,
path: String,
@@ -886,7 +860,6 @@ fn provide_contexts<B: 'static + std::fmt::Debug + std::default::Default>(
/// - [ResponseOptions]
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "info", fields(error), skip_all)]
pub fn render_app_async<IV>(
options: LeptosOptions,
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
@@ -928,7 +901,6 @@ where
/// - [ResponseOptions]
/// - [MetaContext](leptos_meta::MetaContext)
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
#[tracing::instrument(level = "info", fields(error), skip_all)]
pub fn render_app_async_with_context<IV>(
options: LeptosOptions,
additional_context: impl Fn(leptos::Scope) + 'static + Clone + Send,
@@ -1017,25 +989,9 @@ where
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths.
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn generate_route_list<IV>(
app_fn: impl FnOnce(Scope) -> IV + 'static,
) -> Vec<RouteListing>
where
IV: IntoView + 'static,
{
generate_route_list_with_exclusions(app_fn, None).await
}
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. Adding excluded_routes
/// to this function will stop `.leptos_routes()` from generating a route for it, allowing a custom handler. These need to be in Axum path format
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn generate_route_list_with_exclusions<IV>(
app_fn: impl FnOnce(Scope) -> IV + 'static,
excluded_routes: Option<Vec<String>>,
) -> Vec<RouteListing>
where
IV: IntoView + 'static,
{
@@ -1062,7 +1018,7 @@ where
let routes = routes.0.read().to_owned();
// Axum's Router defines Root routes as "/" not ""
let mut routes = routes
let routes = routes
.into_iter()
.map(|listing| {
let path = listing.path();
@@ -1085,10 +1041,6 @@ where
[leptos_router::Method::Get],
)]
} else {
// Routes to exclude from auto generation
if let Some(excluded_routes) = excluded_routes {
routes.retain(|p| !excluded_routes.iter().any(|e| e == p.path()))
}
routes
}
}
@@ -1127,7 +1079,6 @@ pub trait LeptosRoutes {
/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests
/// to those paths to Leptos's renderer.
impl LeptosRoutes for axum::Router {
#[tracing::instrument(level = "info", fields(error), skip_all)]
fn leptos_routes<IV>(
self,
options: LeptosOptions,
@@ -1140,7 +1091,6 @@ impl LeptosRoutes for axum::Router {
self.leptos_routes_with_context(options, paths, |_| {}, app_fn)
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn leptos_routes_with_context<IV>(
self,
options: LeptosOptions,
@@ -1208,7 +1158,6 @@ impl LeptosRoutes for axum::Router {
router
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn leptos_routes_with_handler<H, T>(
self,
paths: Vec<RouteListing>,
@@ -1238,7 +1187,7 @@ impl LeptosRoutes for axum::Router {
router
}
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn get_leptos_pool() -> LocalPoolHandle {
static LOCAL_POOL: OnceCell<LocalPoolHandle> = OnceCell::new();
LOCAL_POOL

View File

@@ -13,4 +13,3 @@ leptos = { workspace = true, features = ["ssr"] }
leptos_hot_reload = { workspace = true }
leptos_meta = { workspace = true, features = ["ssr"] }
leptos_config = { workspace = true }
tracing="0.1.37"

View File

@@ -3,9 +3,6 @@ use leptos::{use_context, RuntimeId, ScopeId};
use leptos_config::LeptosOptions;
use leptos_meta::MetaContext;
extern crate tracing;
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn autoreload(options: &LeptosOptions) -> String {
let site_ip = &options.site_addr.ip().to_string();
let reload_port = options.reload_port;
@@ -42,7 +39,7 @@ fn autoreload(options: &LeptosOptions) -> String {
false => "".to_string(),
}
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn html_parts(
options: &LeptosOptions,
meta: Option<&MetaContext>,
@@ -51,10 +48,10 @@ pub fn html_parts(
let output_name = &options.output_name;
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME at compile time
// Otherwise we need to add _bg because wasm_pack always does.
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
let mut wasm_output_name = output_name.clone();
if std::option_env!("LEPTOS_OUTPUT_NAME").is_none() {
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
wasm_output_name.push_str("_bg");
}
@@ -78,7 +75,6 @@ pub fn html_parts(
(head, tail)
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub fn html_parts_separated(
options: &LeptosOptions,
meta: Option<&MetaContext>,
@@ -119,7 +115,6 @@ pub fn html_parts_separated(
(head, tail)
}
#[tracing::instrument(level = "trace", fields(error), skip_all)]
pub async fn build_async_response(
stream: impl Stream<Item = String> + 'static,
options: &LeptosOptions,

View File

@@ -947,19 +947,6 @@ where
pub async fn generate_route_list<IV>(
app_fn: impl FnOnce(Scope) -> IV + 'static,
) -> Vec<RouteListing>
where
IV: IntoView + 'static,
{
generate_route_list_with_exclusions(app_fn, None).await
}
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
/// create routes in Viz's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
/// as an argument so it can walk you app tree. This version is tailored to generate Viz compatible paths.
pub async fn generate_route_list_with_exclusions<IV>(
app_fn: impl FnOnce(Scope) -> IV + 'static,
excluded_routes: Option<Vec<String>>,
) -> Vec<RouteListing>
where
IV: IntoView + 'static,
{
@@ -986,7 +973,7 @@ where
let routes = routes.0.read().to_owned();
// Viz's Router defines Root routes as "/" not ""
let mut routes = routes
let routes = routes
.into_iter()
.map(|listing| {
let path = listing.path();
@@ -1009,9 +996,6 @@ where
[leptos_router::Method::Get],
)]
} else {
if let Some(excluded_routes) = excluded_routes {
routes.retain(|p| !excluded_routes.iter().any(|e| e == p.path()))
}
routes
}
}

View File

@@ -45,20 +45,18 @@ where
provide_context(cx, errors);
// Run children so that they render and execute resources
let children = children(cx).into_view(cx);
let errors_empty = create_memo(cx, move |_| errors.with(Errors::is_empty));
let children = children(cx);
move || {
if errors_empty.get() {
children.clone().into_view(cx)
} else {
view! { cx,
match errors.with(Errors::is_empty) {
true => children.clone().into_view(cx),
false => view! { cx,
<>
{fallback(cx, errors)}
<leptos-error-boundary style="display: none">{children.clone()}</leptos-error-boundary>
</>
}
.into_view(cx)
.into_view(cx),
}
}
}

View File

@@ -41,10 +41,6 @@ use std::hash::Hash;
/// }
/// }
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all)
)]
#[component(transparent)]
pub fn For<IF, I, T, EF, N, KF, K>(
cx: Scope,

View File

@@ -1,5 +1,6 @@
#![deny(missing_docs)]
#![forbid(unsafe_code)]
//! # About Leptos
//!
//! Leptos is a full-stack framework for building web applications in Rust. You can use it to build
@@ -42,7 +43,6 @@
//! HTTP request within your reactive code.
//! - [`router`](https://github.com/leptos-rs/leptos/tree/main/examples/router) shows how to use Leptoss nested router
//! to enable client-side navigation and route-specific, reactive data loading.
//! - [`slots`](https://github.com/leptos-rs/leptos/tree/main/examples/slots) shows how to use slots on components.
//! - [`counter_isomorphic`](https://github.com/leptos-rs/leptos/tree/main/examples/counter_isomorphic) shows
//! different methods of interaction with a stateful server, including server functions, server actions, forms,
//! and server-sent events (SSE).
@@ -160,8 +160,7 @@ pub use leptos_dom::{
request_animation_frame, request_animation_frame_with_handle,
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,
window_event_listener,
},
html, log, math, mount_to, mount_to_body, svg, warn, window, Attribute,
Class, Errors, Fragment, HtmlElement, IntoAttribute, IntoClass,
@@ -186,10 +185,11 @@ pub use suspense::*;
mod text_prop;
mod transition;
pub use text_prop::TextProp;
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
#[doc(hidden)]
pub use tracing;
pub use transition::*;
extern crate self as leptos;
/// The most common type for the `children` property on components,

View File

@@ -29,10 +29,6 @@ use std::{cell::RefCell, rc::Rc};
/// }
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all)
)]
#[component]
pub fn Show<F, W, IV>(
/// The scope the component is running in

View File

@@ -50,10 +50,6 @@ use std::rc::Rc;
/// # });
/// # }
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all)
)]
#[component(transparent)]
pub fn Suspense<F, E>(
cx: Scope,

View File

@@ -60,10 +60,6 @@ use std::{
/// # });
/// # }
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all)
)]
#[component(transparent)]
pub fn Transition<F, E>(
cx: Scope,

View File

@@ -53,23 +53,12 @@ pub struct LeptosOptions {
impl LeptosOptions {
fn try_from_env() -> Result<Self, LeptosConfigError> {
let output_name = env_w_default(
"LEPTOS_OUTPUT_NAME",
std::option_env!("LEPTOS_OUTPUT_NAME",).unwrap_or_default(),
)?;
if output_name.is_empty() {
eprintln!(
"It looks like you're trying to compile Leptos without the \
LEPTOS_OUTPUT_NAME environment variable being set. There are \
two options\n 1. cargo-leptos is not being used, but \
get_configuration() is being passed None. This needs to be \
changed to Some(\"Cargo.toml\")\n 2. You are compiling \
Leptos without LEPTOS_OUTPUT_NAME being set with \
cargo-leptos. This shouldn't be possible!"
);
}
Ok(LeptosOptions {
output_name,
output_name: std::env::var("LEPTOS_OUTPUT_NAME").map_err(|e| {
LeptosConfigError::EnvVarError(format!(
"LEPTOS_OUTPUT_NAME: {e}"
))
})?,
site_root: env_w_default("LEPTOS_SITE_ROOT", "target/site")?,
site_pkg_dir: env_w_default("LEPTOS_SITE_PKG_DIR", "pkg")?,
env: Env::default(),

View File

@@ -31,6 +31,9 @@ fn env_w_default_test() {
#[test]
fn try_from_env_test() {
std::env::remove_var("LEPTOS_OUTPUT_NAME");
assert!(LeptosOptions::try_from_env().is_err());
// Test config values from environment variables
std::env::set_var("LEPTOS_OUTPUT_NAME", "app_test");
std::env::set_var("LEPTOS_SITE_ROOT", "my_target/site");
@@ -48,4 +51,19 @@ fn try_from_env_test() {
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
// Test default config values
std::env::remove_var("LEPTOS_SITE_ROOT");
std::env::remove_var("LEPTOS_SITE_PKG_DIR");
std::env::remove_var("LEPTOS_SITE_ADDR");
std::env::remove_var("LEPTOS_RELOAD_PORT");
let config = LeptosOptions::try_from_env().unwrap();
assert_eq!(config.site_root, "target/site");
assert_eq!(config.site_pkg_dir, "pkg");
assert_eq!(
config.site_addr,
SocketAddr::from_str("127.0.0.1:3000").unwrap()
);
assert_eq!(config.reload_port, 3001);
}

View File

@@ -140,6 +140,9 @@ fn get_config_from_str_content() {
#[tokio::test]
async fn get_config_from_env() {
std::env::remove_var("LEPTOS_OUTPUT_NAME");
assert!(get_configuration(None).await.is_err());
// Test config values from environment variables
std::env::set_var("LEPTOS_OUTPUT_NAME", "app_test");
std::env::set_var("LEPTOS_SITE_ROOT", "my_target/site");

View File

@@ -54,7 +54,7 @@ pub struct ComponentRepr {
pub(crate) document_fragment: web_sys::DocumentFragment,
#[cfg(all(target_arch = "wasm32", feature = "web"))]
mounted: Rc<OnceCell<()>>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) name: Cow<'static, str>,
#[cfg(debug_assertions)]
_opening: Comment,
@@ -140,23 +140,18 @@ impl Mountable for ComponentRepr {
self.closing.node.clone()
}
}
impl From<ComponentRepr> for View {
fn from(value: ComponentRepr) -> Self {
impl IntoView for ComponentRepr {
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "<Component />", skip_all, fields(name = %self.name)))]
fn into_view(self, _: Scope) -> View {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
if !HydrationCtx::is_hydrating() {
for child in &value.children {
mount_child(MountKind::Before(&value.closing.node), child);
for child in &self.children {
mount_child(MountKind::Before(&self.closing.node), child);
}
}
View::Component(value)
}
}
impl IntoView for ComponentRepr {
#[cfg_attr(any(debug_assertions, feature = "ssr"), instrument(level = "info", name = "<Component />", skip_all, fields(name = %self.name)))]
fn into_view(self, _: Scope) -> View {
self.into()
View::Component(self)
}
}
@@ -212,7 +207,7 @@ impl ComponentRepr {
#[cfg(debug_assertions)]
_opening: markers.1,
closing: markers.0,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
name,
children: Vec::with_capacity(1),
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]

View File

@@ -158,8 +158,8 @@ where
N: IntoView,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "<DynChild />", skip_all)
debug_assertions,
instrument(level = "trace", name = "<DynChild />", skip_all)
)]
#[inline]
fn into_view(self, cx: Scope) -> View {

View File

@@ -351,8 +351,8 @@ where
T: 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "<Each />", skip_all)
debug_assertions,
instrument(level = "trace", name = "<Each />", skip_all)
)]
fn into_view(self, cx: Scope) -> crate::View {
let Self {

View File

@@ -14,10 +14,6 @@ where
I: IntoIterator<Item = V>,
V: IntoView,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
fn into_fragment(self, cx: Scope) -> Fragment {
self.into_iter().map(|v| v.into_view(cx)).collect()
}
@@ -45,21 +41,6 @@ impl From<View> for Fragment {
}
}
impl From<Fragment> for View {
fn from(value: Fragment) -> Self {
let mut frag = ComponentRepr::new_with_id("", value.id.clone());
#[cfg(debug_assertions)]
{
frag.view_marker = value.view_marker;
}
frag.children = value.nodes;
frag.into()
}
}
impl Fragment {
/// Creates a new [`Fragment`] from a [`Vec<Node>`].
#[inline(always)]
@@ -105,8 +86,17 @@ impl Fragment {
}
impl IntoView for Fragment {
#[cfg_attr(debug_assertions, instrument(level = "info", name = "</>", skip_all, fields(children = self.nodes.len())))]
fn into_view(self, _: leptos_reactive::Scope) -> View {
self.into()
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "</>", skip_all, fields(children = self.nodes.len())))]
fn into_view(self, cx: leptos_reactive::Scope) -> View {
let mut frag = ComponentRepr::new_with_id("", self.id.clone());
#[cfg(debug_assertions)]
{
frag.view_marker = self.view_marker;
}
frag.children = self.nodes;
frag.into_view(cx)
}
}

View File

@@ -62,8 +62,8 @@ pub struct Unit;
impl IntoView for Unit {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "<() />", skip_all)
debug_assertions,
instrument(level = "trace", name = "<() />", skip_all)
)]
fn into_view(self, _: leptos_reactive::Scope) -> crate::View {
let component = UnitRepr::default();

View File

@@ -1,6 +1,6 @@
//! A variety of DOM utility functions.
use crate::{events::typed as ev, is_server, window};
use crate::{is_server, window};
use leptos_reactive::{on_cleanup, Scope};
use std::time::Duration;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
@@ -196,7 +196,7 @@ impl TimeoutHandle {
/// Executes the given function after the given duration of time has passed.
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
debug_assertions,
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
@@ -206,7 +206,7 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
debug_assertions,
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
#[inline(always)]
@@ -329,7 +329,7 @@ impl IntervalHandle {
/// returning a cancelable handle.
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
debug_assertions,
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
#[deprecated = "use set_interval_with_handle() instead. In the future, \
@@ -364,7 +364,7 @@ pub fn set_interval(
/// returning a cancelable handle.
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
debug_assertions,
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
#[inline(always)]
@@ -403,30 +403,12 @@ pub fn set_interval_with_handle(
}
/// 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,
instrument(level = "trace", skip_all, fields(event_name = %event_name))
)]
#[inline(always)]
pub fn window_event_listener_untyped(
pub fn window_event_listener(
event_name: &str,
cb: impl Fn(web_sys::Event) + 'static,
) {
@@ -456,18 +438,6 @@ pub fn window_event_listener_untyped(
}
}
/// 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
E::EventType: JsCast,
{
window_event_listener_untyped(&event.name(), move |e| {
cb(e.unchecked_into::<E::EventType>())
});
}
#[doc(hidden)]
/// This exists only to enable type inference on event listeners when in SSR mode.
pub fn ssr_event_listener<E: crate::ev::EventDescriptor + 'static>(

View File

@@ -658,15 +658,6 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
}
/// Adds a class to an element.
///
/// **Note**: In the builder syntax, this will be overwritten by the `class`
/// attribute if you use `.attr("class", /* */)`. In the `view` macro, they
/// are automatically re-ordered so that this over-writing does not happen.
///
/// # Panics
/// This directly uses the browsers `classList` API, which means it will throw
/// a runtime error if you pass more than a single class name. If you want to
/// pass more than one class name at a time, you can use [HtmlElement::classes].
#[track_caller]
pub fn class(
self,
@@ -977,7 +968,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
}
impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
#[cfg_attr(any(debug_assertions, feature = "ssr"), instrument(level = "trace", name = "<HtmlElement />", skip_all, fields(tag = %self.element.name())))]
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "<HtmlElement />", skip_all, fields(tag = %self.element.name())))]
#[cfg_attr(all(target_arch = "wasm32", feature = "web"), inline(always))]
fn into_view(self, _: Scope) -> View {
#[cfg(all(target_arch = "wasm32", feature = "web"))]
@@ -1021,7 +1012,7 @@ impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
impl<El: ElementDescriptor, const N: usize> IntoView for [HtmlElement<El>; N] {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(level = "trace", name = "[HtmlElement; N]", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
@@ -1148,7 +1139,7 @@ macro_rules! generate_html_tags {
#[$meta]
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "HtmlElement",

View File

@@ -6,7 +6,7 @@
//! The DOM implementation for `leptos`.
#[doc(hidden)]
#[cfg_attr(any(debug_assertions, feature = "ssr"), macro_use)]
#[cfg_attr(debug_assertions, macro_use)]
pub extern crate tracing;
mod components;
@@ -93,8 +93,8 @@ pub trait Mountable {
impl IntoView for () {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "<() />", skip_all)
debug_assertions,
instrument(level = "trace", name = "<() />", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
Unit.into_view(cx)
@@ -106,8 +106,8 @@ where
T: IntoView,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "Option<T>", skip_all)
debug_assertions,
instrument(level = "trace", name = "Option<T>", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
if let Some(t) = self {
@@ -124,8 +124,8 @@ where
N: IntoView,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "Fn() -> impl IntoView", skip_all)
debug_assertions,
instrument(level = "trace", name = "Fn() -> impl IntoView", skip_all)
)]
#[track_caller]
fn into_view(self, cx: Scope) -> View {
@@ -149,7 +149,7 @@ where
T: IntoView + Clone,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(level = "trace", name = "ReadSignal<T>", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
@@ -162,7 +162,7 @@ where
T: IntoView + Clone,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(level = "trace", name = "RwSignal<T>", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
@@ -175,7 +175,7 @@ where
T: IntoView + Clone,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(level = "trace", name = "Memo<T>", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
@@ -188,7 +188,7 @@ where
T: IntoView + Clone,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(level = "trace", name = "Signal<T>", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
@@ -201,7 +201,7 @@ where
T: IntoView + Clone,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(level = "trace", name = "MaybeSignal<T>", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
@@ -332,7 +332,7 @@ impl Element {
}
impl IntoView for Element {
#[cfg_attr(debug_assertions, instrument(level = "info", name = "<Element />", skip_all, fields(tag = %self.name)))]
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "<Element />", skip_all, fields(tag = %self.name)))]
fn into_view(self, _: Scope) -> View {
View::Element(self)
}
@@ -443,7 +443,7 @@ impl fmt::Debug for Text {
}
impl IntoView for Text {
#[cfg_attr(debug_assertions, instrument(level = "info", name = "#text", skip_all, fields(content = %self.content)))]
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "#text", skip_all, fields(content = %self.content)))]
fn into_view(self, _: Scope) -> View {
View::Text(self)
}
@@ -505,7 +505,7 @@ impl Default for View {
}
impl IntoView for View {
#[cfg_attr(debug_assertions, instrument(level = "info", name = "Node", skip_all, fields(kind = self.kind_name())))]
#[cfg_attr(debug_assertions, instrument(level = "trace", name = "Node", skip_all, fields(kind = self.kind_name())))]
fn into_view(self, _: Scope) -> View {
self
}
@@ -519,8 +519,8 @@ impl IntoView for &View {
impl<const N: usize> IntoView for [View; N] {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "[Node; N]", skip_all)
debug_assertions,
instrument(level = "trace", name = "[Node; N]", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
Fragment::new(self.into_iter().collect()).into_view(cx)
@@ -533,12 +533,6 @@ impl IntoView for &Fragment {
}
}
impl FromIterator<View> for View {
fn from_iter<T: IntoIterator<Item = View>>(iter: T) -> Self {
iter.into_iter().collect::<Fragment>().into()
}
}
#[cfg(all(target_arch = "wasm32", feature = "web"))]
impl Mountable for View {
fn get_mountable_node(&self) -> web_sys::Node {
@@ -1004,8 +998,8 @@ api_planning! {
impl IntoView for String {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "#text", skip_all)
debug_assertions,
instrument(level = "trace", name = "#text", skip_all)
)]
#[inline(always)]
fn into_view(self, _: Scope) -> View {
@@ -1014,10 +1008,6 @@ impl IntoView for String {
}
impl IntoView for &'static str {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "#text", skip_all)
)]
#[inline(always)]
fn into_view(self, _: Scope) -> View {
View::Text(Text::new(self.into()))
@@ -1028,10 +1018,6 @@ impl<V> IntoView for Vec<V>
where
V: IntoView,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", name = "#text", skip_all)
)]
fn into_view(self, cx: Scope) -> View {
self.into_iter()
.map(|v| v.into_view(cx))

View File

@@ -26,10 +26,6 @@ type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;
/// assert!(html.contains("Hello, world!</p>"));
/// # }}
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
pub fn render_to_string<F, N>(f: F) -> String
where
F: FnOnce(Scope) -> N + 'static,
@@ -59,10 +55,6 @@ where
/// it is waiting for a resource to resolve from the server, it doesn't run it initially.
/// 3) 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(
view: impl FnOnce(Scope) -> View + 'static,
) -> impl Stream<Item = String> {
@@ -83,10 +75,6 @@ pub fn render_to_stream(
/// 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(
view: impl FnOnce(Scope) -> View + 'static,
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
@@ -112,10 +100,6 @@ pub fn render_to_stream_with_prefix(
/// 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(
view: impl FnOnce(Scope) -> View + 'static,
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
@@ -138,10 +122,6 @@ pub fn render_to_stream_with_prefix_undisposed(
/// 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(
view: impl FnOnce(Scope) -> View + 'static,
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
@@ -229,10 +209,7 @@ pub fn render_to_stream_with_prefix_undisposed_with_context(
(stream, runtime, scope)
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn fragments_to_chunks(
fragments: impl Stream<Item = (String, String)>,
) -> impl Stream<Item = String> {
@@ -266,18 +243,10 @@ fn fragments_to_chunks(
impl View {
/// Consumes the node and renders it into an HTML string.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
pub fn render_to_string(self, _cx: Scope) -> Cow<'static, str> {
self.render_to_string_helper(false)
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub(crate) fn render_to_string_helper(
self,
dont_escape_text: bool,
@@ -574,10 +543,7 @@ pub(crate) fn to_kebab_case(name: &str) -> String {
new_name
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub(crate) fn render_serializers(
serializers: FuturesUnordered<PinnedFuture<(ResourceId, String)>>,
) -> impl Stream<Item = String> {

View File

@@ -19,7 +19,6 @@ use std::{borrow::Cow, collections::VecDeque};
/// Renders a view to HTML, waiting to return until all `async` [Resource](leptos_reactive::Resource)s
/// loaded in `<Suspense/>` elements have finished loading.
#[tracing::instrument(level = "info", skip_all)]
pub async fn render_to_string_async(
view: impl FnOnce(Scope) -> View + 'static,
) -> String {
@@ -35,7 +34,6 @@ pub async fn render_to_string_async(
/// in order:
/// 1. HTML from the `view` in order, pausing to wait for each `<Suspense/>`
/// 2. any serialized [Resource](leptos_reactive::Resource)s
#[tracing::instrument(level = "info", skip_all)]
pub fn render_to_stream_in_order(
view: impl FnOnce(Scope) -> View + 'static,
) -> impl Stream<Item = String> {
@@ -50,7 +48,6 @@ pub fn render_to_stream_in_order(
///
/// `additional_context` is injected before the `view` is rendered. The `prefix` is generated
/// after the `view` is rendered, but before `<Suspense/>` nodes have resolved.
#[tracing::instrument(level = "trace", skip_all)]
pub fn render_to_stream_in_order_with_prefix(
view: impl FnOnce(Scope) -> View + 'static,
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
@@ -73,7 +70,6 @@ pub fn render_to_stream_in_order_with_prefix(
///
/// `additional_context` is injected before the `view` is rendered. The `prefix` is generated
/// after the `view` is rendered, but before `<Suspense/>` nodes have resolved.
#[tracing::instrument(level = "trace", skip_all)]
pub fn render_to_stream_in_order_with_prefix_undisposed_with_context(
view: impl FnOnce(Scope) -> View + 'static,
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
@@ -148,7 +144,6 @@ pub fn render_to_stream_in_order_with_prefix_undisposed_with_context(
(stream, runtime, scope_id)
}
#[tracing::instrument(level = "trace", skip_all)]
#[async_recursion(?Send)]
async fn handle_blocking_chunks(
tx: UnboundedSender<String>,
@@ -189,7 +184,6 @@ async fn handle_blocking_chunks(
queued_chunks
}
#[tracing::instrument(level = "trace", skip_all)]
#[async_recursion(?Send)]
async fn handle_chunks(
tx: UnboundedSender<String>,
@@ -217,13 +211,12 @@ async fn handle_chunks(
impl View {
/// Renders the view into a set of HTML chunks that can be streamed.
#[tracing::instrument(level = "trace", skip_all)]
pub fn into_stream_chunks(self, cx: Scope) -> VecDeque<StreamChunk> {
let mut chunks = VecDeque::new();
self.into_stream_chunks_helper(cx, &mut chunks, false);
chunks
}
#[tracing::instrument(level = "trace", skip_all)]
fn into_stream_chunks_helper(
self,
cx: Scope,

View File

@@ -26,7 +26,6 @@ leptos_hot_reload = { workspace = true }
server_fn_macro = { workspace = true }
convert_case = "0.6.0"
uuid = { version = "1", features = ["v4"] }
tracing = "0.1.37"
[dev-dependencies]
log = "0.4"

View File

@@ -91,7 +91,7 @@ impl Parse for Model {
// implemented manually because Vec::drain_filter is nightly only
// follows std recommended parallel
pub fn drain_filter<T>(
fn drain_filter<T>(
vec: &mut Vec<T>,
mut some_predicate: impl FnMut(&mut T) -> bool,
) {
@@ -105,7 +105,7 @@ pub fn drain_filter<T>(
}
}
pub fn convert_from_snake_case(name: &Ident) -> Ident {
fn convert_from_snake_case(name: &Ident) -> Ident {
let name_str = name.to_string();
if !name_str.is_case(Snake) {
name.clone()
@@ -157,8 +157,8 @@ impl ToTokens for Model {
quote! {
#[allow(clippy::let_with_type_underscore)]
#[cfg_attr(
any(debug_assertions, feature="ssr"),
::leptos::leptos_dom::tracing::instrument(level = "info", name = #trace_name, skip_all)
debug_assertions,
::leptos::leptos_dom::tracing::instrument(level = "trace", name = #trace_name, skip_all)
)]
},
quote! {
@@ -208,12 +208,6 @@ impl ToTokens for Model {
}
}
impl #generics ::leptos::IntoView for #props_name #generics #where_clause {
fn into_view(self, cx: ::leptos::Scope) -> ::leptos::View {
#name(cx, self).into_view(cx)
}
}
#docs
#component_fn_prop_docs
#[allow(non_snake_case, clippy::too_many_arguments)]
@@ -291,7 +285,7 @@ impl Prop {
}
#[derive(Clone)]
pub struct Docs(Vec<Attribute>);
struct Docs(Vec<Attribute>);
impl ToTokens for Docs {
fn to_tokens(&self, tokens: &mut TokenStream) {
@@ -306,7 +300,7 @@ impl ToTokens for Docs {
}
impl Docs {
pub fn new(attrs: &[Attribute]) -> Self {
fn new(attrs: &[Attribute]) -> Self {
let attrs = attrs
.iter()
.filter(|attr| attr.path == parse_quote!(doc))
@@ -316,7 +310,7 @@ impl Docs {
Self(attrs)
}
pub fn padded(&self) -> TokenStream {
fn padded(&self) -> TokenStream {
self.0
.iter()
.enumerate()
@@ -350,7 +344,7 @@ impl Docs {
.collect()
}
pub fn typed_builder(&self) -> String {
fn typed_builder(&self) -> String {
#[allow(unstable_name_collisions)]
let doc_str = self
.0
@@ -464,18 +458,10 @@ fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
let builder_docs = prop_to_doc(prop, PropDocStyle::Inline);
// Children won't need documentation in many cases
let allow_missing_docs = if name.ident == "children" {
quote!(#[allow(missing_docs)])
} else {
quote!()
};
quote! {
#docs
#builder_docs
#builder_attrs
#allow_missing_docs
#vis #name: #ty,
}
})
@@ -531,7 +517,7 @@ fn generate_component_fn_prop_docs(props: &[Prop]) -> TokenStream {
}
}
pub fn is_option(ty: &Type) -> bool {
fn is_option(ty: &Type) -> bool {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
@@ -547,7 +533,7 @@ pub fn is_option(ty: &Type) -> bool {
}
}
pub fn unwrap_option(ty: &Type) -> Type {
fn unwrap_option(ty: &Type) -> Type {
const STD_OPTION_MSG: &str =
"make sure you're not shadowing the `std::option::Option` type that \
is automatically imported from the standard prelude";
@@ -620,11 +606,12 @@ fn prop_to_doc(
PropDocStyle::List => {
let arg_ty_doc = LitStr::new(
&if !prop_opts.into {
format!("- **{}**: [`{pretty_ty}`]", quote!(#name))
format!("- **{}**: [`{}`]", quote!(#name), pretty_ty)
} else {
format!(
"- **{}**: [`impl Into<{pretty_ty}>`]({pretty_ty})",
"- **{}**: `impl`[`Into<{}>`]",
quote!(#name),
pretty_ty
)
},
name.ident.span(),

View File

@@ -5,7 +5,7 @@
extern crate proc_macro_error;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenTree};
use proc_macro2::TokenTree;
use quote::ToTokens;
use server_fn_macro::{server_macro_impl, ServerContext};
use syn::parse_macro_input;
@@ -35,7 +35,6 @@ mod view;
use template::render_template;
use view::render_view;
mod component;
mod slot;
mod template;
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
@@ -283,10 +282,6 @@ mod template;
/// ```
#[proc_macro_error::proc_macro_error]
#[proc_macro]
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn view(tokens: TokenStream) -> TokenStream {
let tokens: proc_macro2::TokenStream = tokens.into();
let mut tokens = tokens.into_iter();
@@ -628,7 +623,9 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let is_transparent = if !args.is_empty() {
let transparent = parse_macro_input!(args as syn::Ident);
if transparent != "transparent" {
let transparent_token: syn::Ident = syn::parse_quote!(transparent);
if transparent != transparent_token {
abort!(
transparent,
"only `transparent` is supported";
@@ -647,128 +644,6 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
.into()
}
/// Annotates a struct so that it can be used with your Component as a `slot`.
///
/// The `#[slot]` macro allows you to annotate plain Rust struct as component slots and use them
/// within your Leptos [component](crate::component!) properties. The struct can contain any number
/// of fields. When you use the component somewhere else, the names of the slot fields are the
/// names of the properties you use in the [view](crate::view!) macro.
///
/// Heres how you would define and use a simple Leptos component which can accept a custom slot:
/// ```rust
/// # use leptos::*;
/// use std::time::Duration;
///
/// #[slot]
/// struct HelloSlot {
/// // Same prop syntax as components.
/// #[prop(optional)]
/// children: Option<Children>,
/// }
///
/// #[component]
/// fn HelloComponent(
/// cx: Scope,
/// /// Component slot, should be passed through the <HelloSlot slot> syntax.
/// hello_slot: HelloSlot,
/// ) -> impl IntoView {
/// // mirror the children from the slot, if any were passed
/// if let Some(children) = hello_slot.children {
/// (children)(cx).into_view(cx)
/// } else {
/// ().into_view(cx)
/// }
/// }
///
/// #[component]
/// fn App(cx: Scope) -> impl IntoView {
/// view! { cx,
/// <HelloComponent>
/// <HelloSlot slot>
/// "Hello, World!"
/// </HelloSlot>
/// </HelloComponent>
/// }
/// }
/// ```
///
/// /// Here are some important details about how slots work within the framework:
/// 1. Most of the same rules from [component](crate::component!) macro should also be followed on slots.
///
/// 2. Specifying only `slot` without a name (such as in `<HelloSlot slot>`) will default the chosen slot to
/// the a snake case version of the slot struct name (`hello_slot` for `<HelloSlot>`).
///
/// 3. Event handlers cannot be specified directly on the slot.
///
/// ```compile_error
/// // ❌ This won't work
/// # use leptos::*;
///
/// #[slot]
/// struct SlotWithChildren {
/// children: Children,
/// }
///
/// #[component]
/// fn ComponentWithSlot(cx: Scope, slot: SlotWithChildren) -> impl IntoView {
/// (slot.children)(cx)
/// }
///
/// #[component]
/// fn App(cx: Scope) -> impl IntoView {
/// view! { cx,
/// <ComponentWithSlot>
/// <SlotWithChildren slot:slot on:click=move |_| {}>
/// <h1>"Hello, World!"</h1>
/// </SlotWithChildren>
/// </ComponentWithSlot>
/// }
/// }
/// ```
///
/// ```
/// // ✅ Do this instead
/// # use leptos::*;
///
/// #[slot]
/// struct SlotWithChildren {
/// children: Children,
/// }
///
/// #[component]
/// fn ComponentWithSlot(cx: Scope, slot: SlotWithChildren) -> impl IntoView {
/// (slot.children)(cx)
/// }
///
/// #[component]
/// fn App(cx: Scope) -> impl IntoView {
/// view! { cx,
/// <ComponentWithSlot>
/// <SlotWithChildren slot:slot>
/// <div on:click=move |_| {}>
/// <h1>"Hello, World!"</h1>
/// </div>
/// </SlotWithChildren>
/// </ComponentWithSlot>
/// }
/// }
/// ```
#[proc_macro_error::proc_macro_error]
#[proc_macro_attribute]
pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
if !args.is_empty() {
abort!(
Span::call_site(),
"no arguments are supported";
help = "try just `#[slot]`"
);
}
parse_macro_input!(s as slot::Model)
.into_token_stream()
.into()
}
/// Declares that a function is a [server function](https://docs.rs/server_fn/latest/server_fn/index.html).
/// This means that its body will only run on the server, i.e., when the `ssr` feature is enabled.
///
@@ -817,8 +692,7 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
/// 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/). **Note**: You should explicitly include `serde` with the
/// `derive` feature enabled in your `Cargo.toml`. You can do this by running `cargo add serde --features=derive`.
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
/// - **The `Scope` comes from the server.** Optionally, the first argument of a server function
/// can be a Leptos `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.

View File

@@ -1,346 +0,0 @@
use crate::component::{
convert_from_snake_case, drain_filter, is_option, unwrap_option, Docs,
};
use attribute_derive::Attribute as AttributeDerive;
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn::{
parse::Parse, parse_quote, Field, ItemStruct, LitStr, Type, Visibility,
};
pub struct Model {
docs: Docs,
vis: Visibility,
name: Ident,
props: Vec<Prop>,
body: ItemStruct,
}
impl Parse for Model {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut item = ItemStruct::parse(input)?;
let docs = Docs::new(&item.attrs);
let props = item
.fields
.clone()
.into_iter()
.map(Prop::new)
.collect::<Vec<_>>();
// We need to remove the `#[doc = ""]` and `#[builder(_)]`
// attrs from the function signature
drain_filter(&mut item.attrs, |attr| {
attr.path == parse_quote!(doc) || attr.path == parse_quote!(prop)
});
item.fields.iter_mut().for_each(|arg| {
drain_filter(&mut arg.attrs, |attr| {
attr.path == parse_quote!(doc)
|| attr.path == parse_quote!(prop)
});
});
Ok(Self {
docs,
vis: item.vis.clone(),
name: convert_from_snake_case(&item.ident),
props,
body: item,
})
}
}
impl ToTokens for Model {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
docs,
vis,
name,
props,
body,
} = self;
let (_, generics, where_clause) = body.generics.split_for_impl();
let prop_builder_fields = prop_builder_fields(vis, props);
let prop_docs = generate_prop_docs(props);
let builder_name_doc = LitStr::new(
&format!("Props for the [`{name}`] slot."),
name.span(),
);
let output = quote! {
#[doc = #builder_name_doc]
#[doc = ""]
#docs
#prop_docs
#[derive(::leptos::typed_builder::TypedBuilder)]
#[builder(doc)]
#vis struct #name #generics #where_clause {
#prop_builder_fields
}
impl #generics From<#name #generics> for Vec<#name #generics> #where_clause {
fn from(value: #name #generics) -> Self {
vec![value]
}
}
};
tokens.append_all(output)
}
}
struct Prop {
docs: Docs,
prop_opts: PropOpt,
name: Ident,
ty: Type,
}
impl Prop {
fn new(arg: Field) -> Self {
let prop_opts =
PropOpt::from_attributes(&arg.attrs).unwrap_or_else(|e| {
// TODO: replace with `.unwrap_or_abort()` once https://gitlab.com/CreepySkeleton/proc-macro-error/-/issues/17 is fixed
abort!(e.span(), e.to_string());
});
let name = if let Some(i) = arg.ident {
i
} else {
abort!(
arg.ident,
"only `prop: bool` style types are allowed within the \
`#[slot]` macro"
);
};
Self {
docs: Docs::new(&arg.attrs),
prop_opts,
name,
ty: arg.ty,
}
}
}
#[derive(Clone, Debug, AttributeDerive)]
#[attribute(ident = prop)]
struct PropOpt {
#[attribute(conflicts = [optional_no_strip, strip_option])]
pub optional: bool,
#[attribute(conflicts = [optional, strip_option])]
pub optional_no_strip: bool,
#[attribute(conflicts = [optional, optional_no_strip])]
pub strip_option: bool,
#[attribute(example = "5 * 10")]
pub default: Option<syn::Expr>,
pub into: bool,
}
struct TypedBuilderOpts {
default: bool,
default_with_value: Option<syn::Expr>,
strip_option: bool,
into: bool,
}
impl TypedBuilderOpts {
pub fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self {
Self {
default: opts.optional || opts.optional_no_strip,
default_with_value: opts.default.clone(),
strip_option: opts.strip_option || opts.optional && is_ty_option,
into: opts.into,
}
}
}
impl ToTokens for TypedBuilderOpts {
fn to_tokens(&self, tokens: &mut TokenStream) {
let default = if let Some(v) = &self.default_with_value {
let v = v.to_token_stream().to_string();
quote! { default_code=#v, }
} else if self.default {
quote! { default, }
} else {
quote! {}
};
let strip_option = if self.strip_option {
quote! { strip_option, }
} else {
quote! {}
};
let into = if self.into {
quote! { into, }
} else {
quote! {}
};
let setter = if !strip_option.is_empty() || !into.is_empty() {
quote! { setter(#strip_option #into) }
} else {
quote! {}
};
let output = quote! { #[builder(#default #setter)] };
tokens.append_all(output);
}
}
fn prop_builder_fields(vis: &Visibility, props: &[Prop]) -> TokenStream {
props
.iter()
.map(|prop| {
let Prop {
docs,
name,
prop_opts,
ty,
} = prop;
let builder_attrs =
TypedBuilderOpts::from_opts(prop_opts, is_option(ty));
let builder_docs = prop_to_doc(prop, PropDocStyle::Inline);
quote! {
#docs
#builder_docs
#builder_attrs
#vis #name: #ty,
}
})
.collect()
}
fn generate_prop_docs(props: &[Prop]) -> TokenStream {
let required_prop_docs = props
.iter()
.filter(|Prop { prop_opts, .. }| {
!(prop_opts.optional || prop_opts.optional_no_strip)
})
.map(|p| prop_to_doc(p, PropDocStyle::List))
.collect::<TokenStream>();
let optional_prop_docs = props
.iter()
.filter(|Prop { prop_opts, .. }| {
prop_opts.optional || prop_opts.optional_no_strip
})
.map(|p| prop_to_doc(p, PropDocStyle::List))
.collect::<TokenStream>();
let required_prop_docs = if !required_prop_docs.is_empty() {
quote! {
#[doc = "# Required Props"]
#required_prop_docs
}
} else {
quote! {}
};
let optional_prop_docs = if !optional_prop_docs.is_empty() {
quote! {
#[doc = "# Optional Props"]
#optional_prop_docs
}
} else {
quote! {}
};
quote! {
#required_prop_docs
#optional_prop_docs
}
}
#[derive(Clone, Copy)]
enum PropDocStyle {
List,
Inline,
}
fn prop_to_doc(
Prop {
docs,
name,
ty,
prop_opts,
}: &Prop,
style: PropDocStyle,
) -> TokenStream {
let ty = if (prop_opts.optional || prop_opts.strip_option) && is_option(ty)
{
unwrap_option(ty)
} else {
ty.to_owned()
};
let type_item: syn::Item = parse_quote! {
type SomeType = #ty;
};
let file = syn::File {
shebang: None,
attrs: vec![],
items: vec![type_item],
};
let pretty_ty = prettyplease::unparse(&file);
let pretty_ty = &pretty_ty[16..&pretty_ty.len() - 2];
match style {
PropDocStyle::List => {
let arg_ty_doc = LitStr::new(
&if !prop_opts.into {
format!("- **{}**: [`{}`]", quote!(#name), pretty_ty)
} else {
format!(
"- **{}**: `impl`[`Into<{}>`]",
quote!(#name),
pretty_ty
)
},
name.span(),
);
let arg_user_docs = docs.padded();
quote! {
#[doc = #arg_ty_doc]
#arg_user_docs
}
}
PropDocStyle::Inline => {
let arg_ty_doc = LitStr::new(
&if !prop_opts.into {
format!(
"**{}**: [`{}`]{}",
quote!(#name),
pretty_ty,
docs.typed_builder()
)
} else {
format!(
"**{}**: `impl`[`Into<{}>`]{}",
quote!(#name),
pretty_ty,
docs.typed_builder()
)
},
name.span(),
);
quote! {
#[builder(setter(doc = #arg_ty_doc))]
}
}
}
}

View File

@@ -1,9 +1,7 @@
use crate::{attribute_value, Mode};
use convert_case::{Case::Snake, Casing};
use leptos_hot_reload::parsing::{is_component_node, value_to_string};
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use std::collections::HashMap;
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
use syn_rsx::{Node, NodeAttribute, NodeElement, NodeName, NodeValueExpr};
@@ -151,16 +149,14 @@ pub(crate) fn render_view(
global_class: Option<&TokenTree>,
call_site: Option<String>,
) -> TokenStream {
let empty = {
let span = Span::call_site();
quote_spanned! {
span => leptos::leptos_dom::Unit
}
};
if mode == Mode::Ssr {
match nodes.len() {
0 => empty,
0 => {
let span = Span::call_site();
quote_spanned! {
span => leptos::leptos_dom::Unit
}
}
1 => {
root_node_to_tokens_ssr(cx, &nodes[0], global_class, call_site)
}
@@ -174,27 +170,28 @@ pub(crate) fn render_view(
}
} else {
match nodes.len() {
0 => empty,
0 => {
let span = Span::call_site();
quote_spanned! {
span => leptos::leptos_dom::Unit
}
}
1 => node_to_tokens(
cx,
&nodes[0],
TagType::Unknown,
None,
global_class,
call_site,
)
.unwrap_or_default(),
),
_ => fragment_to_tokens(
cx,
Span::call_site(),
nodes,
true,
TagType::Unknown,
None,
global_class,
call_site,
)
.unwrap_or(empty),
),
}
}
}
@@ -229,7 +226,6 @@ fn root_node_to_tokens_ssr(
}
Node::Element(node) => {
root_element_to_tokens_ssr(cx, node, global_class, view_marker)
.unwrap_or_default()
}
}
}
@@ -267,14 +263,9 @@ fn root_element_to_tokens_ssr(
node: &NodeElement,
global_class: Option<&TokenTree>,
view_marker: Option<String>,
) -> Option<TokenStream> {
) -> TokenStream {
if is_component_node(node) {
if let Some(slot) = get_slot(node) {
slot_to_tokens(cx, node, slot, None, global_class);
None
} else {
Some(component_to_tokens(cx, node, global_class))
}
component_to_tokens(cx, node, global_class)
} else {
let mut exprs_for_compiler = Vec::<TokenStream>::new();
@@ -284,7 +275,6 @@ fn root_element_to_tokens_ssr(
element_to_tokens_ssr(
cx,
node,
None,
&mut template,
&mut holes,
&mut chunks,
@@ -358,12 +348,12 @@ fn root_element_to_tokens_ssr(
} else {
quote! {}
};
Some(quote! {
quote! {
{
#(#exprs_for_compiler)*
::leptos::HtmlElement::from_chunks(#cx, #full_name, [#(#chunks),*])#view_marker
}
})
}
}
}
@@ -379,7 +369,6 @@ enum SsrElementChunks {
fn element_to_tokens_ssr(
cx: &Ident,
node: &NodeElement,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
template: &mut String,
holes: &mut Vec<TokenStream>,
chunks: &mut Vec<SsrElementChunks>,
@@ -388,20 +377,13 @@ fn element_to_tokens_ssr(
global_class: Option<&TokenTree>,
) {
if is_component_node(node) {
if let Some(slot) = get_slot(node) {
slot_to_tokens(cx, node, slot, parent_slots, global_class);
return;
}
let component = component_to_tokens(cx, node, global_class);
if !template.is_empty() {
chunks.push(SsrElementChunks::String {
template: std::mem::take(template),
holes: std::mem::take(holes),
})
}
chunks.push(SsrElementChunks::View(quote! {
{#component}.into_view(#cx)
}));
@@ -471,7 +453,6 @@ fn element_to_tokens_ssr(
element_to_tokens_ssr(
cx,
child,
None,
template,
holes,
chunks,
@@ -734,50 +715,22 @@ fn set_class_attribute_ssr(
}
}
#[allow(clippy::too_many_arguments)]
fn fragment_to_tokens(
cx: &Ident,
_span: Span,
nodes: &[Node],
lazy: bool,
parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>,
view_marker: Option<String>,
) -> Option<TokenStream> {
let mut slots = HashMap::new();
let has_slots = parent_slots.is_some();
) -> TokenStream {
let nodes = nodes.iter().map(|node| {
let node = node_to_tokens(cx, node, parent_type, global_class, None);
let mut nodes = nodes
.iter()
.filter_map(|node| {
let node = node_to_tokens(
cx,
node,
parent_type,
has_slots.then_some(&mut slots),
global_class,
None,
)?;
Some(quote! {
#node.into_view(#cx)
})
})
.peekable();
if nodes.peek().is_none() {
_ = nodes.collect::<Vec<_>>();
if let Some(parent_slots) = parent_slots {
for (slot, mut values) in slots.drain() {
parent_slots
.entry(slot)
.and_modify(|entry| entry.append(&mut values))
.or_insert(values);
}
quote! {
#node.into_view(#cx)
}
return None;
}
});
let view_marker = if let Some(marker) = view_marker {
quote! { .with_view_marker(#marker) }
@@ -785,7 +738,7 @@ fn fragment_to_tokens(
quote! {}
};
let tokens = if lazy {
if lazy {
quote! {
{
leptos::Fragment::lazy(|| vec![
@@ -803,28 +756,16 @@ fn fragment_to_tokens(
#view_marker
}
}
};
if let Some(parent_slots) = parent_slots {
for (slot, mut values) in slots.drain() {
parent_slots
.entry(slot)
.and_modify(|entry| entry.append(&mut values))
.or_insert(values);
}
}
Some(tokens)
}
fn node_to_tokens(
cx: &Ident,
node: &Node,
parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>,
view_marker: Option<String>,
) -> Option<TokenStream> {
) -> TokenStream {
match node {
Node::Fragment(fragment) => fragment_to_tokens(
cx,
@@ -832,32 +773,24 @@ fn node_to_tokens(
&fragment.children,
true,
parent_type,
None,
global_class,
view_marker,
),
Node::Comment(_) | Node::Doctype(_) => Some(quote! {}),
Node::Comment(_) | Node::Doctype(_) => quote! {},
Node::Text(node) => {
let value = node.value.as_ref();
Some(quote! {
quote! {
leptos::leptos_dom::html::text(#value)
})
}
}
Node::Block(node) => {
let value = node.value.as_ref();
Some(quote! { #value })
quote! { #value }
}
Node::Attribute(node) => {
Some(attribute_to_tokens(cx, node, global_class))
Node::Attribute(node) => attribute_to_tokens(cx, node, global_class),
Node::Element(node) => {
element_to_tokens(cx, node, parent_type, global_class, view_marker)
}
Node::Element(node) => element_to_tokens(
cx,
node,
parent_type,
parent_slots,
global_class,
view_marker,
),
}
}
@@ -865,17 +798,11 @@ fn element_to_tokens(
cx: &Ident,
node: &NodeElement,
mut parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>,
view_marker: Option<String>,
) -> Option<TokenStream> {
) -> TokenStream {
if is_component_node(node) {
if let Some(slot) = get_slot(node) {
slot_to_tokens(cx, node, slot, parent_slots, global_class);
None
} else {
Some(component_to_tokens(cx, node, global_class))
}
component_to_tokens(cx, node, global_class)
} else {
let tag = node.name.to_string();
let name = if is_custom_element(&tag) {
@@ -915,10 +842,7 @@ fn element_to_tokens(
};
let attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
let name = node.key.to_string();
if name.trim().starts_with("class:")
|| fancy_class_name(&name, cx, node).is_some()
{
if node.key.to_string().trim().starts_with("class:") {
None
} else {
Some(attribute_to_tokens(cx, node, global_class))
@@ -929,10 +853,7 @@ fn element_to_tokens(
});
let class_attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
let name = node.key.to_string();
if let Some((fancy, _, _)) = fancy_class_name(&name, cx, node) {
Some(fancy)
} else if name.trim().starts_with("class:") {
if node.key.to_string().trim().starts_with("class:") {
Some(attribute_to_tokens(cx, node, global_class))
} else {
None
@@ -961,16 +882,9 @@ fn element_to_tokens(
&fragment.children,
true,
parent_type,
None,
global_class,
None,
)
.unwrap_or({
let span = Span::call_site();
quote_spanned! {
span => leptos::leptos_dom::Unit
}
}),
),
false,
),
Node::Text(node) => {
@@ -1004,11 +918,9 @@ fn element_to_tokens(
cx,
node,
parent_type,
None,
global_class,
None,
)
.unwrap_or_default(),
),
false,
),
Node::Comment(_) | Node::Doctype(_) | Node::Attribute(_) => {
@@ -1030,14 +942,14 @@ fn element_to_tokens(
} else {
quote! {}
};
Some(quote! {
quote! {
#name
#(#attrs)*
#(#class_attrs)*
#global_class_expr
#(#children)*
#view_marker
})
}
}
}
@@ -1220,141 +1132,6 @@ pub(crate) fn parse_event_name(name: &str) -> (TokenStream, bool, bool) {
(event_type, is_custom, is_force_undelegated)
}
pub(crate) fn slot_to_tokens(
cx: &Ident,
node: &NodeElement,
slot: &NodeAttribute,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>,
) {
let name = slot.key.to_string();
let name = name.trim();
let name = convert_to_snake_case(if name.starts_with("slot:") {
name.replacen("slot:", "", 1)
} else {
node.name.to_string()
});
let component_name = ident_from_tag_name(&node.name);
let span = node.name.span();
let Some(parent_slots) = parent_slots else {
proc_macro_error::emit_error!(span, "slots cannot be used inside HTML elements");
return;
};
let attrs = node.attributes.iter().filter_map(|node| {
if let Node::Attribute(node) = node {
if is_slot(node) {
None
} else {
Some(node)
}
} else {
None
}
});
let props = attrs
.clone()
.filter(|attr| !attr.key.to_string().starts_with("clone:"))
.map(|attr| {
let name = &attr.key;
let value = attr
.value
.as_ref()
.map(|v| {
let v = v.as_ref();
quote! { #v }
})
.unwrap_or_else(|| quote! { #name });
quote! {
.#name(#[allow(unused_braces)] #value)
}
});
let items_to_clone = attrs
.clone()
.filter_map(|attr| {
attr.key
.to_string()
.strip_prefix("clone:")
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
})
.collect::<Vec<_>>();
let mut slots = HashMap::new();
let children = if node.children.is_empty() {
quote! {}
} else {
cfg_if::cfg_if! {
if #[cfg(debug_assertions)] {
let marker = format!("<{component_name}/>-children");
let view_marker = quote! { .with_view_marker(#marker) };
} else {
let view_marker = quote! {};
}
}
let children = fragment_to_tokens(
cx,
span,
&node.children,
true,
TagType::Unknown,
Some(&mut slots),
global_class,
None,
);
if let Some(children) = children {
let clonables = items_to_clone
.iter()
.map(|ident| quote! { let #ident = #ident.clone(); });
quote! {
.children({
#(#clonables)*
Box::new(move |#cx| #children #view_marker)
})
}
} else {
quote! {}
}
};
let slots = slots.drain().map(|(slot, values)| {
let slot = Ident::new(&slot, span);
if values.len() > 1 {
quote! {
.#slot(vec![
#(#values)*
])
}
} else {
let value = &values[0];
quote! { .#slot(#value) }
}
});
let slot = quote! {
#component_name::builder()
#(#props)*
#(#slots)*
#children
.build()
.into(),
};
parent_slots
.entry(name)
.and_modify(|entry| entry.push(slot.clone()))
.or_insert(vec![slot]);
}
pub(crate) fn component_to_tokens(
cx: &Ident,
node: &NodeElement,
@@ -1416,7 +1193,6 @@ pub(crate) fn component_to_tokens(
})
.collect::<Vec<_>>();
let mut slots = HashMap::new();
let children = if node.children.is_empty() {
quote! {}
} else {
@@ -1435,48 +1211,28 @@ pub(crate) fn component_to_tokens(
&node.children,
true,
TagType::Unknown,
Some(&mut slots),
global_class,
None,
);
if let Some(children) = children {
let clonables = items_to_clone
.iter()
.map(|ident| quote! { let #ident = #ident.clone(); });
let clonables = items_to_clone
.iter()
.map(|ident| quote! { let #ident = #ident.clone(); });
quote! {
.children({
#(#clonables)*
quote! {
.children({
#(#clonables)*
Box::new(move |#cx| #children #view_marker)
})
}
} else {
quote! {}
Box::new(move |#cx| #children #view_marker)
})
}
};
let slots = slots.drain().map(|(slot, values)| {
let slot = Ident::new(&slot, span);
if values.len() > 1 {
quote! {
.#slot(vec![
#(#values)*
])
}
} else {
let value = &values[0];
quote! { .#slot(#value) }
}
});
let component = quote! {
#name(
#cx,
::leptos::component_props_builder(&#name)
#(#props)*
#(#slots)*
#children
.build()
)
@@ -1564,34 +1320,6 @@ fn expr_to_ident(expr: &syn::Expr) -> Option<&ExprPath> {
}
}
fn is_slot(node: &NodeAttribute) -> bool {
let key = node.key.to_string();
let key = key.trim();
key == "slot" || key.starts_with("slot:")
}
fn get_slot(node: &NodeElement) -> Option<&NodeAttribute> {
node.attributes.iter().find_map(|node| {
if let Node::Attribute(node) = node {
if is_slot(node) {
Some(node)
} else {
None
}
} else {
None
}
})
}
fn convert_to_snake_case(name: String) -> String {
if !name.is_case(Snake) {
name.to_case(Snake)
} else {
name
}
}
fn is_custom_element(tag: &str) -> bool {
tag.contains('-')
}

View File

@@ -4,23 +4,16 @@ use leptos::*;
fn missing_scope() {}
#[component]
fn missing_return_type(cx: Scope) {
_ = cx;
}
fn missing_return_type(cx: Scope) {}
#[component]
fn unknown_prop_option(cx: Scope, #[prop(hello)] test: bool) -> impl IntoView {
_ = cx;
_ = test;
}
fn unknown_prop_option(cx: Scope, #[prop(hello)] test: bool) -> impl IntoView {}
#[component]
fn optional_and_optional_no_strip(
cx: Scope,
#[prop(optional, optional_no_strip)] conflicting: bool,
) -> impl IntoView {
_ = cx;
_ = conflicting;
}
#[component]
@@ -28,8 +21,6 @@ fn optional_and_strip_option(
cx: Scope,
#[prop(optional, strip_option)] conflicting: bool,
) -> impl IntoView {
_ = cx;
_ = conflicting;
}
#[component]
@@ -37,8 +28,6 @@ fn optional_no_strip_and_strip_option(
cx: Scope,
#[prop(optional_no_strip, strip_option)] conflicting: bool,
) -> impl IntoView {
_ = cx;
_ = conflicting;
}
#[component]
@@ -46,8 +35,6 @@ fn default_without_value(
cx: Scope,
#[prop(default)] default: bool,
) -> impl IntoView {
_ = cx;
_ = default;
}
#[component]
@@ -55,8 +42,6 @@ fn default_with_invalid_value(
cx: Scope,
#[prop(default= |)] default: bool,
) -> impl IntoView {
_ = cx;
_ = default;
}
fn main() {}

View File

@@ -9,45 +9,45 @@ error: this method requires a `Scope` parameter
error: return type is incorrect
--> tests/ui/component.rs:7:1
|
7 | fn missing_return_type(cx: Scope) {
7 | fn missing_return_type(cx: Scope) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: return signature must be `-> impl IntoView`
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default` and `into`
--> tests/ui/component.rs:12:42
--> tests/ui/component.rs:10:42
|
12 | fn unknown_prop_option(cx: Scope, #[prop(hello)] test: bool) -> impl IntoView {
10 | fn unknown_prop_option(cx: Scope, #[prop(hello)] test: bool) -> impl IntoView {}
| ^^^^^
error: `optional` conflicts with mutually exclusive `optional_no_strip`
--> tests/ui/component.rs:20:12
--> tests/ui/component.rs:15:12
|
20 | #[prop(optional, optional_no_strip)] conflicting: bool,
15 | #[prop(optional, optional_no_strip)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `optional` conflicts with mutually exclusive `strip_option`
--> tests/ui/component.rs:29:12
--> tests/ui/component.rs:22:12
|
29 | #[prop(optional, strip_option)] conflicting: bool,
22 | #[prop(optional, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^
error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
--> tests/ui/component.rs:38:12
--> tests/ui/component.rs:29:12
|
38 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
29 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected assignment `=`
--> tests/ui/component.rs:47:19
--> tests/ui/component.rs:36:19
|
47 | #[prop(default)] default: bool,
36 | #[prop(default)] default: bool,
| ^
error: unexpected end of input, expected one of: `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component.rs:56:22
--> tests/ui/component.rs:43:22
|
56 | #[prop(default= |)] default: bool,
43 | #[prop(default= |)] default: bool,
| ^

View File

@@ -2,23 +2,16 @@
fn missing_scope() {}
#[::leptos::component]
fn missing_return_type(cx: ::leptos::Scope) {
_ = cx;
}
fn missing_return_type(cx: ::leptos::Scope) {}
#[::leptos::component]
fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {
_ = cx;
_ = test;
}
fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {}
#[::leptos::component]
fn optional_and_optional_no_strip(
cx: Scope,
#[prop(optional, optional_no_strip)] conflicting: bool,
) -> impl IntoView {
_ = cx;
_ = conflicting;
}
#[::leptos::component]
@@ -26,8 +19,6 @@ fn optional_and_strip_option(
cx: ::leptos::Scope,
#[prop(optional, strip_option)] conflicting: bool,
) -> impl ::leptos::IntoView {
_ = cx;
_ = conflicting;
}
#[::leptos::component]
@@ -35,8 +26,6 @@ fn optional_no_strip_and_strip_option(
cx: ::leptos::Scope,
#[prop(optional_no_strip, strip_option)] conflicting: bool,
) -> impl ::leptos::IntoView {
_ = cx;
_ = conflicting;
}
#[::leptos::component]
@@ -44,8 +33,6 @@ fn default_without_value(
cx: ::leptos::Scope,
#[prop(default)] default: bool,
) -> impl ::leptos::IntoView {
_ = cx;
_ = default;
}
#[::leptos::component]
@@ -53,13 +40,10 @@ fn default_with_invalid_value(
cx: ::leptos::Scope,
#[prop(default= |)] default: bool,
) -> impl ::leptos::IntoView {
_ = cx;
_ = default;
}
#[::leptos::component]
pub fn using_the_view_macro(cx: ::leptos::Scope) -> impl ::leptos::IntoView {
_ = cx;
::leptos::view! { cx,
"ok"
}

View File

@@ -9,45 +9,45 @@ error: this method requires a `Scope` parameter
error: return type is incorrect
--> tests/ui/component_absolute.rs:5:1
|
5 | fn missing_return_type(cx: ::leptos::Scope) {
5 | fn missing_return_type(cx: ::leptos::Scope) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: return signature must be `-> impl IntoView`
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default` and `into`
--> tests/ui/component_absolute.rs:10:52
|
10 | fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {
| ^^^^^
--> tests/ui/component_absolute.rs:8:52
|
8 | fn unknown_prop_option(cx: ::leptos::Scope, #[prop(hello)] test: bool) -> impl ::leptos::IntoView {}
| ^^^^^
error: `optional` conflicts with mutually exclusive `optional_no_strip`
--> tests/ui/component_absolute.rs:18:12
--> tests/ui/component_absolute.rs:13:12
|
18 | #[prop(optional, optional_no_strip)] conflicting: bool,
13 | #[prop(optional, optional_no_strip)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `optional` conflicts with mutually exclusive `strip_option`
--> tests/ui/component_absolute.rs:27:12
--> tests/ui/component_absolute.rs:20:12
|
27 | #[prop(optional, strip_option)] conflicting: bool,
20 | #[prop(optional, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^
error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
--> tests/ui/component_absolute.rs:36:12
--> tests/ui/component_absolute.rs:27:12
|
36 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
27 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected assignment `=`
--> tests/ui/component_absolute.rs:45:19
--> tests/ui/component_absolute.rs:34:19
|
45 | #[prop(default)] default: bool,
34 | #[prop(default)] default: bool,
| ^
error: unexpected end of input, expected one of: `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component_absolute.rs:54:22
--> tests/ui/component_absolute.rs:41:22
|
54 | #[prop(default= |)] default: bool,
41 | #[prop(default= |)] default: bool,
| ^

View File

@@ -47,10 +47,6 @@ use std::any::{Any, TypeId};
/// todo!()
/// }
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
pub fn provide_context<T>(cx: Scope, value: T)
where
T: Clone + 'static,
@@ -110,10 +106,6 @@ where
/// todo!()
/// }
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
pub fn use_context<T>(cx: Scope) -> Option<T>
where
T: Clone + 'static,

View File

@@ -47,7 +47,7 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -103,7 +103,7 @@ where
/// # assert_eq!(b(), 2);
/// # }).dispose();
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -128,7 +128,7 @@ pub fn create_isomorphic_effect<T>(
#[doc(hidden)]
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -153,7 +153,7 @@ where
{
pub(crate) f: F,
pub(crate) ty: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -167,7 +167,7 @@ where
F: Fn(Option<T>) -> T,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
name = "Effect::run()",
level = "debug",

View File

@@ -67,7 +67,7 @@
//! });
//! ```
#[cfg_attr(any(debug_assertions, feature = "ssr"), macro_use)]
#[cfg_attr(debug_assertions, macro_use)]
extern crate tracing;
#[macro_use]

View File

@@ -61,7 +61,7 @@ use std::{any::Any, cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -159,7 +159,7 @@ where
pub(crate) runtime: RuntimeId,
pub(crate) id: NodeId,
pub(crate) ty: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -172,7 +172,7 @@ where
runtime: self.runtime,
id: self.id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: self.defined_at,
}
}
@@ -182,7 +182,7 @@ impl<T> Copy for Memo<T> {}
impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::get_untracked()",
@@ -200,7 +200,7 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
match self.id.try_with_no_subscription(runtime, f) {
Ok(t) => t,
Err(_) => panic_getting_dead_memo(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
@@ -209,7 +209,7 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_get_untracked()",
@@ -229,7 +229,7 @@ impl<T: Clone> SignalGetUntracked<T> for Memo<T> {
impl<T> SignalWithUntracked<T> for Memo<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::with_untracked()",
@@ -248,7 +248,7 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
match self.id.try_with_no_subscription(runtime, |v: &T| f(v)) {
Ok(t) => t,
Err(_) => panic_getting_dead_memo(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
@@ -257,7 +257,7 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_with_untracked()",
@@ -297,7 +297,7 @@ impl<T> SignalWithUntracked<T> for Memo<T> {
/// ```
impl<T: Clone> SignalGet<T> for Memo<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
name = "Memo::get()",
level = "trace",
@@ -316,7 +316,7 @@ impl<T: Clone> SignalGet<T> for Memo<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_get()",
@@ -337,7 +337,7 @@ impl<T: Clone> SignalGet<T> for Memo<T> {
impl<T> SignalWith<T> for Memo<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::with()",
@@ -354,14 +354,14 @@ impl<T> SignalWith<T> for Memo<T> {
match self.try_with(f) {
Some(t) => t,
None => panic_getting_dead_memo(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::try_with()",
@@ -392,7 +392,7 @@ impl<T> SignalWith<T> for Memo<T> {
impl<T: Clone> SignalStream<T> for Memo<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Memo::to_stream()",
@@ -439,7 +439,7 @@ where
{
pub f: F,
pub t: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -449,7 +449,7 @@ where
F: Fn(Option<&T>) -> T,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
name = "Memo::run()",
level = "debug",
@@ -489,18 +489,17 @@ where
#[track_caller]
fn format_memo_warning(
msg: &str,
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: &'static std::panic::Location<'static>,
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
) -> String {
let location = std::panic::Location::caller();
let defined_at_msg = {
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
{
format!("signal created here: {defined_at}\n")
}
#[cfg(not(any(debug_assertions, feature = "ssr")))]
#[cfg(not(debug_assertions))]
{
String::default()
}
@@ -513,14 +512,13 @@ fn format_memo_warning(
#[inline(never)]
#[track_caller]
pub(crate) fn panic_getting_dead_memo(
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: &'static std::panic::Location<'static>,
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
) -> ! {
panic!(
"{}",
format_memo_warning(
"Attempted to get a memo after it was disposed.",
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at,
)
)

View File

@@ -63,18 +63,6 @@ use std::{
/// # }
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature="ssr"),
instrument(
level = "info",
skip_all,
fields(
scope = ?cx.id,
ty = %std::any::type_name::<T>(),
signal_ty = %std::any::type_name::<S>(),
)
)
)]
pub fn create_resource<S, T, Fu>(
cx: Scope,
source: impl Fn() -> S + 'static,
@@ -100,9 +88,9 @@ where
/// serialized, or you just want to make sure the [`Future`] runs locally, use
/// [`create_local_resource_with_initial_value()`].
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "info",
level = "trace",
skip_all,
fields(
scope = ?cx.id,
@@ -147,9 +135,9 @@ where
/// **Note**: This is not “blocking” in the sense that it blocks the current thread. Rather,
/// it is blocking in the sense that it blocks the server from sending a response.
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "info",
level = "trace",
skip_all,
fields(
scope = ?cx.id,
@@ -236,7 +224,7 @@ where
id,
source_ty: PhantomData,
out_ty: PhantomData,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -272,18 +260,6 @@ where
/// # }
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature="ssr"),
instrument(
level = "info",
skip_all,
fields(
scope = ?cx.id,
ty = %std::any::type_name::<T>(),
signal_ty = %std::any::type_name::<S>(),
)
)
)]
pub fn create_local_resource<S, T, Fu>(
cx: Scope,
source: impl Fn() -> S + 'static,
@@ -306,9 +282,9 @@ where
/// on the local system and therefore its output type does not need to be
/// [`Serializable`].
#[cfg_attr(
any(debug_assertions, feature="ssr"),
debug_assertions,
instrument(
level = "info",
level = "trace",
skip_all,
fields(
scope = ?cx.id,
@@ -372,7 +348,7 @@ where
id,
source_ty: PhantomData,
out_ty: PhantomData,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -471,10 +447,6 @@ where
///
/// If you want to get the value without cloning it, use [`Resource::with`].
/// (`value.read(cx)` is equivalent to `value.with(cx, T::clone)`.)
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
#[track_caller]
pub fn read(&self, cx: Scope) -> Option<T>
where
@@ -497,10 +469,6 @@ where
///
/// If you want to get the value by cloning it, you can use
/// [`Resource::read`].
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
#[track_caller]
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
let location = std::panic::Location::caller();
@@ -514,10 +482,6 @@ where
}
/// Returns a signal that indicates whether the resource is currently loading.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn loading(&self) -> ReadSignal<bool> {
with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
@@ -531,10 +495,6 @@ where
}
/// Re-runs the async function with the current source data.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn refetch(&self) {
_ = with_runtime(self.runtime, |runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
@@ -546,10 +506,6 @@ where
/// Returns a [`Future`] that will resolve when the resource has loaded,
/// yield its [`ResourceId`] and a JSON string.
#[cfg(any(feature = "ssr", doc))]
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub async fn to_serialization_resolver(
&self,
cx: Scope,
@@ -718,7 +674,7 @@ where
pub(crate) id: ResourceId,
pub(crate) source_ty: PhantomData<S>,
pub(crate) out_ty: PhantomData<T>,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -733,17 +689,13 @@ where
S: 'static,
T: 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn clone(&self) -> Self {
Self {
runtime: self.runtime,
id: self.id,
source_ty: PhantomData,
out_ty: PhantomData,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(debug_assertions)]
defined_at: self.defined_at,
}
}
@@ -793,10 +745,6 @@ where
S: Clone + 'static,
T: 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
#[track_caller]
pub fn read(
&self,
@@ -809,10 +757,6 @@ where
self.with(cx, T::clone, location)
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "info", skip_all,)
)]
#[track_caller]
pub fn with<U>(
&self,
@@ -881,17 +825,11 @@ where
create_isomorphic_effect(cx, increment);
v
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn refetch(&self) {
self.load(true);
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn load(&self, refetching: bool) {
// doesn't refetch if already refetching
if refetching && self.scheduled.get() {
@@ -958,10 +896,7 @@ where
})
});
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn resource_to_serialization_resolver(
&self,
cx: Scope,
@@ -1022,10 +957,7 @@ where
fn as_any(&self) -> &dyn Any {
self
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn to_serialization_resolver(
&self,
cx: Scope,

View File

@@ -360,10 +360,6 @@ impl Debug for Runtime {
}
/// Get the selected runtime from the thread-local set of runtimes. On the server,
/// this will return the correct runtime. In the browser, there should only be one runtime.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)] // it monomorphizes anyway
pub(crate) fn with_runtime<T>(
id: RuntimeId,
@@ -526,14 +522,14 @@ impl RuntimeId {
runtime: self,
id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
},
WriteSignal {
runtime: self,
id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
},
)
@@ -577,14 +573,14 @@ impl RuntimeId {
runtime: self,
id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
},
WriteSignal {
runtime: self,
id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
},
)
@@ -612,7 +608,7 @@ impl RuntimeId {
runtime: self,
id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -675,7 +671,7 @@ impl RuntimeId {
Rc::new(Effect {
f,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}),
)
@@ -697,12 +693,12 @@ impl RuntimeId {
Rc::new(MemoState {
f,
t: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}),
),
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -730,10 +726,7 @@ impl Runtime {
.borrow_mut()
.insert(AnyResource::Serializable(state))
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub(crate) fn resource<S, T, U>(
&self,
id: ResourceId,

View File

@@ -37,10 +37,6 @@ pub fn create_scope(
/// values will not have access to values created under another `create_scope`.
///
/// You usually don't need to call this manually.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
runtime.raw_scope_and_disposer()
}
@@ -52,10 +48,6 @@ pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
/// of the synchronous operation.
///
/// You usually don't need to call this manually.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn run_scope<T>(
runtime: RuntimeId,
f: impl FnOnce(Scope) -> T + 'static,
@@ -69,10 +61,6 @@ pub fn run_scope<T>(
/// If you do not dispose of the scope on your own, memory will leak.
///
/// You usually don't need to call this manually.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn run_scope_undisposed<T>(
runtime: RuntimeId,
f: impl FnOnce(Scope) -> T + 'static,
@@ -128,10 +116,6 @@ impl Scope {
/// This is useful for applications like a list or a router, which may want to create child scopes and
/// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user
/// has navigated away from the route.)
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
pub fn child_scope(self, f: impl FnOnce(Scope)) -> ScopeDisposer {
let (_, disposer) = self.run_child_scope(f);
@@ -147,10 +131,6 @@ impl Scope {
/// This is useful for applications like a list or a router, which may want to create child scopes and
/// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user
/// has navigated away from the route.)
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
pub fn run_child_scope<T>(
self,
@@ -203,10 +183,6 @@ impl Scope {
///
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
pub fn untrack<T>(&self, f: impl FnOnce() -> T) -> T {
with_runtime(self.runtime, |runtime| {
@@ -252,10 +228,6 @@ impl Scope {
/// 1. dispose of all child `Scope`s
/// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup).
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn dispose(self) {
_ = with_runtime(self.runtime, |runtime| {
// dispose of all child scopes
@@ -329,10 +301,7 @@ impl Scope {
}
})
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub(crate) fn push_scope_property(&self, prop: ScopeProperty) {
_ = with_runtime(self.runtime, |runtime| {
let scopes = runtime.scopes.borrow();
@@ -345,10 +314,7 @@ impl Scope {
}
})
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
/// Returns the the parent Scope, if any.
pub fn parent(&self) -> Option<Scope> {
match with_runtime(self.runtime, |runtime| {
@@ -363,10 +329,6 @@ impl Scope {
}
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn push_cleanup(cx: Scope, cleanup_fn: Box<dyn FnOnce()>) {
_ = with_runtime(cx.runtime, |runtime| {
let mut cleanups = runtime.scope_cleanups.borrow_mut();
@@ -426,10 +388,6 @@ impl ScopeDisposer {
impl Scope {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn all_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.all_resources())
.unwrap_or_default()
@@ -437,20 +395,12 @@ impl Scope {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope that are
/// pending from the server.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn pending_resources(&self) -> Vec<ResourceId> {
with_runtime(self.runtime, |runtime| runtime.pending_resources())
.unwrap_or_default()
}
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn serialization_resolvers(
&self,
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
@@ -462,10 +412,6 @@ impl Scope {
/// Registers the given [`SuspenseContext`](crate::SuspenseContext) with the current scope,
/// calling the `resolver` when its resources are all resolved.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn register_suspense(
&self,
context: SuspenseContext,
@@ -519,10 +465,6 @@ impl Scope {
///
/// The keys are hydration IDs. Values are tuples of two pinned
/// `Future`s that return content for out-of-order and in-order streaming, respectively.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn pending_fragments(&self) -> HashMap<String, FragmentData> {
with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
@@ -532,10 +474,6 @@ impl Scope {
}
/// A future that will resolve when all blocking fragments are ready.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn blocking_fragments_ready(self) -> PinnedFuture<()> {
use futures::StreamExt;
@@ -559,10 +497,6 @@ impl Scope {
///
/// Returns a tuple of two pinned `Future`s that return content for out-of-order
/// and in-order streaming, respectively.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn take_pending_fragment(&self, id: &str) -> Option<FragmentData> {
with_runtime(self.runtime, |runtime| {
let mut shared_context = runtime.shared_context.borrow_mut();
@@ -578,10 +512,6 @@ impl Scope {
///
/// # Panics
/// Panics if the runtime this scope belongs to has already been disposed.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
pub fn batch<T>(&self, f: impl FnOnce() -> T) -> T {
with_runtime(self.runtime, move |runtime| {

View File

@@ -332,7 +332,7 @@ pub trait SignalDispose {
/// #
/// ```
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -354,7 +354,7 @@ pub fn create_signal<T>(
/// Works exactly as [`create_signal`], but creates multiple signals at once.
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -374,7 +374,7 @@ pub fn create_many_signals<T>(
/// Works exactly as [`create_many_signals`], but applies the map function to each signal pair.
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -404,7 +404,7 @@ where
/// **Note**: If used on the server side during server rendering, this will return `None`
/// immediately and not begin driving the stream.
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -496,13 +496,13 @@ where
pub(crate) runtime: RuntimeId,
pub(crate) id: NodeId,
pub(crate) ty: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
impl<T: Clone> SignalGetUntracked<T> for ReadSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::get_untracked()",
@@ -522,14 +522,14 @@ impl<T: Clone> SignalGetUntracked<T> for ReadSignal<T> {
{
Ok(t) => t,
Err(_) => panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::try_get_untracked()",
@@ -553,7 +553,7 @@ impl<T: Clone> SignalGetUntracked<T> for ReadSignal<T> {
impl<T> SignalWithUntracked<T> for ReadSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::with_untracked()",
@@ -571,7 +571,7 @@ impl<T> SignalWithUntracked<T> for ReadSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::try_with_untracked()",
@@ -617,7 +617,7 @@ impl<T> SignalWithUntracked<T> for ReadSignal<T> {
/// ```
impl<T> SignalWith<T> for ReadSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::with()",
@@ -641,14 +641,14 @@ impl<T> SignalWith<T> for ReadSignal<T> {
{
Ok(o) => o,
Err(_) => panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::try_with()",
@@ -688,7 +688,7 @@ impl<T> SignalWith<T> for ReadSignal<T> {
/// ```
impl<T: Clone> SignalGet<T> for ReadSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::get()",
@@ -711,14 +711,14 @@ impl<T: Clone> SignalGet<T> for ReadSignal<T> {
{
Ok(t) => t,
Err(_) => panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::try_get()",
@@ -737,7 +737,7 @@ impl<T: Clone> SignalGet<T> for ReadSignal<T> {
impl<T: Clone> SignalStream<T> for ReadSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "ReadSignal::to_stream()",
@@ -862,7 +862,7 @@ where
pub(crate) runtime: RuntimeId,
pub(crate) id: NodeId,
pub(crate) ty: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -871,7 +871,7 @@ where
T: 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "WriteSignal::set_untracked()",
@@ -889,7 +889,7 @@ where
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "WriteSignal::try_set_untracked()",
@@ -913,7 +913,7 @@ where
impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "WriteSignal::updated_untracked()",
@@ -931,7 +931,7 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "WriteSignal::update_returning_untracked()",
@@ -978,7 +978,7 @@ impl<T> SignalUpdateUntracked<T> for WriteSignal<T> {
/// ```
impl<T> SignalUpdate<T> for WriteSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
name = "WriteSignal::update()",
level = "trace",
@@ -994,14 +994,14 @@ impl<T> SignalUpdate<T> for WriteSignal<T> {
fn update(&self, f: impl FnOnce(&mut T)) {
if self.id.update(self.runtime, f).is_none() {
warn_updating_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
);
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
name = "WriteSignal::try_update()",
level = "trace",
@@ -1038,7 +1038,7 @@ impl<T> SignalUpdate<T> for WriteSignal<T> {
/// ```
impl<T> SignalSet<T> for WriteSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "WriteSignal::set()",
@@ -1055,7 +1055,7 @@ impl<T> SignalSet<T> for WriteSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "WriteSignal::try_set()",
@@ -1113,7 +1113,7 @@ impl<T> Copy for WriteSignal<T> {}
/// #
/// ```
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -1180,7 +1180,7 @@ where
pub(crate) runtime: RuntimeId,
pub(crate) id: NodeId,
pub(crate) ty: PhantomData<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -1194,7 +1194,7 @@ impl<T> Copy for RwSignal<T> {}
impl<T: Clone> SignalGetUntracked<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::get_untracked()",
@@ -1211,7 +1211,7 @@ impl<T: Clone> SignalGetUntracked<T> for RwSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_get_untracked()",
@@ -1231,7 +1231,7 @@ impl<T: Clone> SignalGetUntracked<T> for RwSignal<T> {
{
Ok(t) => t,
Err(_) => panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
@@ -1240,7 +1240,7 @@ impl<T: Clone> SignalGetUntracked<T> for RwSignal<T> {
impl<T> SignalWithUntracked<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::with_untracked()",
@@ -1258,7 +1258,7 @@ impl<T> SignalWithUntracked<T> for RwSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_with_untracked()",
@@ -1286,7 +1286,7 @@ impl<T> SignalWithUntracked<T> for RwSignal<T> {
impl<T> SignalSetUntracked<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::set_untracked()",
@@ -1304,7 +1304,7 @@ impl<T> SignalSetUntracked<T> for RwSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_set_untracked()",
@@ -1328,7 +1328,7 @@ impl<T> SignalSetUntracked<T> for RwSignal<T> {
impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::update_untracked()",
@@ -1346,7 +1346,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
}
#[cfg_attr(
any(debug_assertions, features="ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::update_returning_untracked()",
@@ -1367,7 +1367,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_update_untracked()",
@@ -1409,7 +1409,7 @@ impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
/// ```
impl<T> SignalWith<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::with()",
@@ -1433,14 +1433,14 @@ impl<T> SignalWith<T> for RwSignal<T> {
{
Ok(o) => o,
Err(_) => panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_with()",
@@ -1481,7 +1481,7 @@ impl<T> SignalWith<T> for RwSignal<T> {
/// ```
impl<T: Clone> SignalGet<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::get()",
@@ -1507,14 +1507,14 @@ impl<T: Clone> SignalGet<T> for RwSignal<T> {
{
Ok(t) => t,
Err(_) => panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
),
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_get()",
@@ -1561,7 +1561,7 @@ impl<T: Clone> SignalGet<T> for RwSignal<T> {
/// ```
impl<T> SignalUpdate<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::update()",
@@ -1577,14 +1577,14 @@ impl<T> SignalUpdate<T> for RwSignal<T> {
fn update(&self, f: impl FnOnce(&mut T)) {
if self.id.update(self.runtime, f).is_none() {
warn_updating_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
self.defined_at,
);
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_update()",
@@ -1616,7 +1616,7 @@ impl<T> SignalUpdate<T> for RwSignal<T> {
/// ```
impl<T> SignalSet<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::set()",
@@ -1633,7 +1633,7 @@ impl<T> SignalSet<T> for RwSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::try_set()",
@@ -1697,7 +1697,7 @@ impl<T> RwSignal<T> {
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::read_only()",
@@ -1715,7 +1715,7 @@ impl<T> RwSignal<T> {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -1735,7 +1735,7 @@ impl<T> RwSignal<T> {
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::write_only()",
@@ -1753,7 +1753,7 @@ impl<T> RwSignal<T> {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -1772,7 +1772,7 @@ impl<T> RwSignal<T> {
/// # }).dispose();
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "RwSignal::split()",
@@ -1791,14 +1791,14 @@ impl<T> RwSignal<T> {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
},
WriteSignal {
runtime: self.runtime,
id: self.id,
ty: PhantomData,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
},
)
@@ -2028,18 +2028,17 @@ impl NodeId {
#[track_caller]
fn format_signal_warning(
msg: &str,
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: &'static std::panic::Location<'static>,
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
) -> String {
let location = std::panic::Location::caller();
let defined_at_msg = {
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
{
format!("signal created here: {defined_at}\n")
}
#[cfg(not(any(debug_assertions, feature = "ssr")))]
#[cfg(not(debug_assertions))]
{
String::default()
}
@@ -2052,14 +2051,13 @@ fn format_signal_warning(
#[inline(never)]
#[track_caller]
pub(crate) fn panic_getting_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: &'static std::panic::Location<'static>,
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
) -> ! {
panic!(
"{}",
format_signal_warning(
"Attempted to get a signal after it was disposed.",
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at,
)
)
@@ -2069,12 +2067,11 @@ pub(crate) fn panic_getting_dead_signal(
#[inline(never)]
#[track_caller]
pub(crate) fn warn_updating_dead_signal(
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: &'static std::panic::Location<'static>,
#[cfg(debug_assertions)] defined_at: &'static std::panic::Location<'static>,
) {
console_warn(&format_signal_warning(
"Attempted to update a signal after it was disposed.",
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at,
));
}

View File

@@ -66,7 +66,7 @@ where
T: 'static,
{
inner: SignalTypes<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: &'static std::panic::Location<'static>,
}
@@ -74,7 +74,7 @@ impl<T> Clone for Signal<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: self.defined_at,
}
}
@@ -87,7 +87,7 @@ impl<T> Copy for Signal<T> {}
/// `Signal::get_untracked`.
impl<T: Clone> SignalGetUntracked<T> for Signal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Signal::get_untracked()",
@@ -109,7 +109,7 @@ impl<T: Clone> SignalGetUntracked<T> for Signal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Signal::try_get_untracked()",
@@ -133,7 +133,7 @@ impl<T: Clone> SignalGetUntracked<T> for Signal<T> {
impl<T> SignalWithUntracked<T> for Signal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Signal::with_untracked()",
@@ -159,7 +159,7 @@ impl<T> SignalWithUntracked<T> for Signal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Signal::try_with_untracked()",
@@ -212,7 +212,7 @@ impl<T> SignalWithUntracked<T> for Signal<T> {
/// ```
impl<T> SignalWith<T> for Signal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Signal::with()",
@@ -232,7 +232,7 @@ impl<T> SignalWith<T> for Signal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "Signal::try_with()",
@@ -338,7 +338,7 @@ where
/// ```
#[track_caller]
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -360,7 +360,7 @@ where
cx,
store_value(cx, Box::new(derived_signal)),
),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -380,7 +380,7 @@ impl<T> From<ReadSignal<T>> for Signal<T> {
fn from(value: ReadSignal<T>) -> Self {
Self {
inner: SignalTypes::ReadSignal(value),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -391,7 +391,7 @@ impl<T> From<RwSignal<T>> for Signal<T> {
fn from(value: RwSignal<T>) -> Self {
Self {
inner: SignalTypes::ReadSignal(value.read_only()),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -402,7 +402,7 @@ impl<T> From<Memo<T>> for Signal<T> {
fn from(value: Memo<T>) -> Self {
Self {
inner: SignalTypes::Memo(value),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -606,7 +606,7 @@ impl<T: Clone> SignalGet<T> for MaybeSignal<T> {
/// ```
impl<T> SignalWith<T> for MaybeSignal<T> {
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::with()",
@@ -622,7 +622,7 @@ impl<T> SignalWith<T> for MaybeSignal<T> {
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::try_with()",
@@ -711,7 +711,7 @@ where
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
name = "MaybeSignal::derive()",

View File

@@ -57,7 +57,7 @@ where
T: 'static,
{
inner: SignalSetterTypes<T>,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: &'static std::panic::Location<'static>,
}
@@ -65,7 +65,7 @@ impl<T> Clone for SignalSetter<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: self.defined_at,
}
}
@@ -76,7 +76,7 @@ impl<T: Default + 'static> Default for SignalSetter<T> {
fn default() -> Self {
Self {
inner: SignalSetterTypes::Default,
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -138,7 +138,7 @@ where
/// ```
#[track_caller]
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -153,7 +153,7 @@ where
cx,
store_value(cx, Box::new(mapped_setter)),
),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -179,7 +179,7 @@ where
/// assert_eq!(count(), 8);
/// # });
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
debug_assertions,
instrument(
level = "trace",
skip_all,
@@ -203,7 +203,7 @@ impl<T> From<WriteSignal<T>> for SignalSetter<T> {
fn from(value: WriteSignal<T>) -> Self {
Self {
inner: SignalSetterTypes::Write(value),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}
@@ -214,7 +214,7 @@ impl<T> From<RwSignal<T>> for SignalSetter<T> {
fn from(value: RwSignal<T>) -> Self {
Self {
inner: SignalSetterTypes::Write(value.write_only()),
#[cfg(any(debug_assertions, feature = "ssr"))]
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
}
}

View File

@@ -191,10 +191,8 @@ impl<T> StoredValue<T> {
/// the signal is still valid. [`None`] otherwise.
pub fn try_with_value<O>(&self, f: impl FnOnce(&T) -> O) -> Option<O> {
with_runtime(self.runtime, |runtime| {
let value = {
let values = runtime.stored_values.borrow();
values.get(self.id)?.clone()
};
let values = runtime.stored_values.borrow();
let value = values.get(self.id)?;
let value = value.borrow();
let value = value.downcast_ref::<T>()?;
Some(f(value))
@@ -409,7 +407,6 @@ impl<T> StoredValue<T> {
/// let callback_b = move || data.with(|data| data.value == "b");
/// # }).dispose();
/// ```
#[track_caller]
pub fn store_value<T>(cx: Scope, value: T) -> StoredValue<T>
where
T: 'static,

View File

@@ -14,7 +14,6 @@ server_fn = { workspace = true, default-features = false }
lazy_static = "1"
serde = { version = "1", features = ["derive"] }
thiserror = "1"
tracing = "0.1"
[dev-dependencies]
leptos = { path = "../leptos" }

View File

@@ -89,28 +89,16 @@ where
O: 'static,
{
/// Calls the `async` function with a reference to the input type as its argument.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
self.0.with_value(|a| a.dispatch(input))
}
/// Whether the action has been dispatched and is currently waiting for its future to be resolved.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn pending(&self) -> ReadSignal<bool> {
self.0.with_value(|a| a.pending.read_only())
}
/// Updates whether the action is currently pending.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn set_pending(&self, pending: bool) {
self.0.try_with_value(|a| a.pending.set(pending));
}
@@ -123,10 +111,6 @@ where
/// Associates the URL of the given server function with this action.
/// This enables integration with the `ActionForm` component in `leptos_router`.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn using_server_fn<T: ServerFn>(self) -> Self {
let prefix = T::prefix();
self.0.update_value(|state| {
@@ -146,19 +130,11 @@ where
/// The current argument that was dispatched to the `async` function.
/// `Some` while we are waiting for it to resolve, `None` if it has resolved.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn input(&self) -> RwSignal<Option<I>> {
self.0.with_value(|a| a.input)
}
/// The most recent return value of the `async` function.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn value(&self) -> RwSignal<Option<O>> {
self.0.with_value(|a| a.value)
}
@@ -205,10 +181,6 @@ where
O: 'static,
{
/// Calls the `async` function with a reference to the input type as its argument.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
let fut = (self.action_fn)(&input);
self.input.set(Some(input));
@@ -301,10 +273,6 @@ where
/// create_action(cx, |input: &(usize, String)| async { todo!() });
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_action<I, O, F, Fu>(cx: Scope, action_fn: F) -> Action<I, O>
where
I: 'static,
@@ -348,10 +316,6 @@ where
/// let my_server_action = create_server_action::<MyServerFn>(cx);
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_server_action<S>(
cx: Scope,
) -> Action<S, Result<S::Output, ServerFnError>>

View File

@@ -73,8 +73,7 @@
//! 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/). **Note**: You should explicitly include `serde` with the
//! `derive` feature enabled in your `Cargo.toml`. You can do this by running `cargo add serde --features=derive`.
//! 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.
@@ -86,8 +85,6 @@ mod action;
mod multi_action;
pub use action::*;
pub use multi_action::*;
extern crate tracing;
#[cfg(any(feature = "ssr", doc))]
use std::{
collections::HashMap,

View File

@@ -94,19 +94,11 @@ where
O: 'static,
{
/// Calls the `async` function with a reference to the input type as its argument.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
self.0.with_value(|a| a.dispatch(input))
}
/// The set of all submissions to this multi-action.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
self.0.with_value(|a| a.submissions())
}
@@ -118,20 +110,12 @@ where
}
/// How many times an action has successfully resolved.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn version(&self) -> RwSignal<usize> {
self.0.with_value(|a| a.version)
}
/// Associates the URL of the given server function with this action.
/// This enables integration with the `MultiActionForm` component in `leptos_router`.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn using_server_fn<T: ServerFn>(self) -> Self {
let prefix = T::prefix();
self.0.update_value(|a| {
@@ -211,10 +195,6 @@ where
O: 'static,
{
/// Calls the `async` function with a reference to the input type as its argument.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
let cx = self.cx;
let fut = (self.action_fn)(&input);
@@ -303,10 +283,6 @@ where
/// create_multi_action(cx, |input: &(usize, String)| async { todo!() });
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_multi_action<I, O, F, Fu>(
cx: Scope,
action_fn: F,
@@ -350,10 +326,6 @@ where
/// let my_server_multi_action = create_server_multi_action::<MyServerFn>(cx);
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_server_multi_action<S>(
cx: Scope,
) -> MultiAction<S, Result<S::Output, ServerFnError>>

View File

@@ -10,7 +10,7 @@ description = "Router for the Leptos web framework."
[dependencies]
leptos = { workspace = true }
cached = { version = "0.43.0", optional = true }
cached = { optional = true }
cfg-if = "1"
common_macros = "0.1"
gloo-net = { version = "0.2", features = ["http"] }

View File

@@ -10,10 +10,6 @@ type OnResponse = Rc<dyn Fn(&web_sys::Response)>;
/// An HTML [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) progressively
/// enhanced to use client-side routing.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
#[component]
pub fn Form<A>(
cx: Scope,
@@ -206,10 +202,6 @@ where
/// Automatically turns a server [Action](leptos_server::Action) into an HTML
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
/// progressively enhanced to use client-side routing.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
#[component]
pub fn ActionForm<I, O>(
cx: Scope,
@@ -336,10 +328,6 @@ where
/// Automatically turns a server [MultiAction](leptos_server::MultiAction) into an HTML
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
/// progressively enhanced to use client-side routing.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
#[component]
pub fn MultiActionForm<I, O>(
cx: Scope,
@@ -420,10 +408,7 @@ where
}
form
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
fn extract_form_attributes(
ev: &web_sys::Event,
) -> (web_sys::HtmlFormElement, String, String, String) {
@@ -551,10 +536,6 @@ impl<T> FromFormData for T
where
T: serde::de::DeserializeOwned,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
fn from_event(
ev: &web_sys::Event,
) -> Result<Self, serde_urlencoded::de::Error> {
@@ -564,10 +545,7 @@ where
Self::from_form_data(&form_data)
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
fn from_form_data(
form_data: &web_sys::FormData,
) -> Result<Self, serde_urlencoded::de::Error> {

View File

@@ -42,10 +42,6 @@ where
/// 2) Sets the `aria-current` attribute if this link is the active link (i.e., its a link to the page youre on).
/// This is helpful for accessibility and for styling. For example, maybe you want to set the link a
/// different color if its a link to the page youre currently on.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all,)
)]
#[component]
pub fn A<H>(
cx: Scope,
@@ -75,10 +71,6 @@ pub fn A<H>(
where
H: ToHref + 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
fn inner(
cx: Scope,
href: Memo<Option<String>>,

View File

@@ -8,10 +8,6 @@ use web_sys::AnimationEvent;
/// 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.
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all,)
)]
#[component]
pub fn Outlet(cx: Scope) -> impl IntoView {
let id = HydrationCtx::id();
@@ -170,25 +166,14 @@ pub fn AnimatedOutlet(
animation_class.to_string()
}
};
let node_ref = create_node_ref::<html::Div>(cx);
let animationend = move |ev: AnimationEvent| {
use wasm_bindgen::JsCast;
if let Some(target) = ev.target() {
let node_ref = node_ref.get();
if node_ref.is_none()
|| target
.unchecked_ref::<web_sys::Node>()
.is_same_node(Some(&*node_ref.unwrap()))
{
ev.stop_propagation();
let current = current_animation.get();
set_animation_state.update(|current_state| {
let (next, _) =
animation.next_state(&current, is_back.get_untracked());
*current_state = next;
});
}
}
ev.stop_propagation();
let current = current_animation.get();
set_animation_state.update(|current_state| {
let (next, _) =
animation.next_state(&current, is_back.get_untracked());
*current_state = next;
});
};
view! { cx,

Some files were not shown because too many files have changed in this diff Show More