Compare commits

..

56 Commits

Author SHA1 Message Date
Greg Johnston
ab69750c01 fmt 2024-07-29 08:37:47 -04:00
Greg Johnston
b90fe273d5 remove unused import 2024-07-29 08:28:36 -04:00
Greg Johnston
5f0fab9f63 fix: untrack children in Portal to avoid re-triggering it accidentally 2024-07-24 12:23:07 -04:00
Greg Johnston
8848eb8b87 0.6.13 2024-07-24 08:00:11 -04:00
Ar4ys
7e75801f7c fix: move lint rules outside of quote_spanned (closes #2527) (#2709) 2024-07-24 07:54:46 -04:00
Dreo
0763a81cf1 fix: remove unnecessary 'static lifetime from argument in function Style::as_value_string() (#2683) 2024-07-12 11:53:48 -04:00
Dreo
3d37f08539 add impl IntoStyle for Style (#2682) 2024-07-12 11:13:29 -04:00
Oleg Shatov
b3db094618 chore: remove cfg-related warnings (#2654) 2024-07-03 06:49:32 -04:00
Oleg Shatov
0c817d51fe fix: accurately update number of pending action dispatches (closes #2652) (#2653) 2024-07-03 06:48:02 -04:00
Chris
fb5d8513ff docs: generate link to definition (#2656) 2024-07-03 06:46:02 -04:00
David Karrick
c53fc67d38 feat: Add Compression to Hacker News w/ Islands Example (#2613)
* Add task for cargo leptos w/ precompression

* Update makefile

* Update deps

* Serve precompressed assets

Code was taken from https://github.com/leptos-rs/cargo-leptos/pull/165#issuecomment-1647843037

Co-authored-by: Sebastian Dobe <sebastiandobe@mailbox.org>

* Dynamically compress html

* Update README

* Refactor: Format for ci

* Refactor: Replace use of format!

* Chore: Remove old build file

* Feat: Hash files

This will prevent users from using an old cached file after updates are made

* Fix: Prevent chicken & egg problem with target/site

* Refactor: Use normal cargo-leptos

---------

Co-authored-by: Sebastian Dobe <sebastiandobe@mailbox.org>
2024-06-28 15:01:05 -04:00
Greg Johnston
ff0c8252b0 fix: do not unescape / and other route characters when following a link (#2651) 2024-06-28 14:29:05 -04:00
Evan Almloff
551f9b0a04 feat: add a StreamingJson encoding (#2623) 2024-06-28 11:49:26 -04:00
Greg Johnston
44cd3272f9 Merge pull request #2639 from Giovanni-Tably/dispose-fix
fix: ensure everything is disposed of consistently
2024-06-28 11:30:31 -04:00
Vasily Zorin
73a9797ef9 book_ru: SUMMARY.md (#2648)
* book_ru: SUMMARY.md

* book_ru: SUMMARY.md

* book_ru: SUMMARY.md
2024-06-27 06:46:36 -07:00
Spencer Ferris
57a00a33a3 docs: Add docs for ToChildren (#2643)
* docs: Add docs for `ToChildren`

As discussed in https://github.com/leptos-rs/leptos/discussions/2640,
the `ToChildren` trait is useful to consumers who want to use the
builder syntax. However, because it is currently annotated with
`#[docs(hidden)]`, it's not visible in docs and also not included in
Jetbrains's auto-complete.

Add a doc comment for the `ToChildren` trait, including doc tests that
demonstrate how to use the trait and how it compares to directly
creating children.

* docs: Fix incorrect examples in `ToChildren` docs

Some examples were added to `ToChildren` that don't compile. This
wasn't caught earlier because no errors were seen in the IDE when
writing the examples. The issue was correctly caught by CI, however.
2024-06-26 14:57:40 -07:00
Adrian
5f445cdfbf Translating titles of sections in SUMMARY (#2542) 2024-06-26 14:56:59 -07:00
Hamir Mahal
c9d0ef5033 chore: simplify string interpolation (#2626) 2024-06-21 07:51:38 -04:00
Giovanni
af85623a22 test: add regression tests 2024-06-20 19:37:08 +01:00
Giovanni
40ecc2bd78 fix: dispose of watch effect normally 2024-06-20 15:36:03 +01:00
Giovanni
41a18a1218 fix: clean up recursively in dispose_node 2024-06-20 15:26:55 +01:00
Giovanni
739d1b2e3e refactor: split a couple of functions 2024-06-20 15:26:37 +01:00
Giovanni
9e6996a59f fix: untrack around all on_cleanups 2024-06-20 15:26:37 +01:00
Giovanni
cca3f1f42d refactor: rename cleanup_property -> dispose_property
The property and its node are removed entirely, so it's more aligned with `dispose_node` than `cleanup_node`.
2024-06-20 15:26:37 +01:00
Greg Johnston
80bbb20089 Merge pull request #2631 from leptos-rs/2610
fix `rkyv` feature interaction with Axum integration
2024-06-14 15:10:26 -04:00
Greg Johnston
33e7ed83cc fix: specify correct serialization trait in server fn handler (closes #2610) 2024-06-14 14:20:50 -04:00
Greg Johnston
dcaa1df63d fix: derive rkyv traits on ServerFnError 2024-06-14 14:19:54 -04:00
Oto Petřík
8606f3d928 fix: try_with should not panic on disposed resources (closes #2620) (#2621) 2024-06-12 20:19:44 -04:00
Thomas Versteeg
32e6ac7bb7 docs: remove duplicated code block in example of For (#2622) 2024-06-12 20:11:57 -04:00
ARSON
b22f3bb3bd fix: extract dyn_bindings impl into DynBindings trait (#2619) 2024-06-12 09:07:56 -04:00
Greg Johnston
00a42daa63 Merge pull request #2611 from leptos-rs/failing-ci
Fix failing CI
2024-06-02 15:39:58 -04:00
Greg Johnston
ec19c59850 chore: update hackernews_js_fetch example to latest versions of leptos and axum 2024-06-02 15:39:01 -04:00
Greg Johnston
b06097d085 chore(ci): fix wasm-pack installation 2024-06-02 15:38:22 -04:00
Greg Johnston
a59561f796 chore: clippy 2024-06-02 15:37:54 -04:00
Greg Johnston
96b448805d v0.6.12 2024-06-02 14:08:08 -04:00
Luxalpa
2ef27cb0bb fix: URL encoding issue (closes #2602) (#2601) 2024-06-02 14:06:41 -04:00
SleeplessOne1917
21a6551ce6 feat: allow slice! macro to index tuples (#2598)
* Allow slice! macro to index tuples

* Undo changes to component tests

---------

Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
2024-05-29 09:07:41 -04:00
Mingwei Samuel
2f4fd87c05 feat: #[component] now handles impl Trait by converting to generic type params, fix #2274 (#2599)
Book needs to be updated to remove this line:
35c380ffc8/src/view/03_components.md (L233)
2024-05-29 09:06:52 -04:00
Hecatron
13ad1b235d projects: example using the bevy 3d game engine and leptos (#2577)
* feat: Added example using the bevy 3d game engine and leptos

* fix: moved example to projects

* workspace fix
2024-05-27 15:55:27 -04:00
David Pitoniak
a2c7e23d54 docs: grammar typo for MultiActon doc comment (#2589) 2024-05-11 15:05:35 -04:00
Greg Johnston
9e65f71db4 fix: only issue NodeRef warning in debug mode (necessary to compile in --release) (#2587) 2024-05-11 15:05:17 -04:00
Luxalpa
7f4a2926c1 fix: StoredValue and Resource borrowMut error during dispose (#2583) 2024-05-11 15:04:57 -04:00
Hecatron
7c5203db19 examples: counter with DWARF debugging (breakpoints and sourcemap) (#2563)
* feat: Added initial dwarf debug counter example

* fix: update to readme and launch.json, task.json

* fix: fix tasks.json for debugging

* fix: added Trunk.toml to fix the port

* fix: moved example to projects
2024-05-11 15:02:33 -04:00
Greg Johnston
3760ced0ec fix: allow temporaries as props (closes #2541) (#2582) 2024-05-08 19:35:57 -04:00
Greg Johnston
f3f3a053ba fix: don't insert empty child for comment/doctype (closes #2549) (#2581) 2024-05-08 07:19:57 -04:00
Antoine Büsch
6a8e4bb453 Fix empty_docs warnings in #[component] macro (#2574) 2024-05-06 22:09:19 -04:00
Luxalpa
20f4323e50 feat: allow customize derives for serverfn input struct (#2545) 2024-05-06 08:54:29 -04:00
martin frances
47bcee0ef4 docs: improve NodeRef warning (#2414) (#2467) 2024-05-06 08:51:32 -04:00
SleeplessOne1917
ac3b95d35a examples: use trunk's built-in way of handling tailwind (#2557)
* Use trunk built-in way of handling tailwind

* Remove package manager from package.json
2024-05-06 08:49:07 -04:00
Greg Johnston
a314a4fcd9 docs: clarify the purpose of local resources (#2543) 2024-05-06 08:48:29 -04:00
Sam Judelson
b2a77f06b9 projects: OpenAPI Utopia (#2556) 2024-05-06 08:48:09 -04:00
Sam Judelson
9741c41356 projects: added an index to projects README (#2555)
The Index gives a high level overview of the projects
2024-05-06 08:47:13 -04:00
Joey McKenzie
4e4a770600 projects: add sitemap demo project (#2553) 2024-05-06 08:46:49 -04:00
martin frances
289c02fdac Minor: examples/server_fns_axum FileWatcher logs errors to the console. (#2547)
* Minor: examples/server_fns_axum FileWatcher logs errors to the console.

The cause is an assumption that the directory

./watched_files/

exits.

* chore: Now using .gitkeep to preserve directory structure.
2024-05-06 08:45:27 -04:00
itowlson
123d95c34c Update leptos-spin-macro reference (#2570)
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
2024-05-02 15:25:22 -07:00
Greg Johnston
da9711a743 docs: add caveats for ProtectedRoute (#2558) 2024-05-01 07:06:54 -04:00
151 changed files with 3825 additions and 2411 deletions

View File

@@ -48,9 +48,6 @@ jobs:
- name: Install wasm-bindgen
run: cargo binstall wasm-bindgen-cli --no-confirm
- name: Install wasm-pack
run: cargo binstall wasm-pack --no-confirm
- name: Install cargo-leptos
run: cargo binstall cargo-leptos --no-confirm

View File

@@ -1,7 +1,7 @@
[workspace]
resolver = "2"
members = [
# utilities
# utilities
"oco",
# core
@@ -28,24 +28,24 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.6.11"
version = "0.6.13"
rust-version = "1.75"
[workspace.dependencies]
oco_ref = { path = "./oco", version = "0.1.0" }
leptos = { path = "./leptos", version = "0.6.11" }
leptos_dom = { path = "./leptos_dom", version = "0.6.11" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.11" }
leptos_macro = { path = "./leptos_macro", version = "0.6.11" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.11" }
leptos_server = { path = "./leptos_server", version = "0.6.11" }
server_fn = { path = "./server_fn", version = "0.6.11" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.11" }
leptos = { path = "./leptos", version = "0.6.13" }
leptos_dom = { path = "./leptos_dom", version = "0.6.13" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.13" }
leptos_macro = { path = "./leptos_macro", version = "0.6.13" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.13" }
leptos_server = { path = "./leptos_server", version = "0.6.13" }
server_fn = { path = "./server_fn", version = "0.6.13" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.13" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
leptos_config = { path = "./leptos_config", version = "0.6.11" }
leptos_router = { path = "./router", version = "0.6.11" }
leptos_meta = { path = "./meta", version = "0.6.11" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.11" }
leptos_config = { path = "./leptos_config", version = "0.6.13" }
leptos_router = { path = "./router", version = "0.6.13" }
leptos_meta = { path = "./meta", version = "0.6.13" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.13" }
[profile.release]
codegen-units = 1

View File

@@ -1,56 +1,55 @@
# Summary
# Оглавление
- [Introduction](./01_introduction.md)
- [Getting Started](./getting_started/README.md)
- [Вступление](./01_introduction.md)
- [Начало работы](./getting_started/README.md)
- [Leptos DX](./getting_started/leptos_dx.md)
- [The Leptos Community and leptos-* Crates](./getting_started/community_crates.md)
- [Part 1: Building User Interfaces](./view/README.md)
- [A Basic Component](./view/01_basic_component.md)
- [Dynamic Attributes](./view/02_dynamic_attributes.md)
- [Components and Props](./view/03_components.md)
- [Iteration](./view/04_iteration.md)
- [Iterating over More Complex Data](./view/04b_iteration.md)
- [Forms and Inputs](./view/05_forms.md)
- [Control Flow](./view/06_control_flow.md)
- [Error Handling](./view/07_errors.md)
- [Parent-Child Communication](./view/08_parent_child.md)
- [Passing Children to Components](./view/09_component_children.md)
- [No Macros: The View Builder Syntax](./view/builder.md)
- [Reactivity](./reactivity/README.md)
- [Working with Signals](./reactivity/working_with_signals.md)
- [Responding to Changes with `create_effect`](./reactivity/14_create_effect.md)
- [Interlude: Reactivity and Functions](./reactivity/interlude_functions.md)
- [Testing](./testing.md)
- [Async](./async/README.md)
- [Loading Data with Resources](./async/10_resources.md)
- [Suspense](./async/11_suspense.md)
- [Transition](./async/12_transition.md)
- [Actions](./async/13_actions.md)
- [Interlude: Projecting Children](./interlude_projecting_children.md)
- [Global State Management](./15_global_state.md)
- [Router](./router/README.md)
- [Defining `<Routes/>`](./router/16_routes.md)
- [Nested Routing](./router/17_nested_routing.md)
- [Params and Queries](./router/18_params_and_queries.md)
- [Сообщество Leptos и leptos-* Крейты](./getting_started/community_crates.md)
- [Часть 1: Построение UI](./view/README.md)
- [Простой компонент](./view/01_basic_component.md)
- [Динамические атрибуты](./view/02_dynamic_attributes.md)
- [Компоненты и свойства](./view/03_components.md)
- [Итерирование](./view/04_iteration.md)
- [Итерирование более сложных структур через `<For>`](./view/04b_iteration.md)
- [Формы и поля ввода](./view/05_forms.md)
- [Порядок выполнения](./view/06_control_flow.md)
- [Обработка ошибок](./view/07_errors.md)
- [Общение Родитель-Ребёнок в дереве компонентов](./view/08_parent_child.md)
- [Передача Детей другим компонентам](./view/09_component_children.md)
- [Без макросов: синтаксис билдера View](./view/builder.md)
- [Реактивность](./reactivity/README.md)
- [Работа с сигналами](./reactivity/working_with_signals.md)
- [Реагирование на изменения с помощью `create_effect`](./reactivity/14_create_effect.md)
- [Примечание: Реактивность и функции](./reactivity/interlude_functions.md)
- [Тестирование](./testing.md)
- [Асинхронность](./async/README.md)
- [Подгрузка данных с помощью ресурсов (Resource)](./async/10_resources.md)
- [Ожидания (Suspense)](./async/11_suspense.md)
- [Переходы (Transition)](./async/12_transition.md)
- [Действия (Action)](./async/13_actions.md)
- [Примечание: Пробрасывание дочерних элементов](./interlude_projecting_children.md)
- [Управление глобальным состоянием](./15_global_state.md)
- [Маршрутизатор URL](./router/README.md)
- [Определение `<Routes/>`](./router/16_routes.md)
- [Вложенная маршрутизация](./router/17_nested_routing.md)
- [Параметры в пути и в строке запроса](./router/18_params_and_queries.md)
- [`<A/>`](./router/19_a.md)
- [`<Form/>`](./router/20_form.md)
- [Interlude: Styling](./interlude_styling.md)
- [Metadata](./metadata.md)
- [Client-Side Rendering: Wrapping Up](./csr_wrapping_up.md)
- [Part 2: Server Side Rendering](./ssr/README.md)
- [Примечание: Стили](./interlude_styling.md)
- [Метаданные](./metadata.md)
- [Рендеринг на стороне клиента (CSR): Заключение](./csr_wrapping_up.md)
- [Часть 2: Рендеринг на стороне сервера (SSR)](./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 Bugs](./ssr/24_hydration_bugs.md)
- [Working with the Server](./server/README.md)
- [Server Functions](./server/25_server_functions.md)
- [Extractors](./server/26_extractors.md)
- [Responses and Redirects](./server/27_response.md)
- [Progressive Enhancement and Graceful Degradation](./progressive_enhancement/README.md)
- [`<ActionForm/>`s](./progressive_enhancement/action_form.md)
- [Deployment](./deployment/README.md)
- [Optimizing WASM Binary Size](./deployment/binary_size.md)
- [Guide: Islands](./islands.md)
- [Appendix: How Does the Reactive System Work?](./appendix_reactive_graph.md)
- [Жизненный цикл загрузки страницы](./ssr/22_life_cycle.md)
- [Асинхронный рендеринг и режимы SSR](./ssr/23_ssr_modes.md)
- [Баги возникающие при гидратации](./ssr/24_hydration_bugs.md)
- [Работа с сервером](./server/README.md)
- [Серверные функции](./server/25_server_functions.md)
- [Экстракторы](./server/26_extractors.md)
- [Ответы и перенаправления](./server/27_response.md)
- [Постепенное улучшение и Изящная деградация](./progressive_enhancement/README.md)
- [`<ActionForm/>`](./progressive_enhancement/action_form.md)
- [Развёртывание](./deployment/README.md)
- [Оптимизация размера бинарника WASM](./deployment/binary_size.md)
- [Руководство: Острова](./islands.md)
- [Приложение: Как работает реактивная система?](./appendix_reactive_graph.md)

View File

@@ -47,11 +47,11 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
workspace = false
description = "Generate the list of workspace members"
script = '''
examples=$(ls |
grep -v .md |
grep -v Makefile.toml |
grep -v cargo-make |
grep -v gtk |
examples=$(ls |
grep -v .md |
grep -v Makefile.toml |
grep -v cargo-make |
grep -v gtk |
jq -R -s -c 'split("\n")[:-1]')
echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
'''

View File

@@ -0,0 +1,44 @@
extend = [
{ path = "./lint.toml" }
]
[tasks.make-target-site-dir]
command = "mkdir"
args = ["-p", "target/site"]
[tasks.install-cargo-leptos]
install_crate = { crate_name = "cargo-leptos", binary = "cargo-leptos", test_arg = "--help" }
[tasks.cargo-leptos-e2e]
command = "cargo"
args = ["leptos", "end-to-end"]
[tasks.build]
clear = true
command = "cargo"
dependencies = ["make-target-site-dir"]
args = ["leptos", "build", "--release", "-P"]
[tasks.check]
clear = true
dependencies = ["check-debug", "check-release"]
[tasks.check-debug]
toolchain = "stable"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"
[tasks.check-release]
toolchain = "stable"
command = "cargo"
args = ["check-all-features", "--release"]
install_crate = "cargo-all-features"
[tasks.lint]
dependencies = ["make-target-site-dir", "check-style"]
[tasks.start-client]
dependencies = ["install-cargo-leptos"]
command = "cargo"
args = ["leptos", "watch", "--release", "-P"]

View File

@@ -1,9 +1,11 @@
[tasks.build]
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "--help" }
clear = true
command = "deno"
args = ["task", "build"]
[tasks.start-client]
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "--help" }
command = "deno"
args = ["task", "start"]

View File

@@ -31,6 +31,7 @@ axum = { version = "0.7", optional = true, features = ["http2"] }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = [
"fs",
"compression-gzip",
"compression-br",
], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
@@ -38,6 +39,8 @@ http = { version = "1.0", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2"
lazy_static = "1.4.0"
rust-embed = { version = "8", features = ["axum", "mime_guess", "tokio"], optional = true }
mime_guess = { version = "2.0.4", optional = true }
[features]
default = []
@@ -49,6 +52,8 @@ ssr = [
"dep:tower-http",
"dep:tokio",
"dep:http",
"dep:rust-embed",
"dep:mime_guess",
"leptos/ssr",
"leptos_axum",
"leptos_meta/ssr",
@@ -94,6 +99,12 @@ bin-features = ["ssr"]
# Optional. Defaults to false.
bin-default-features = false
# This feature will add a hash to the filename of assets.
# This is useful here because our files are precompressed and use a `Cache-Control` policy to reduce HTTP requests
#
# Optional. Defaults to false.
hash_file = true
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features

View File

@@ -1,6 +1,6 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/cargo-leptos.toml" },
{ path = "../cargo-make/cargo-leptos-compress.toml" },
]
[env]

View File

@@ -1,6 +1,11 @@
# Leptos Hacker News Example with Axum
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. This repo differs from the main Hacker News example by using Axum as it's server.
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to:
- Create a client-side rendered app
- Create a server side rendered app with hydration
- Precompress static assets and bundle those in with the server binary
This repo differs from the main Hacker News example by using Axum as it's server, precompressing and embedding static assets into the binary, and dynamically compressing the generated HTML.
## Getting Started
@@ -8,4 +13,4 @@ See the [Examples README](../README.md) for setup and run instructions.
## Quick Start
Run `cargo leptos watch` to run this example.
Run `cargo leptos watch --release -P` to run this example.

View File

@@ -1,8 +0,0 @@
wasm-pack build --target=web --features=hydrate --release
cd pkg
rm *.br
cp hackernews.js hackernews.unmin.js
cat hackernews.unmin.js | esbuild > hackernews.js
brotli hackernews.js
brotli hackernews_bg.wasm
brotli style.css

View File

@@ -2,20 +2,34 @@ use crate::error_template::error_template;
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
http::{header, Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::LeptosOptions;
use tower::ServiceExt;
use tower_http::services::ServeDir;
use std::borrow::Cow;
#[cfg(not(debug_assertions))]
const DEV_MODE: bool = false;
#[cfg(debug_assertions)]
const DEV_MODE: bool = true;
#[derive(rust_embed::RustEmbed)]
#[folder = "target/site/"]
struct Assets;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
let accept_encoding = req
.headers()
.get("accept-encoding")
.map(|h| h.to_str().unwrap_or("none"))
.unwrap_or("none")
.to_string();
let res = get_static_file(uri.clone(), accept_encoding).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
@@ -30,19 +44,56 @@ pub async fn file_and_error_handler(
async fn get_static_file(
uri: Uri,
root: &str,
accept_encoding: String,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)),
let (_, path) = uri.path().split_at(1); // split off the first `/`
let mime = mime_guess::from_path(path);
let (path, encoding) = if DEV_MODE {
// during DEV, don't care about the precompression -> faster workflow
(Cow::from(path), "none")
} else if accept_encoding.contains("br") {
(Cow::from(format!("{}.br", path)), "br")
} else if accept_encoding.contains("gzip") {
(Cow::from(format!("{}.gz", path)), "gzip")
} else {
(Cow::from(path), "none")
};
match Assets::get(path.as_ref()) {
Some(content) => {
let body = Body::from(content.data);
let res = match DEV_MODE {
true => Response::builder()
.header(
header::CONTENT_TYPE,
mime.first_or_octet_stream().as_ref(),
)
.header(header::CONTENT_ENCODING, encoding)
.body(body)
.unwrap(),
false => Response::builder()
.header(header::CACHE_CONTROL, "max-age=86400")
.header(
header::CONTENT_TYPE,
mime.first_or_octet_stream().as_ref(),
)
.header(header::CONTENT_ENCODING, encoding)
.body(body)
.unwrap(),
};
Ok(res.into_response())
}
None => {
eprintln!(">> Asset {} not found", path);
for a in Assets::iter() {
eprintln!("Available asset: {}", a);
}
Err((StatusCode::NOT_FOUND, "Not found".to_string()))
}
}
}

View File

@@ -6,16 +6,33 @@ async fn main() {
use hackernews_islands::*;
pub use leptos::get_configuration;
pub use leptos_axum::{generate_route_list, LeptosRoutes};
use tower_http::compression::{
predicate::{NotForContentType, SizeAbove},
CompressionLayer, CompressionLevel, Predicate,
};
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = generate_route_list(App);
let predicate = SizeAbove::new(1500) // files smaller than 1501 bytes are not compressed, since the MTU (Maximum Transmission Unit) of a TCP packet is 1500 bytes
.and(NotForContentType::GRPC)
.and(NotForContentType::IMAGES)
// prevent compressing assets that are already statically compressed
.and(NotForContentType::const_new("application/javascript"))
.and(NotForContentType::const_new("application/wasm"))
.and(NotForContentType::const_new("text/css"));
// build our application with a route
let app = Router::new()
.route("/favicon.ico", get(file_and_error_handler))
.leptos_routes(&leptos_options, routes, App)
.layer(
CompressionLayer::new()
.quality(CompressionLevel::Fastest)
.compress_when(predicate),
)
.fallback(file_and_error_handler)
.with_state(leptos_options);

View File

@@ -14,17 +14,17 @@ lto = true
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { version = "0.5" }
leptos_axum = { version = "0.5", default-features = false, optional = true }
leptos_meta = { version = "0.5" }
leptos_router = { version = "0.5" }
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
tracing = "0.1"
gloo-net = { version = "0.4.0", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6", default-features = false, optional = true }
axum = { version = "0.7", default-features = false, optional = true }
tower = { version = "0.4.13", optional = true }
http = { version = "0.2.11", optional = true }
web-sys = { version = "0.3", features = [
@@ -37,7 +37,7 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4.37", features = [
"futures-core-03-stream",
], optional = true }
axum-js-fetch = { version = "0.2.1", optional = true }
axum-js-fetch = { git = "https://github.com/seanaye/axum-js-fetch", optional = true }
lazy_static = "1.4.0"
[features]

View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "stable" # test change

View File

@@ -62,8 +62,8 @@ mod ssr_imports {
let routes = generate_route_list(App);
// build our application with a route
let app: axum::Router<(), axum::body::Body> = Router::new()
.leptos_routes(&leptos_options, routes, || view! { <App/> })
let app: axum::Router<()> = Router::new()
.leptos_routes(&leptos_options, routes, App)
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.with_state(leptos_options);
@@ -73,7 +73,7 @@ mod ssr_imports {
}
pub async fn serve(&self, req: web_sys::Request) -> web_sys::Response {
self.0.serve(req).await
self.0.oneshot(req).await
}
}
}

View File

@@ -8,7 +8,7 @@ leptos = { path = "../../leptos", features = ["csr"] }
leptos_meta = { path = "../../meta", features = ["csr"] }
leptos_router = { path = "../../router", features = ["csr"] }
log = "0.4"
gloo-net = { version = "0.2", features = ["http"] }
gloo-net = { version = "0.5", features = ["http"] }
# dependencies for client (enable when csr or hydrate set)
wasm-bindgen = { version = "0.2" }

View File

@@ -1,4 +1,4 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/trunk_server.toml" },
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/trunk_server.toml" },
]

View File

@@ -1,4 +0,0 @@
[[hooks]]
stage = "pre_build"
command = "sh"
command_arguments = ["-c", "npx tailwindcss -i input.css -o style/output.css"]

View File

@@ -1,14 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<head>
<meta charset="utf-8" />
<link data-trunk rel="rust" data-wasm-opt="z" />
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico" />
<link data-trunk rel="css" href="/style/output.css" />
<link data-trunk rel="tailwind-css" href="/style/tailwind.css" />
<title>Leptos • Counter with Tailwind</title>
</head>
<body></body>
</head>
<body></body>
</html>

View File

@@ -1,604 +0,0 @@
/*
! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
*/
html {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.my-0 {
margin-top: 0px;
margin-bottom: 0px;
}
.max-w-3xl {
max-width: 48rem;
}
.rounded-lg {
border-radius: 0.5rem;
}
.bg-amber-600 {
--tw-bg-opacity: 1;
background-color: rgb(217 119 6 / var(--tw-bg-opacity));
}
.p-6 {
padding: 1.5rem;
}
.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.pb-10 {
padding-bottom: 2.5rem;
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:bg-sky-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(3 105 161 / var(--tw-bg-opacity));
}

View File

@@ -27,3 +27,6 @@ tokio = { version = "1", features = ["rt", "fs"] }
[features]
nonce = ["leptos/nonce"]
experimental-islands = ["leptos_integration_utils/experimental-islands"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -729,10 +729,7 @@ async fn stream_app(
build_stream_response(options, res_options, stream, runtime).await
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[cfg_attr(any(debug_assertions), instrument(level = "trace", skip_all,))]
async fn stream_app_in_order(
options: &LeptosOptions,
app: impl FnOnce() -> View + 'static,

View File

@@ -37,3 +37,6 @@ nonce = ["leptos/nonce"]
wasm = []
default = ["tokio/fs", "tokio/sync"]
experimental-islands = ["leptos_integration_utils/experimental-islands"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -54,7 +54,10 @@ use leptos_meta::{generate_head_metadata_separated, MetaContext};
use leptos_router::*;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use server_fn::{error::NoCustomError, redirect::REDIRECT_HEADER};
use server_fn::{
error::{NoCustomError, ServerFnErrorSerde},
redirect::REDIRECT_HEADER,
};
use std::{fmt::Debug, io, pin::Pin, sync::Arc, thread::available_parallelism};
use tokio_util::task::LocalPoolHandle;
use tracing::Instrument;
@@ -357,9 +360,10 @@ async fn handle_server_fns_inner(
rx.await.unwrap_or_else(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ServerFnError::<NoCustomError>::ServerError(e.to_string())
.ser()
.unwrap_or_default(),
ServerFnErrorSerde::ser(
&ServerFnError::<NoCustomError>::ServerError(e.to_string()),
)
.unwrap_or_default(),
)
.into_response()
})

View File

@@ -18,3 +18,6 @@ tracing = "0.1.37"
[features]
experimental-islands = []
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -16,7 +16,7 @@ leptos_macro = { workspace = true }
leptos_reactive = { workspace = true }
leptos_server = { workspace = true }
leptos_config = { workspace = true }
leptos-spin-macro = { version = "0.1", optional = true }
leptos-spin-macro = { version = "0.2", optional = true }
tracing = "0.1"
typed-builder = "0.18"
typed-builder-macro = "0.18"
@@ -70,7 +70,7 @@ nightly = [
serde = ["leptos_reactive/serde"]
serde-lite = ["leptos_reactive/serde-lite"]
miniserde = ["leptos_reactive/miniserde"]
rkyv = ["leptos_reactive/rkyv"]
rkyv = ["leptos_reactive/rkyv", "server_fn/rkyv"]
tracing = ["leptos_macro/tracing"]
nonce = ["leptos_dom/nonce"]
spin = ["leptos_reactive/spin", "leptos-spin-macro"]
@@ -141,3 +141,6 @@ skip_feature_sets = [
"rustls",
],
]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -16,8 +16,80 @@ pub type ChildrenFnMut = Box<dyn FnMut() -> Fragment>;
// This is to still support components that accept `Box<dyn Fn() -> Fragment>` as a children.
type BoxedChildrenFn = Box<dyn Fn() -> Fragment>;
#[doc(hidden)]
/// This trait can be used when constructing a component that takes children without needing
/// to know exactly what children type the component expects. This is used internally by the
/// `view!` macro implementation, and can also be used explicitly when using the builder syntax.
///
/// # Examples
///
/// ## Without ToChildren
///
/// Without [ToChildren], consumers need to explicitly provide children using the type expected
/// by the component. For example, [Provider][crate::Provider]'s children need to wrapped in
/// a [Box], while [Show][crate::Show]'s children need to be wrapped in an [Rc].
///
/// ```
/// # use leptos::{ProviderProps, ShowProps};
/// # use leptos_dom::html::p;
/// # use leptos_dom::IntoView;
/// # use leptos_macro::component;
/// # use std::rc::Rc;
/// #
/// #[component]
/// fn App() -> impl IntoView {
/// (
/// ProviderProps::builder()
/// .children(Box::new(|| p().child("Foo").into_view().into()))
/// // ...
/// # .value("Foo")
/// # .build(),
/// ShowProps::builder()
/// .children(Rc::new(|| p().child("Foo").into_view().into()))
/// // ...
/// # .when(|| true)
/// # .fallback(|| p().child("foo"))
/// # .build(),
/// )
/// }
/// ```
///
/// ## With ToChildren
///
/// With [ToChildren], consumers don't need to know exactly which type a component uses for
/// its children.
///
/// ```
/// # use leptos::{ProviderProps, ShowProps};
/// # use leptos_dom::html::p;
/// # use leptos_dom::IntoView;
/// # use leptos_macro::component;
/// # use std::rc::Rc;
/// # use leptos::ToChildren;
/// #
/// #[component]
/// fn App() -> impl IntoView {
/// (
/// ProviderProps::builder()
/// .children(ToChildren::to_children(|| {
/// p().child("Foo").into_view().into()
/// }))
/// // ...
/// # .value("Foo")
/// # .build(),
/// ShowProps::builder()
/// .children(ToChildren::to_children(|| {
/// p().child("Foo").into_view().into()
/// }))
/// // ...
/// # .when(|| true)
/// # .fallback(|| p().child("foo"))
/// # .build(),
/// )
/// }
pub trait ToChildren<F> {
/// Convert the provided type to (generally a closure) to Self (generally a "children" type,
/// e.g., [Children]). See the implementations to see exactly which input types are supported
/// and which "children" type they are converted to.
fn to_children(f: F) -> Self;
}

View File

@@ -34,18 +34,6 @@ use std::hash::Hash;
/// }
/// }
/// />
/// <For
/// // a function that returns the items we're iterating over; a signal is fine
/// each=move || counters.get()
/// // a unique key for each item
/// key=|counter| counter.id
/// // renders each item to a view
/// children=move |counter: Counter| {
/// view! {
/// <button>"Value: " {move || counter.count.get()}</button>
/// }
/// }
/// />
/// </div>
/// }
/// }

View File

@@ -285,6 +285,21 @@ pub trait DynAttrs {
impl DynAttrs for () {}
#[doc(hidden)]
pub trait DynBindings {
fn dyn_bindings<B: Into<Binding>>(
self,
_args: impl IntoIterator<Item = B>,
) -> Self
where
Self: Sized,
{
self
}
}
impl DynBindings for () {}
#[doc(hidden)]
pub trait PropsOrNoPropsBuilder {
type Builder;

View File

@@ -2,6 +2,11 @@ use crate::ChildrenFn;
use cfg_if::cfg_if;
use leptos_dom::IntoView;
use leptos_macro::component;
#[cfg(all(
target_arch = "wasm32",
any(feature = "hydrate", feature = "csr")
))]
use leptos_reactive::untrack;
/// Renders components somewhere else in the DOM.
///
@@ -36,6 +41,7 @@ pub fn Portal(
.unwrap_or_else(|| document().body().expect("body to exist").unchecked_into());
create_effect(move |_| {
leptos::logging::log!("inside Portal effect");
let tag = if is_svg { "g" } else { "div" };
let container = document()
@@ -53,7 +59,8 @@ pub fn Portal(
container.clone()
};
let _ = render_root.append_child(&children().into_view().get_mountable_node());
let children = untrack(|| children().into_view().get_mountable_node());
let _ = render_root.append_child(&children);
let _ = mount.append_child(&container);

View File

@@ -20,3 +20,6 @@ typed-builder = "0.18"
tokio = { version = "1", features = ["rt", "macros"] }
tempfile = "3"
temp-env = { version = "0.3.6", features = ["async_closure"] }
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -178,3 +178,6 @@ trace-component-props = []
[package.metadata.cargo-all-features]
denylist = ["nightly", "trace-component-props"]
skip_feature_sets = [["web", "ssr"]]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -46,6 +46,16 @@ pub trait IntoStyle {
fn into_style_boxed(self: Box<Self>) -> Style;
}
impl IntoStyle for Style {
fn into_style(self) -> Style {
self
}
fn into_style_boxed(self: Box<Self>) -> Style {
*self
}
}
impl IntoStyle for &'static str {
#[inline(always)]
fn into_style(self) -> Style {
@@ -176,7 +186,7 @@ impl Style {
/// Converts the style to its HTML value at that moment so it can be rendered on the server.
pub fn as_value_string(
&self,
style_name: &'static str,
style_name: &str,
) -> Option<Oco<'static, str>> {
match self {
Style::Value(value) => {

View File

@@ -31,10 +31,12 @@ use std::cell::Cell;
/// }
/// }
/// ```
#[repr(transparent)]
pub struct NodeRef<T: ElementDescriptor + 'static>(
RwSignal<Option<HtmlElement<T>>>,
);
#[cfg_attr(not(debug_assertions), repr(transparent))]
pub struct NodeRef<T: ElementDescriptor + 'static> {
element: RwSignal<Option<HtmlElement<T>>>,
#[cfg(debug_assertions)]
defined_at: &'static std::panic::Location<'static>,
}
/// Creates a shared reference to a DOM node created while using the `view`
/// macro to create your UI.
@@ -65,9 +67,14 @@ pub struct NodeRef<T: ElementDescriptor + 'static>(
/// }
/// }
/// ```
#[track_caller]
#[inline(always)]
pub fn create_node_ref<T: ElementDescriptor + 'static>() -> NodeRef<T> {
NodeRef(create_rw_signal(None))
NodeRef {
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
element: create_rw_signal(None),
}
}
impl<T: ElementDescriptor + 'static> NodeRef<T> {
@@ -120,7 +127,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
where
T: Clone,
{
self.0.get()
self.element.get()
}
/// Gets the element that is currently stored in the reference.
@@ -132,7 +139,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
where
T: Clone,
{
self.0.get_untracked()
self.element.get_untracked()
}
#[doc(hidden)]
@@ -144,13 +151,15 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
where
T: Clone,
{
self.0.update(|current| {
self.element.update(|current| {
if current.is_some() {
#[cfg(debug_assertions)]
crate::debug_warn!(
"You are setting a NodeRef that has already been filled. \
Its possible this is intentional, but its also \
possible that youre accidentally using the same NodeRef \
for multiple _ref attributes."
"You are setting the NodeRef defined at {}, which has \
already been filled Its possible this is intentional, \
but its also possible that youre accidentally using \
the same NodeRef for multiple _ref attributes.",
self.defined_at
);
}
*current = Some(node.clone());

View File

@@ -51,3 +51,6 @@ axum = ["server_fn_macro/axum"]
[package.metadata.cargo-all-features]
denylist = ["nightly", "tracing", "trace-component-props"]
skip_feature_sets = [["csr", "hydrate"], ["hydrate", "csr"], ["hydrate", "ssr"]]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -8,10 +8,11 @@ use leptos_hot_reload::parsing::value_to_string;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::{
parse::Parse, parse_quote, spanned::Spanned,
AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, Item,
ItemFn, LitStr, Meta, Pat, PatIdent, Path, PathArguments, ReturnType,
Signature, Stmt, Type, TypePath, Visibility,
parse::Parse, parse_quote, spanned::Spanned, token::Colon,
visit_mut::VisitMut, AngleBracketedGenericArguments, Attribute, FnArg,
GenericArgument, GenericParam, Item, ItemFn, LitStr, Meta, Pat, PatIdent,
Path, PathArguments, ReturnType, Signature, Stmt, Type, TypeImplTrait,
TypeParam, TypePath, Visibility,
};
pub struct Model {
@@ -28,6 +29,7 @@ pub struct Model {
impl Parse for Model {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut item = ItemFn::parse(input)?;
convert_impl_trait_to_generic(&mut item.sig);
let docs = Docs::new(&item.attrs);
@@ -178,6 +180,18 @@ impl ToTokens for Model {
);
let component_fn_prop_docs = generate_component_fn_prop_docs(props);
let docs_and_prop_docs = if component_fn_prop_docs.is_empty() {
// Avoid generating an empty doc line in case the component has no doc and no props.
quote! {
#docs
}
} else {
quote! {
#docs
#[doc = ""]
#component_fn_prop_docs
}
};
let (
tracing_instrument_attr,
@@ -214,7 +228,7 @@ impl ToTokens for Model {
let component_id = name.to_string();
let hydrate_fn_name =
Ident::new(&format!("_island_{}", component_id), name.span());
Ident::new(&format!("_island_{component_id}"), name.span());
let island_serialize_props = if is_island_with_other_props {
quote! {
@@ -502,9 +516,7 @@ impl ToTokens for Model {
let output = quote! {
#[doc = #builder_name_doc]
#[doc = ""]
#docs
#[doc = ""]
#component_fn_prop_docs
#docs_and_prop_docs
#[derive(::leptos::typed_builder_macro::TypedBuilder #props_derive_serialize)]
//#[builder(doc)]
#[builder(crate_module_path=::leptos::typed_builder)]
@@ -531,7 +543,7 @@ impl ToTokens for Model {
}
}
impl #impl_generics #props_name #generics #where_clause {
impl #impl_generics ::leptos::DynBindings for #props_name #generics #where_clause {
fn dyn_bindings<B: Into<::leptos::leptos_dom::html::Binding>>(mut self, bindings: impl std::iter::IntoIterator<Item = B>) -> Self {
for binding in bindings.into_iter() {
let binding: ::leptos::leptos_dom::html::Binding = binding.into();
@@ -548,9 +560,7 @@ impl ToTokens for Model {
#into_view
#docs
#[doc = ""]
#component_fn_prop_docs
#docs_and_prop_docs
#[allow(non_snake_case, clippy::too_many_arguments)]
#[allow(clippy::needless_lifetimes)]
#tracing_instrument_attr
@@ -1221,3 +1231,57 @@ fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
Ident::new(&format!("__{ident}"), ident.span())
}
/// Converts all `impl Trait`s in a function signature to use generic params instead.
fn convert_impl_trait_to_generic(sig: &mut Signature) {
fn new_generic_ident(i: usize, span: Span) -> Ident {
Ident::new(&format!("__ImplTrait{i}"), span)
}
// First: visit all `impl Trait`s and replace them with new generic params.
#[derive(Default)]
struct RemoveImplTrait(Vec<TypeImplTrait>);
impl VisitMut for RemoveImplTrait {
fn visit_type_mut(&mut self, ty: &mut Type) {
syn::visit_mut::visit_type_mut(self, ty);
if matches!(ty, Type::ImplTrait(_)) {
let ident = new_generic_ident(self.0.len(), ty.span());
let generic_type = Type::Path(TypePath {
qself: None,
path: Path::from(ident),
});
let Type::ImplTrait(impl_trait) =
std::mem::replace(ty, generic_type)
else {
unreachable!();
};
self.0.push(impl_trait);
}
}
// Early exits.
fn visit_attribute_mut(&mut self, _: &mut Attribute) {}
fn visit_pat_mut(&mut self, _: &mut Pat) {}
}
let mut visitor = RemoveImplTrait::default();
for fn_arg in sig.inputs.iter_mut() {
visitor.visit_fn_arg_mut(fn_arg);
}
let RemoveImplTrait(impl_traits) = visitor;
// Second: Add the new generic params into the signature.
for (i, impl_trait) in impl_traits.into_iter().enumerate() {
let span = impl_trait.span();
let ident = new_generic_ident(i, span);
// We can simply append to the end (only lifetime params must be first).
// Note currently default generics are not allowed in `fn`, so not a concern.
sig.generics.params.push(GenericParam::Type(TypeParam {
attrs: vec![],
ident,
colon_token: Some(Colon { spans: [span] }),
bounds: impl_trait.bounds,
eq_token: None,
default: None,
}));
}
}

View File

@@ -24,10 +24,7 @@ pub(crate) enum Mode {
impl Default for Mode {
fn default() -> Self {
if cfg!(feature = "hydrate")
|| cfg!(feature = "csr")
|| cfg!(feature = "web")
{
if cfg!(feature = "hydrate") || cfg!(feature = "csr") {
Mode::Client
} else {
Mode::Ssr

View File

@@ -11,7 +11,7 @@ use syn::{
struct SliceMacroInput {
root: syn::Ident,
path: Punctuated<syn::Ident, Token![.]>,
path: Punctuated<syn::Member, Token![.]>,
}
impl Parse for SliceMacroInput {
@@ -19,7 +19,7 @@ impl Parse for SliceMacroInput {
let root: syn::Ident = input.parse()?;
input.parse::<Token![.]>()?;
// do not accept trailing punctuation
let path: Punctuated<syn::Ident, Token![.]> =
let path: Punctuated<syn::Member, Token![.]> =
Punctuated::parse_separated_nonempty(input)?;
if path.is_empty() {

View File

@@ -52,12 +52,10 @@ pub(crate) fn fragment_to_tokens(
None,
)?;
let node = quote_spanned! {span=>
#[allow(unused_braces)] {#node}
};
let node = quote_spanned!(span => { #node });
Some(quote! {
::leptos::IntoView::into_view(#node)
::leptos::IntoView::into_view(#[allow(unused_braces)] #node)
})
})
.peekable();
@@ -298,34 +296,38 @@ pub(crate) fn element_to_tokens(
let children = node
.children
.iter()
.map(|node| match node {
Node::Fragment(fragment) => fragment_to_tokens(
&fragment.children,
true,
parent_type,
None,
global_class,
None,
)
.unwrap_or(quote_spanned! {
Span::call_site()=> ::leptos::leptos_dom::Unit
}),
Node::Text(node) => quote! { #node },
.filter_map(|node| match node {
Node::Fragment(fragment) => Some(
fragment_to_tokens(
&fragment.children,
true,
parent_type,
None,
global_class,
None,
)
.unwrap_or(quote_spanned! {
Span::call_site()=> ::leptos::leptos_dom::Unit
}),
),
Node::Text(node) => Some(quote! { #node }),
Node::RawText(node) => {
let text = node.to_string_best();
let text = syn::LitStr::new(&text, node.span());
quote! { #text }
Some(quote! { #text })
}
Node::Block(node) => quote! { #node },
Node::Element(node) => element_to_tokens(
node,
parent_type,
None,
global_class,
None,
)
.unwrap_or_default(),
Node::Comment(_) | Node::Doctype(_) => quote! {},
Node::Block(node) => Some(quote! { #node }),
Node::Element(node) => Some(
element_to_tokens(
node,
parent_type,
None,
global_class,
None,
)
.unwrap_or_default(),
),
Node::Comment(_) | Node::Doctype(_) => None,
})
.map(|node| quote!(.child(#node)));
@@ -335,9 +337,7 @@ pub(crate) fn element_to_tokens(
quote! {}
};
let ide_helper_close_tag = ide_helper_close_tag.into_iter();
Some(quote_spanned! {node.span()=>
#[allow(unused_braces)]
{
let result = quote_spanned! {node.span()=> {
#(#ide_helper_close_tag)*
#name
#(#attrs)*
@@ -348,7 +348,10 @@ pub(crate) fn element_to_tokens(
#(#children)*
#view_marker
}
})
};
// We need to move "allow" out of "quote_spanned" because it breaks hovering in rust-analyzer
Some(quote!(#[allow(unused_braces)] #result))
}
}

View File

@@ -70,12 +70,10 @@ pub(crate) fn component_to_tokens(
})
.unwrap_or_else(|| quote! { #name });
let value = quote_spanned! {value.span()=>
#[allow(unused_braces)] {#value}
};
let value = quote_spanned!(value.span()=> { #value });
quote_spanned! {attr.span()=>
.#name(#value)
.#name(#[allow(unused_braces)] #value)
}
});
@@ -237,25 +235,17 @@ pub(crate) fn component_to_tokens(
#[allow(unused_mut)] // used in debug
let mut component = quote_spanned! {node.span()=>
{
let props = #component_props_builder
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
#name_ref,
#component_props_builder
#(#props)*
#(#slots)*
#children;
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props
#children
#build
#dyn_attrs
#(#spread_bindings)*;
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
#name_ref,
props
)
}
#(#spread_bindings)*
)
};
// (Temporarily?) removed

View File

@@ -83,12 +83,10 @@ pub(crate) fn fragment_to_tokens_ssr(
let nodes = nodes.iter().map(|node| {
let span = node.span();
let node = root_node_to_tokens_ssr(node, global_class, None);
let node = quote_spanned! {span=>
#[allow(unused_braces)] {#node}
};
let node = quote_spanned!(span=> { #node });
quote! {
::leptos::IntoView::into_view(#node)
::leptos::IntoView::into_view(#[allow(unused_braces)] #node)
}
});

View File

@@ -61,12 +61,10 @@ pub(crate) fn slot_to_tokens(
})
.unwrap_or_else(|| quote! { #name });
let value = quote_spanned! {value.span()=>
#[allow(unused_braces)] {#value}
};
let value = quote_spanned!(value.span()=> { #value });
quote_spanned! {attr.span()=>
.#name(#value)
.#name(#[allow(unused_braces)] #value)
}
});
@@ -187,7 +185,7 @@ pub(crate) fn slot_to_tokens(
};
let slot = quote_spanned! {node.span()=>
#[allow(unused_braces)] {
{
let slot = #component_name::builder()
#(#props)*
#(#slots)*
@@ -200,6 +198,9 @@ pub(crate) fn slot_to_tokens(
},
};
// We need to move "allow" out of "quote_spanned" because it breaks hovering in rust-analyzer
let slot = quote!(#[allow(unused_braces)] #slot);
parent_slots
.entry(name)
.and_modify(|entry| entry.push(slot.clone()))

View File

@@ -1,21 +1,14 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: pretty(result)
---
fn view() {
{
let props = ::leptos::component_props_builder(&SimpleCounter)
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&SimpleCounter,
::leptos::component_props_builder(&SimpleCounter)
.initial_value(#[allow(unused_braces)] { 0 })
.step(#[allow(unused_braces)] { 1 });
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props.build();
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&SimpleCounter,
props,
)
}
.step(#[allow(unused_braces)] { 1 })
.build(),
)
}

View File

@@ -1,23 +1,16 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: pretty(result)
---
fn view() {
::leptos::IntoView::into_view(
#[allow(unused_braces)]
{
{
let props = ::leptos::component_props_builder(&ExternalComponent);
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props.build();
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&ExternalComponent,
props,
)
}
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&ExternalComponent,
::leptos::component_props_builder(&ExternalComponent).build(),
)
},
)
.on(
@@ -27,4 +20,3 @@ fn view() {
move |_: Event| set_value(0),
)
}

View File

@@ -1,22 +1,89 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Brace,
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: let,
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '=',
char: '&',
spacing: Alone,
span: bytes(11..24),
},
Ident {
sym: SimpleCounter,
span: bytes(11..24),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
@@ -78,27 +145,27 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(51..52),
span: bytes(37..52),
},
],
span: bytes(51..52),
span: bytes(37..52),
},
],
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Brace,
@@ -128,27 +195,27 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(70..71),
span: bytes(65..71),
},
],
span: bytes(70..71),
span: bytes(65..71),
},
],
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Brace,
@@ -163,90 +230,6 @@ TokenStream [
],
span: bytes(65..71),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: let_unit_value,
span: bytes(10..82),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: unit_arg,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Ident {
sym: let,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '=',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '.',
spacing: Alone,
@@ -261,127 +244,6 @@ TokenStream [
stream: TokenStream [],
span: bytes(11..24),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unreachable_code,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '&',
spacing: Alone,
span: bytes(11..24),
},
Ident {
sym: SimpleCounter,
span: bytes(11..24),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},

View File

@@ -1,6 +1,5 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
@@ -77,19 +76,87 @@ TokenStream [
Group {
delimiter: Brace,
stream: TokenStream [
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Brace,
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: let,
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '=',
char: '&',
spacing: Alone,
span: bytes(11..28),
},
Ident {
sym: ExternalComponent,
span: bytes(11..28),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
@@ -136,90 +203,6 @@ TokenStream [
],
span: bytes(11..28),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: let_unit_value,
span: bytes(10..82),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: unit_arg,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Ident {
sym: let,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '=',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '.',
spacing: Alone,
@@ -234,127 +217,6 @@ TokenStream [
stream: TokenStream [],
span: bytes(11..28),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unreachable_code,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '&',
spacing: Alone,
span: bytes(11..28),
},
Ident {
sym: ExternalComponent,
span: bytes(11..28),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},

View File

@@ -1,21 +1,14 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: pretty(result)
---
fn view() {
{
let props = ::leptos::component_props_builder(&SimpleCounter)
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&SimpleCounter,
::leptos::component_props_builder(&SimpleCounter)
.initial_value(#[allow(unused_braces)] { 0 })
.step(#[allow(unused_braces)] { 1 });
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props.build();
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&SimpleCounter,
props,
)
}
.step(#[allow(unused_braces)] { 1 })
.build(),
)
}

View File

@@ -1,23 +1,16 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: pretty(result)
---
fn view() {
::leptos::IntoView::into_view(
#[allow(unused_braces)]
{
{
let props = ::leptos::component_props_builder(&ExternalComponent);
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props.build();
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&ExternalComponent,
props,
)
}
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&ExternalComponent,
::leptos::component_props_builder(&ExternalComponent).build(),
)
},
)
.on(
@@ -27,4 +20,3 @@ fn view() {
move |_: Event| set_value(0),
)
}

View File

@@ -1,22 +1,89 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Brace,
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: let,
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '=',
char: '&',
spacing: Alone,
span: bytes(11..24),
},
Ident {
sym: SimpleCounter,
span: bytes(11..24),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
@@ -78,27 +145,27 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(51..52),
span: bytes(37..52),
},
],
span: bytes(51..52),
span: bytes(37..52),
},
],
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Brace,
@@ -128,27 +195,27 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(70..71),
span: bytes(65..71),
},
],
span: bytes(70..71),
span: bytes(65..71),
},
],
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Brace,
@@ -163,90 +230,6 @@ TokenStream [
],
span: bytes(65..71),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: let_unit_value,
span: bytes(10..82),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: unit_arg,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Ident {
sym: let,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '=',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '.',
spacing: Alone,
@@ -261,127 +244,6 @@ TokenStream [
stream: TokenStream [],
span: bytes(11..24),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unreachable_code,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '&',
spacing: Alone,
span: bytes(11..24),
},
Ident {
sym: SimpleCounter,
span: bytes(11..24),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},

View File

@@ -1,6 +1,5 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
@@ -77,19 +76,87 @@ TokenStream [
Group {
delimiter: Brace,
stream: TokenStream [
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Brace,
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: let,
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '=',
char: '&',
spacing: Alone,
span: bytes(11..28),
},
Ident {
sym: ExternalComponent,
span: bytes(11..28),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
@@ -136,90 +203,6 @@ TokenStream [
],
span: bytes(11..28),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: let_unit_value,
span: bytes(10..82),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: unit_arg,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Ident {
sym: let,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '=',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '.',
spacing: Alone,
@@ -234,127 +217,6 @@ TokenStream [
stream: TokenStream [],
span: bytes(11..28),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unreachable_code,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '&',
spacing: Alone,
span: bytes(11..28),
},
Ident {
sym: ExternalComponent,
span: bytes(11..28),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},

View File

@@ -1,33 +1,27 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..331),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..331),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(10..331),
},
],
span: bytes(10..331),
},
],
span: bytes(10..331),
},
Group {
delimiter: Brace,
@@ -153,27 +147,22 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(28..83),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(28..83),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(28..83),
},
],
span: bytes(28..83),
},
],
span: bytes(28..83),
},
Group {
delimiter: Brace,
@@ -404,27 +393,22 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(96..176),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(96..176),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(96..176),
},
],
span: bytes(96..176),
},
],
span: bytes(96..176),
},
Group {
delimiter: Brace,
@@ -697,27 +681,22 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(189..223),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(189..223),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(189..223),
},
],
span: bytes(189..223),
},
],
span: bytes(189..223),
},
Group {
delimiter: Brace,
@@ -902,27 +881,22 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(236..316),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(236..316),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(236..316),
},
],
span: bytes(236..316),
},
],
span: bytes(236..316),
},
Group {
delimiter: Brace,

View File

@@ -1,21 +1,14 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: pretty(result)
---
fn view() {
{
let props = ::leptos::component_props_builder(&SimpleCounter)
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&SimpleCounter,
::leptos::component_props_builder(&SimpleCounter)
.initial_value(#[allow(unused_braces)] { 0 })
.step(#[allow(unused_braces)] { 1 });
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props.build();
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&SimpleCounter,
props,
)
}
.step(#[allow(unused_braces)] { 1 })
.build(),
)
}

View File

@@ -1,23 +1,16 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: pretty(result)
---
fn view() {
::leptos::IntoView::into_view(
#[allow(unused_braces)]
{
{
let props = ::leptos::component_props_builder(&ExternalComponent);
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props.build();
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&ExternalComponent,
props,
)
}
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
&ExternalComponent,
::leptos::component_props_builder(&ExternalComponent).build(),
)
},
)
.on(
@@ -27,4 +20,3 @@ fn view() {
move |_: Event| set_value(0),
)
}

View File

@@ -1,22 +1,89 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Brace,
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: let,
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '=',
char: '&',
spacing: Alone,
span: bytes(11..24),
},
Ident {
sym: SimpleCounter,
span: bytes(11..24),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
@@ -78,27 +145,27 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(51..52),
span: bytes(37..52),
},
],
span: bytes(51..52),
span: bytes(37..52),
},
],
span: bytes(51..52),
span: bytes(37..52),
},
Group {
delimiter: Brace,
@@ -128,27 +195,27 @@ TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unused_braces,
span: bytes(70..71),
span: bytes(65..71),
},
],
span: bytes(70..71),
span: bytes(65..71),
},
],
span: bytes(70..71),
span: bytes(65..71),
},
Group {
delimiter: Brace,
@@ -163,90 +230,6 @@ TokenStream [
],
span: bytes(65..71),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: let_unit_value,
span: bytes(10..82),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: unit_arg,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Ident {
sym: let,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '=',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '.',
spacing: Alone,
@@ -261,127 +244,6 @@ TokenStream [
stream: TokenStream [],
span: bytes(11..24),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unreachable_code,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '&',
spacing: Alone,
span: bytes(11..24),
},
Ident {
sym: SimpleCounter,
span: bytes(11..24),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},

View File

@@ -1,6 +1,5 @@
---
source: leptos_macro/src/view/tests.rs
assertion_line: 101
expression: result
---
TokenStream [
@@ -77,19 +76,87 @@ TokenStream [
Group {
delimiter: Brace,
stream: TokenStream [
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Brace,
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: let,
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '=',
char: '&',
spacing: Alone,
span: bytes(11..28),
},
Ident {
sym: ExternalComponent,
span: bytes(11..28),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
@@ -136,90 +203,6 @@ TokenStream [
],
span: bytes(11..28),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: let_unit_value,
span: bytes(10..82),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: unit_arg,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Ident {
sym: let,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '=',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
Punct {
char: '.',
spacing: Alone,
@@ -234,127 +217,6 @@ TokenStream [
stream: TokenStream [],
span: bytes(11..28),
},
Punct {
char: ';',
spacing: Alone,
span: bytes(10..82),
},
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: unreachable_code,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: leptos,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: component_view,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Punct {
char: '#',
spacing: Alone,
span: bytes(10..82),
},
Group {
delimiter: Bracket,
stream: TokenStream [
Ident {
sym: allow,
span: bytes(10..82),
},
Group {
delimiter: Parenthesis,
stream: TokenStream [
Ident {
sym: clippy,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Joint,
span: bytes(10..82),
},
Punct {
char: ':',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: needless_borrows_for_generic_args,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},
Punct {
char: '&',
spacing: Alone,
span: bytes(11..28),
},
Ident {
sym: ExternalComponent,
span: bytes(11..28),
},
Punct {
char: ',',
spacing: Alone,
span: bytes(10..82),
},
Ident {
sym: props,
span: bytes(10..82),
},
],
span: bytes(10..82),
},
],
span: bytes(10..82),
},

View File

@@ -8,20 +8,27 @@ fn Component(
#[prop(strip_option)] strip_option: Option<u8>,
#[prop(default = NonZeroUsize::new(10).unwrap())] default: NonZeroUsize,
#[prop(into)] into: String,
impl_trait: impl Fn() -> i32 + 'static,
) -> impl IntoView {
_ = optional;
_ = optional_no_strip;
_ = strip_option;
_ = default;
_ = into;
_ = impl_trait;
}
#[test]
fn component() {
let cp = ComponentProps::builder().into("").strip_option(9).build();
let cp = ComponentProps::builder()
.into("")
.strip_option(9)
.impl_trait(|| 42)
.build();
assert!(!cp.optional);
assert_eq!(cp.optional_no_strip, None);
assert_eq!(cp.strip_option, Some(9));
assert_eq!(cp.default, NonZeroUsize::new(10).unwrap());
assert_eq!(cp.into, "");
assert_eq!((cp.impl_trait)(), 42);
}

View File

@@ -10,9 +10,12 @@ pub struct OuterState {
#[derive(Clone, PartialEq, Default)]
pub struct InnerState {
inner_count: i32,
inner_name: String,
inner_tuple: InnerTuple,
}
#[derive(Clone, PartialEq, Default)]
pub struct InnerTuple(String);
#[test]
fn green() {
let _ = create_runtime();
@@ -22,7 +25,7 @@ fn green() {
let (_, _) = slice!(outer_signal.count);
let (_, _) = slice!(outer_signal.inner.inner_count);
let (_, _) = slice!(outer_signal.inner.inner_name);
let (_, _) = slice!(outer_signal.inner.inner_tuple.0);
}
#[test]

View File

@@ -14,7 +14,7 @@ error: expected `.`
|
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected end of input, expected identifier
error: unexpected end of input, expected identifier or integer
--> tests/slice/red.rs:25:18
|
25 | let (_, _) = slice!(outer_signal.);
@@ -22,7 +22,7 @@ error: unexpected end of input, expected identifier
|
= note: this error originates in the macro `slice` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected end of input, expected identifier
error: unexpected end of input, expected identifier or integer
--> tests/slice/red.rs:27:18
|
27 | let (_, _) = slice!(outer_signal.inner.);

View File

@@ -121,3 +121,6 @@ skip_feature_sets = [
"rkyv",
],
]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -31,7 +31,7 @@ pub struct SharedContext {
impl SharedContext {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn all_resources() -> Vec<ResourceId> {
@@ -41,7 +41,7 @@ impl SharedContext {
/// 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"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn pending_resources() -> Vec<ResourceId> {
@@ -50,7 +50,7 @@ impl SharedContext {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn serialization_resolvers(
@@ -62,7 +62,7 @@ impl SharedContext {
/// 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"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn register_suspense(
@@ -121,7 +121,7 @@ impl SharedContext {
/// 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"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn take_pending_fragment(id: &str) -> Option<FragmentData> {
@@ -135,7 +135,7 @@ impl SharedContext {
/// A future that will resolve when all blocking fragments are ready.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn blocking_fragments_ready() -> PinnedFuture<()> {
@@ -162,7 +162,7 @@ impl SharedContext {
/// 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"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn pending_fragments() -> HashMap<String, FragmentData> {
@@ -176,7 +176,7 @@ impl SharedContext {
/// Registers the given element as an island with the current reactive owner.
#[cfg(all(feature = "hydrate", feature = "experimental-islands"))]
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn register_island(el: &web_sys::HtmlElement) {
@@ -190,7 +190,7 @@ impl SharedContext {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn fragment_has_local_resources(fragment: &str) -> bool {
@@ -204,7 +204,7 @@ impl SharedContext {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn fragments_with_local_resources() -> HashSet<String> {
@@ -216,7 +216,7 @@ impl SharedContext {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn register_local_fragment(key: String) {

View File

@@ -13,10 +13,7 @@ pub struct Disposer(pub(crate) NodeId);
impl Drop for Disposer {
fn drop(&mut self) {
let id = self.0;
_ = with_runtime(|runtime| {
runtime.cleanup_node(id);
runtime.dispose_node(id);
});
_ = with_runtime(|runtime| runtime.dispose_node(id));
}
}

View File

@@ -271,6 +271,16 @@ where
///
/// Local resources do not load on the server, only in the clients browser.
///
/// ## When to use a Local Resource
///
/// `create_resource` has three different features:
/// 1. gives a synchronous API for asynchronous things
/// 2. integrates with `Suspense`/`Transition``
/// 3. makes your application faster by starting things like DB access or an API request on the server,
/// rather than waiting until you've fully loaded the client
///
/// `create_local_resource` is useful when you can't or don't need to do #3 (serializing data from server
/// to client), but still want #1 (synchronous API for async) and #2 (integration with `Suspense`).
/// ```
/// # use leptos_reactive::*;
/// # let runtime = create_runtime();
@@ -278,7 +288,9 @@ where
/// struct ComplicatedUnserializableStruct {
/// // something here that can't be serialized
/// }
/// // any old async function; maybe this is calling a REST API or something
///
/// // an async function whose results can't be serialized from the server to the client
/// // (for example, opening a connection to the user's device camera)
/// async fn setup_complicated_struct() -> ComplicatedUnserializableStruct {
/// // do some work
/// ComplicatedUnserializableStruct {}
@@ -786,9 +798,11 @@ where
fn try_with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
let location = std::panic::Location::caller();
with_runtime(|runtime| {
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
resource.with_maybe(f, location, self.id)
})
runtime
.try_resource(self.id, |resource: &ResourceState<S, T>| {
resource.with_maybe(f, location, self.id)
})
.flatten()
})
.ok()
.flatten()

View File

@@ -190,27 +190,6 @@ impl Runtime {
self.mark_clean(node_id);
}
pub(crate) fn cleanup_node(&self, node_id: NodeId) {
// first, run our cleanups, if any
let c = { self.on_cleanups.borrow_mut().remove(node_id) };
// untrack around all cleanups
let prev_observer = self.observer.take();
if let Some(cleanups) = c {
for cleanup in cleanups {
cleanup();
}
}
self.observer.set(prev_observer);
// dispose of any of our properties
let properties = { self.node_properties.borrow_mut().remove(node_id) };
if let Some(properties) = properties {
for property in properties {
self.cleanup_property(property);
}
}
}
pub(crate) fn update(&self, node_id: NodeId) {
let node = {
let nodes = self.nodes.borrow();
@@ -254,53 +233,66 @@ impl Runtime {
}
}
pub(crate) fn cleanup_property(&self, property: ScopeProperty) {
pub(crate) fn dispose_node(&self, node_id: NodeId) {
self.cleanup_node(node_id);
// each of the subs needs to remove the node from its dependencies
// so that it doesn't try to read the (now disposed) signal
let subs = self.node_subscribers.borrow_mut().remove(node_id);
if let Some(subs) = subs {
let source_map = self.node_sources.borrow();
for effect in subs.borrow().iter() {
if let Some(effect_sources) = source_map.get(*effect) {
effect_sources.borrow_mut().swap_remove(&node_id);
}
}
}
self.node_sources.borrow_mut().remove(node_id);
let node = { self.nodes.borrow_mut().remove(node_id) };
drop(node);
}
fn cleanup_node(&self, node_id: NodeId) {
self.run_on_cleanups(node_id);
self.dispose_children(node_id);
}
/// Dispose of all of the children of the node recursively and completely.
fn dispose_children(&self, node_id: NodeId) {
let properties = { self.node_properties.borrow_mut().remove(node_id) };
if let Some(properties) = properties {
for property in properties {
self.dispose_property(property);
}
}
}
fn dispose_property(&self, property: ScopeProperty) {
// for signals, triggers, memos, effects, shared node cleanup
match property {
ScopeProperty::Signal(node)
| ScopeProperty::Trigger(node)
| ScopeProperty::Effect(node) => {
// run all cleanups for this node
let cleanups = { self.on_cleanups.borrow_mut().remove(node) };
for cleanup in cleanups.into_iter().flatten() {
cleanup();
}
// clean up all children
let properties =
{ self.node_properties.borrow_mut().remove(node) };
for property in properties.into_iter().flatten() {
self.cleanup_property(property);
}
// each of the subs needs to remove the node from its dependencies
// so that it doesn't try to read the (now disposed) signal
let subs = self.node_subscribers.borrow_mut().remove(node);
if let Some(subs) = subs {
let source_map = self.node_sources.borrow();
for effect in subs.borrow().iter() {
if let Some(effect_sources) = source_map.get(*effect) {
effect_sources.borrow_mut().swap_remove(&node);
}
}
}
// no longer needs to track its sources
self.node_sources.borrow_mut().remove(node);
// remove the node from the graph
let node = { self.nodes.borrow_mut().remove(node) };
drop(node);
self.dispose_node(node);
}
ScopeProperty::Resource(id) => {
self.resources.borrow_mut().remove(id);
let value = self.resources.borrow_mut().remove(id);
drop(value);
}
ScopeProperty::StoredValue(id) => {
self.stored_values.borrow_mut().remove(id);
let value = self.stored_values.borrow_mut().remove(id);
drop(value);
}
}
}
fn run_on_cleanups(&self, node_id: NodeId) {
let c = { self.on_cleanups.borrow_mut().remove(node_id) };
let prev_observer = self.observer.take(); // untrack around all cleanups
if let Some(cleanups) = c {
for cleanup in cleanups {
cleanup();
}
}
self.observer.set(prev_observer);
}
pub(crate) fn cleanup_sources(&self, node_id: NodeId) {
let sources = self.node_sources.borrow();
@@ -500,12 +492,6 @@ impl Runtime {
}
}
pub(crate) fn dispose_node(&self, node: NodeId) {
self.node_sources.borrow_mut().remove(node);
self.node_subscribers.borrow_mut().remove(node);
self.nodes.borrow_mut().remove(node);
}
#[track_caller]
pub(crate) fn register_property(
&self,
@@ -643,7 +629,7 @@ impl Runtime {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[track_caller]
@@ -658,7 +644,7 @@ impl Runtime {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[track_caller]
@@ -1189,11 +1175,7 @@ impl RuntimeId {
);
(id, move || {
with_runtime(|runtime| {
runtime.nodes.borrow_mut().remove(id);
runtime.node_sources.borrow_mut().remove(id);
})
.expect(
with_runtime(|runtime| runtime.dispose_node(id)).expect(
"tried to stop a watch in a runtime that has been disposed",
);
})
@@ -1400,7 +1382,7 @@ impl Drop for SetObserverOnDrop {
///
/// To avoid panicking under any circumstances, use [`try_batch`].
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
@@ -1416,7 +1398,7 @@ pub fn batch<T>(f: impl FnOnce() -> T) -> T {
///
/// Unlike [`batch`], this will not panic if the runtime has been disposed.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
@@ -1465,7 +1447,7 @@ pub fn on_cleanup(cleanup_fn: impl FnOnce() + 'static) {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn push_cleanup(cleanup_fn: Box<dyn FnOnce()>) {
@@ -1527,7 +1509,7 @@ impl ScopeProperty {
/// # runtime.dispose();
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
@@ -1537,7 +1519,7 @@ pub fn untrack<T>(f: impl FnOnce() -> T) -> T {
#[doc(hidden)]
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]

View File

@@ -332,7 +332,7 @@ pub trait SignalDispose {
/// #
/// ```
#[cfg_attr(
any(debug_assertions, features="ssr"),
any(debug_assertions, feature="ssr"),
instrument(
level = "trace",
skip_all,
@@ -354,7 +354,7 @@ pub fn create_signal<T>(value: T) -> (ReadSignal<T>, WriteSignal<T>) {
/// **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"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn create_signal_from_stream<T>(
@@ -1143,7 +1143,7 @@ impl<T> Hash for WriteSignal<T> {
/// #
/// ```
#[cfg_attr(
any(debug_assertions, features="ssr"),
any(debug_assertions, feature="ssr"),
instrument(
level = "trace",
skip_all,
@@ -1432,17 +1432,17 @@ impl<T> SignalSetUntracked<T> for RwSignal<T> {
impl<T> SignalUpdateUntracked<T> for RwSignal<T> {
#[cfg_attr(
any(debug_assertions, features="ssr"),
instrument(
level = "trace",
name = "RwSignal::update_untracked()",
skip_all,
fields(
id = ?self.id,
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
any(debug_assertions, feature="ssr"),
instrument(
level = "trace",
name = "RwSignal::update_untracked()",
skip_all,
fields(
id = ?self.id,
defined_at = %self.defined_at,
ty = %std::any::type_name::<T>()
)
)
)
)]
#[inline(always)]
fn update_untracked(&self, f: impl FnOnce(&mut T)) {

View File

@@ -43,3 +43,66 @@ fn cleanup() {
runtime.dispose();
}
#[test]
fn cleanup_on_dispose() {
use leptos_reactive::{
create_memo, create_runtime, create_trigger, on_cleanup, SignalDispose,
SignalGetUntracked,
};
struct ExecuteOnDrop(Option<Box<dyn FnOnce()>>);
impl ExecuteOnDrop {
fn new(f: impl FnOnce() + 'static) -> Self {
Self(Some(Box::new(f)))
}
}
impl Drop for ExecuteOnDrop {
fn drop(&mut self) {
self.0.take().unwrap()();
}
}
let runtime = create_runtime();
let trigger = create_trigger();
println!("STARTING");
let memo = create_memo(move |_| {
trigger.track();
// An example of why you might want to do this is that
// when something goes out of reactive scope you want it to be cleaned up.
// The cleaning up might have side effects, and those side effects might cause
// re-renders where new `on_cleanup` are registered.
let on_drop = ExecuteOnDrop::new(|| {
on_cleanup(|| println!("Nested cleanup in progress."))
});
on_cleanup(move || {
println!("Cleanup in progress.");
drop(on_drop)
});
});
println!("Memo 1: {:?}", memo);
let _ = memo.get_untracked(); // First cleanup registered.
memo.dispose(); // Cleanup not run here.
println!("Cleanup should have been executed.");
let memo = create_memo(move |_| {
// New cleanup registered. It'll panic here.
on_cleanup(move || println!("Test passed."));
});
println!("Memo 2: {:?}", memo);
println!("^ Note how the memos have the same key (different versions).");
let _ = memo.get_untracked(); // First cleanup registered.
println!("Test passed.");
memo.dispose();
runtime.dispose();
}

View File

@@ -293,3 +293,29 @@ fn owning_memo_slice() {
runtime.dispose();
}
#[test]
fn leak_on_dispose() {
use std::rc::Rc;
let runtime = create_runtime();
let trigger = create_trigger();
let value = Rc::new(());
let weak = Rc::downgrade(&value);
let memo = create_memo(move |_| {
trigger.track();
create_rw_signal(value.clone());
});
memo.get_untracked();
memo.dispose();
assert!(weak.upgrade().is_none()); // Should have been dropped.
runtime.dispose();
}

View File

@@ -19,8 +19,8 @@ fn watch_runs() {
move |a, prev_a, prev_ret| {
let formatted = format!(
"Value is {}; Prev is {:?}; Prev return is {:?}",
a, prev_a, prev_ret
"Value is {a}; Prev is {prev_a:?}; Prev return is \
{prev_ret:?}"
);
*b.borrow_mut() = formatted;
@@ -72,8 +72,8 @@ fn watch_runs_immediately() {
move |a, prev_a, prev_ret| {
let formatted = format!(
"Value is {}; Prev is {:?}; Prev return is {:?}",
a, prev_a, prev_ret
"Value is {a}; Prev is {prev_a:?}; Prev return is \
{prev_ret:?}"
);
*b.borrow_mut() = formatted;

View File

@@ -32,3 +32,6 @@ nightly = ["leptos_reactive/nightly"]
[package.metadata.cargo-all-features]
denylist = ["nightly"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -392,7 +392,7 @@ where
let pending_dispatches = Rc::clone(&self.pending_dispatches);
let value = self.value;
pending.set(true);
pending_dispatches.set(pending_dispatches.get().saturating_sub(1));
pending_dispatches.set(pending_dispatches.get().wrapping_add(1));
spawn_local(async move {
let new_value = fut.await;
let res = try_batch(move || {

View File

@@ -242,7 +242,7 @@ where
}
}
/// Creates an [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
/// Creates a [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
///
/// If youre trying to load data by running an `async` function reactively, you probably
/// want to use a [create_resource](leptos_reactive::create_resource) instead. If youre trying
@@ -319,7 +319,7 @@ where
}))
}
/// Creates an [MultiAction] that can be used to call a server function.
/// Creates a [MultiAction] that can be used to call a server function.
///
/// ```rust,ignore
/// # use leptos::*;

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.6.11"
version = "0.6.13"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"
@@ -29,3 +29,6 @@ nightly = ["leptos/nightly"]
[package.metadata.cargo-all-features]
denylist = ["nightly"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -117,7 +117,7 @@ impl core::fmt::Debug for MetaTagsContext {
impl MetaTagsContext {
/// Converts metadata tags into an HTML string.
#[cfg(any(feature = "ssr", docs))]
#[cfg(any(feature = "ssr", doc))]
pub fn as_string(&self) -> String {
self.els
.borrow()

View File

@@ -601,9 +601,9 @@ mod tests {
#[test]
fn debug_fmt_should_display_quotes_for_strings() {
let s: Oco<str> = Oco::Borrowed("hello");
assert_eq!(format!("{:?}", s), "\"hello\"");
assert_eq!(format!("{s:?}"), "\"hello\"");
let s: Oco<str> = Oco::Counted(Rc::from("hello"));
assert_eq!(format!("{:?}", s), "\"hello\"");
assert_eq!(format!("{s:?}"), "\"hello\"");
}
#[test]

View File

@@ -5,3 +5,24 @@ The `projects` directory is intended as a collective of medium-to-large-scale ex
The `examples` directory is included in our CI, and examples are regularly linted and tested. The barrier to entry for the `projects` directory is intended to be lower: Example projects will generally be built against a particular version, and not regularly linted or updated. Hopefully this distinction allows us to accept more examples without worrying about the maintenance burden of constant updates.
Feel free to submit projects to this directory via PR!
## Index
### meilisearch-searchbar
[Meilisearch](https://www.meilisearch.com/) is a search engine built in Rust that you can self-host. This example shows how to run it alongside a leptos server and present a search bar with autocomplete to the user.
### nginx-mpmc
[Nginx](https://nginx.org/) Multiple Producer Multi Consumer, this example shows how you can use Nginx to provide different clients to the user while running multiple Leptos servers that provide server functions to any of the clients.
### ory-kratos
[Ory](https://www.ory.sh/docs/welcome) is a combination of different authorization services. Ory Kratos is their Identification service, which provides password storage, emailing, login and registration functionality, etc. This example shows running Ory Kratos alongside a leptos server and making use of their UI Node data types in leptos. TODO: This example needs a bit more work to show off SSO passwordless etc
### tauri-from-scratch
This example walks you through in explicit detail how to use [Tauri](https://tauri.app/) to render your Leptos App on non web targets using [WebView](https://en.wikipedia.org/wiki/WebView) while communicating with your leptos server and servering an SSR supported web experience. TODO: It could be simplified since part of the readme includes copying and pasting boilerplate.
### counter_dwarf_debug
This example shows how to add breakpoints within the browser or visual studio code for debugging.
### bevy3d_ui
This example uses the bevy 3d game engine with leptos within webassembly.

View File

@@ -0,0 +1,26 @@
[package]
name = "bevy3d_ui"
version = "0.1.0"
edition = "2021"
[profile.release]
codegen-units = 1
lto = true
[dependencies]
leptos = { version = "0.6.13", features = ["csr"] }
leptos_meta = { version = "0.6.13", features = ["csr"] }
leptos_router = { version = "0.6.13", features = ["csr"] }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
bevy = "0.13.2"
crossbeam-channel = "0.5.12"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"
[workspace]
# The empty workspace here is to keep rust-analyzer satisfied

View File

@@ -0,0 +1,15 @@
# Bevy 3D UI Example
This example combines a leptos UI with a bevy 3D view.
Bevy is a 3D game engine written in rust that can be compiled to web assembly by using the wgpu library.
The wgpu library in turn can target the newer webgpu standard or the older webgl for web browsers.
In the case of a desktop application, if you wanted to use a styled ui via leptos and a 3d view via bevy
you could also combine this with tauri.
## Quick Start
* Run `trunk serve to run the example.
* Browse to http://127.0.0.1:8080/
It's best to use a web browser with webgpu capability for best results such as Chrome or Opera.

View File

@@ -0,0 +1,8 @@
<!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.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "stable" # test change

View File

@@ -0,0 +1,38 @@
use bevy::prelude::*;
/// Event Processor
#[derive(Resource)]
pub struct EventProcessor<TSender, TReceiver> {
pub sender: crossbeam_channel::Sender<TSender>,
pub receiver: crossbeam_channel::Receiver<TReceiver>,
}
impl<TSender, TReceiver> Clone for EventProcessor<TSender, TReceiver> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
receiver: self.receiver.clone(),
}
}
}
/// Events sent from the client to bevy
#[derive(Debug)]
pub enum ClientInEvents {
/// Update the 3d model position from the client
CounterEvt(CounterEvtData),
}
/// Events sent out from bevy to the client
#[derive(Debug)]
pub enum PluginOutEvents {
/// TODO Feed back to the client an event from bevy
Click,
}
/// Input event to update the bevy view from the client
#[derive(Clone, Debug, Event)]
pub struct CounterEvtData {
/// Amount to move on the Y Axis
pub value: f32,
}

View File

@@ -0,0 +1,2 @@
pub mod events;
pub mod plugin;

View File

@@ -0,0 +1,63 @@
use super::events::*;
use bevy::prelude::*;
/// Events plugin for bevy
#[derive(Clone)]
pub struct DuplexEventsPlugin {
/// Client processor for sending ClientInEvents, receiving PluginOutEvents
client_processor: EventProcessor<ClientInEvents, PluginOutEvents>,
/// Internal processor for sending PluginOutEvents, receiving ClientInEvents
plugin_processor: EventProcessor<PluginOutEvents, ClientInEvents>,
}
impl DuplexEventsPlugin {
/// Create a new instance
pub fn new() -> DuplexEventsPlugin {
// For sending messages from bevy to the client
let (bevy_sender, client_receiver) = crossbeam_channel::bounded(50);
// For sending message from the client to bevy
let (client_sender, bevy_receiver) = crossbeam_channel::bounded(50);
let instance = DuplexEventsPlugin {
client_processor: EventProcessor {
sender: client_sender,
receiver: client_receiver,
},
plugin_processor: EventProcessor {
sender: bevy_sender,
receiver: bevy_receiver,
},
};
instance
}
/// Get the client event processor
pub fn get_processor(
&self,
) -> EventProcessor<ClientInEvents, PluginOutEvents> {
self.client_processor.clone()
}
}
/// Build the bevy plugin and attach
impl Plugin for DuplexEventsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(self.plugin_processor.clone())
.init_resource::<Events<CounterEvtData>>()
.add_systems(PreUpdate, input_events_system);
}
}
/// Send the event to bevy using EventWriter
fn input_events_system(
int_processor: Res<EventProcessor<PluginOutEvents, ClientInEvents>>,
mut counter_event_writer: EventWriter<CounterEvtData>,
) {
for input_event in int_processor.receiver.try_iter() {
match input_event {
ClientInEvents::CounterEvt(event) => {
// Send event through Bevy's event system
counter_event_writer.send(event);
}
}
}
}

View File

@@ -0,0 +1,3 @@
pub mod eventqueue;
pub mod scene;
pub mod state;

View File

@@ -0,0 +1,124 @@
use super::eventqueue::events::{
ClientInEvents, CounterEvtData, EventProcessor, PluginOutEvents,
};
use super::eventqueue::plugin::DuplexEventsPlugin;
use super::state::{Shared, SharedResource, SharedState};
use bevy::prelude::*;
/// Represents the Cube in the scene
#[derive(Component, Copy, Clone)]
pub struct Cube;
/// Represents the 3D Scene
#[derive(Clone)]
pub struct Scene {
is_setup: bool,
canvas_id: String,
evt_plugin: DuplexEventsPlugin,
shared_state: Shared<SharedState>,
processor: EventProcessor<ClientInEvents, PluginOutEvents>,
}
impl Scene {
/// Create a new instance
pub fn new(canvas_id: String) -> Scene {
let plugin = DuplexEventsPlugin::new();
let instance = Scene {
is_setup: false,
canvas_id: canvas_id,
evt_plugin: plugin.clone(),
shared_state: SharedState::new(),
processor: plugin.get_processor(),
};
instance
}
/// Get the shared state
pub fn get_state(&self) -> Shared<SharedState> {
self.shared_state.clone()
}
/// Get the event processor
pub fn get_processor(
&self,
) -> EventProcessor<ClientInEvents, PluginOutEvents> {
self.processor.clone()
}
/// Setup and attach the bevy instance to the html canvas element
pub fn setup(&mut self) {
if self.is_setup == true {
return;
};
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some(self.canvas_id.clone()),
..default()
}),
..default()
}))
.add_plugins(self.evt_plugin.clone())
.insert_resource(SharedResource(self.shared_state.clone()))
.add_systems(Startup, setup_scene)
.add_systems(Update, handle_bevy_event)
.run();
self.is_setup = true;
}
}
/// Setup the scene
fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
resource: Res<SharedResource>,
) {
let name = resource.0.lock().unwrap().name.clone();
// circular base
commands.spawn(PbrBundle {
mesh: meshes.add(Circle::new(4.0)),
material: materials.add(Color::WHITE),
transform: Transform::from_rotation(Quat::from_rotation_x(
-std::f32::consts::FRAC_PI_2,
)),
..default()
});
// cube
commands.spawn((
PbrBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
material: materials.add(Color::rgb_u8(124, 144, 255)),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
},
Cube,
));
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.5, 4.5, 9.0)
.looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
commands.spawn(TextBundle::from_section(name, TextStyle::default()));
}
/// Move the Cube on event
fn handle_bevy_event(
mut counter_event_reader: EventReader<CounterEvtData>,
mut cube_query: Query<&mut Transform, With<Cube>>,
) {
let mut cube_transform = cube_query.get_single_mut().expect("no cube :(");
for _ev in counter_event_reader.read() {
cube_transform.translation += Vec3::new(0.0, _ev.value, 0.0);
}
}

View File

@@ -0,0 +1,24 @@
use bevy::ecs::system::Resource;
use std::sync::{Arc, Mutex};
pub type Shared<T> = Arc<Mutex<T>>;
/// Shared Resource used for Bevy
#[derive(Resource)]
pub struct SharedResource(pub Shared<SharedState>);
/// Shared State
pub struct SharedState {
pub name: String,
}
impl SharedState {
/// Get a new shared state
pub fn new() -> Arc<Mutex<SharedState>> {
let state = SharedState {
name: "This can be used for shared state".to_string(),
};
let shared = Arc::new(Mutex::new(state));
shared
}
}

View File

@@ -0,0 +1 @@
pub mod bevydemo1;

View File

@@ -0,0 +1,11 @@
mod demos;
mod routes;
use leptos::*;
use routes::RootPage;
pub fn main() {
// Bevy will output a lot of debug info to the console when this is enabled.
//_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|| view! { <RootPage/> })
}

View File

@@ -0,0 +1,52 @@
use crate::demos::bevydemo1::eventqueue::events::{
ClientInEvents, CounterEvtData,
};
use crate::demos::bevydemo1::scene::Scene;
use leptos::*;
/// 3d view component
#[component]
pub fn Demo1() -> impl IntoView {
// Setup a Counter
let initial_value: i32 = 0;
let step: i32 = 1;
let (value, set_value) = create_signal(initial_value);
// Setup a bevy 3d scene
let scene = Scene::new("#bevy".to_string());
let sender = scene.get_processor().sender;
let (sender_sig, _set_sender_sig) = create_signal(sender);
let (scene_sig, _set_scene_sig) = create_signal(scene);
// We need to add the 3D view onto the canvas post render.
create_effect(move |_| {
request_animation_frame(move || {
scene_sig.get().setup();
});
});
view! {
<div>
<button on:click=move |_| set_value.set(0)>"Clear"</button>
<button on:click=move |_| {
set_value.update(|value| *value -= step);
let newpos = (step as f32) / 10.0;
sender_sig
.get()
.send(ClientInEvents::CounterEvt(CounterEvtData { value: -newpos }))
.expect("could not send event");
}>"-1"</button>
<span>"Value: " {value} "!"</span>
<button on:click=move |_| {
set_value.update(|value| *value += step);
let newpos = step as f32 / 10.0;
sender_sig
.get()
.send(ClientInEvents::CounterEvt(CounterEvtData { value: newpos }))
.expect("could not send event");
}>"+1"</button>
</div>
<canvas id="bevy" width="800" height="600"></canvas>
}
}

View File

@@ -0,0 +1,24 @@
pub mod demo1;
use demo1::Demo1;
use leptos::*;
use leptos_meta::{provide_meta_context, Meta, Stylesheet, Title};
use leptos_router::*;
#[component]
pub fn RootPage() -> impl IntoView {
provide_meta_context();
view! {
<Meta name="charset" content="UTF-8"/>
<Meta name="description" content="Leptonic CSR template"/>
<Meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<Meta name="theme-color" content="#e66956"/>
<Stylesheet href="https://fonts.googleapis.com/css?family=Roboto&display=swap"/>
<Title text="Leptos Bevy3D Example"/>
<Router>
<Routes>
<Route path="" view=|| view! { <Demo1/> }/>
</Routes>
</Router>
}
}

View File

@@ -0,0 +1,2 @@
# For this example we want to include the vscode files
!.vscode

View File

@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Browser Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:4001",
"webRoot": "${workspaceFolder}/dist",
// Needed to keep the dwarf extension in the browser
"userDataDir": false,
"preLaunchTask": "trunk: serve",
"postDebugTask": "postdebugKill"
},
]
}

View File

@@ -0,0 +1,53 @@
{
"version": "2.0.0",
"tasks": [
// Task to build the sources
{
"label": "trunk: build",
"type": "shell",
"command": "trunk",
"args": ["build"],
"problemMatcher": [
"$rustc"
],
"group": "build",
},
// Task to launch trunk serve for debugging
{
"label": "trunk: serve",
"type": "shell",
"command": "trunk",
"args": ["serve"],
"isBackground": true,
"problemMatcher": {
"pattern": {
"regexp": ".",
"file": 1,"line": 1,
"column": 1,"message": 1
},
"background": {
"activeOnStart": true,
"beginsPattern": ".",
"endsPattern": "."
}
}
},
// Terminate the trunk serve task
{
"label": "postdebugKill",
"type": "shell",
"command": "echo ${input:terminate}",
},
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "terminateAll"
}
]
}

View File

@@ -0,0 +1,22 @@
[workspace]
# The empty workspace here is to keep rust-analyzer satisfied
[package]
name = "counter_dwarf_debug"
version = "0.1.0"
edition = "2021"
[profile.release]
codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -0,0 +1,74 @@
# Debugging Leptos Counter Example in Browser and VSCode
This example builds on the simple counter by adding breakpoints and single stepping the source code for debugging.
Both within the browser and VSCode.
This uses a new feature of wasm called Dwarf which is a form of source code mapping.
Note variable inspection during the breakpoints doesn't seem to work at this stage.
## Quick Start
* Install the requirements below
* Open this directory within visual studio code
* Add a breakpoint to the code
* Launch the example using the visual studio code debug launcher
## How This Works
### Html Changes
First we need to make a change to the index.html file
From this
```html
<link data-trunk rel="rust" data-wasm-opt="z"/>
```
To this
```html
<link data-trunk rel="rust" data-keep-debug="true" data-wasm-opt="z"/>
```
This instructs the rust `trunk` utility to pass a long an option to `wasm-bindgen` called `--keep-debug`
This option bundles in a type of sourcemap into the built wasm file.
Be aware that this will make the wasm file much larger.
### Browser Changes
Next we need to allow the browser to read the DWARF data from the wasm file.
For Chrome / Opera there's an extension here that needs to be installed.
* https://chromewebstore.google.com/detail/cc++-devtools-support-dwa/pdcpmagijalfljmkmjngeonclgbbannb?pli=1
## Debugging within the Browser
Within the browser's dev console it should now be possible to view the rust source code and add breakpoints.
![Chrome Debug Image](./img/breakpoint1.png)
## Debugging within VSCode
Note this is still experimental, although I have managed to get breakpoints working under VSCode.
So far I've only tried this within a windows environment.
In order to have the breakpoints land at the correct position.
We need to install the following VSCode extension.
* [WebAssembly DWARF Debugging](https://marketplace.visualstudio.com/items?itemName=ms-vscode.wasm-dwarf-debugging)
Within the browser launch section under `launch.json` we need to set userDataDir to false in order for the DWARF browser extension to be loaded.
```json
{
"name": "Launch Browser Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/dist",
// Needed to keep the dwarf extension in the browser
"userDataDir": false,
},
```
Now we should be able to add breakpoints within visual studio code while debugging the rust wasm.
![Chrome Debug Image](./img/breakpoint2.png)

View File

@@ -0,0 +1,4 @@
[serve]
address = "127.0.0.1"
port = 4001
open = false

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-keep-debug="true" data-wasm-opt="z"/>
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
</head>
<body></body>
</html>

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