Previous commits that aimed at fixing indexing for Myers algorithm broke
insertion before/after a fragment in a html tag, resulting in incorrect
ordering/error
* feat: support conversion from signals and optional get extension for TextProp
* [autofix.ci] apply automated fixes
* remove unused import
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* Add reqwest-no-ws feature to server_fn
* Add dep:tokio to server_fn/reqwest-no-ws
* Fix
* Refactor reqwest-no-ws feature in server_fn crate for wasm32 support
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* Ad cf-worker example
* Fix error messages for trybuild
* Revert "Fix error messages for trybuild"
This reverts commit 42658dd031.
* Fix CI error by disabling on reqwest-no-ws aslike other feature
* Compact deps and add ci
* Revert all server_fn changes as main
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
#4099 introduced caching for inert elements, changing the API for [`dom::Dom::create_element_from_html`](30b0a579ca/tachys/src/renderer/dom.rs (L495)). This updates `svg::InertElement` to match the new API.
* feat(CI): add checking minimal-versions
* chore: unify all deps + exact versioning in root workspace for better maintenance
* feat(CI): run minimal-versions only on release tag
* feat(CI): re-adding dependabot with grouped weekly updates
- The `res_overview` previously being tracked inside the async caused an
extra fetch to happen, and with that tracking being corrected, this no
longer happens.
- Left a comment about how this is untracked.
* fix(CI): prevent regreession from nightly clippy in autofix
* chore: format
* chore: update nightly to 2025-04-16 (proc-macro span, #3852)
* chore: improve the autofix ci workflow
* fix: adjust ServerFn macro test stderr based on nightly-2025-04-16
* fix: limit server_fn server macro trybuild tests nightly only
* fix(examples): incorrect routes in hackernews example (closes#3892)
1. `Avoid calling category()` twice on the story type.
2. `get_static_file()` returns Err on not found, so don’t
unconditionally `unwrap()` it
* cargo fmt
---------
Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
* chore: easing `Display` bound on `FromServerFnError`, #3811 follow-up
* fix: send/receive websocket data
* fix: clippy warnings
* fix: server_fn_axum example
* fix: make de/serialize_result functions public
* fix: make websocket result ser/de private
* chore: make the doc a comment and remove allow dead_code
The tailwind_csr example was not working yet with version 4, and the
tailwind_actix example still had the no-longer-needed config file.
This also brings the three tailwind examples back in sync, mostly. I
don't know why the axum example has a packages.json and
packages-lock.json file, to be honest.
* feat: allow using different error types for request/response with WebSocket
* [autofix.ci] apply automated fixes
* chore: clean up merge issues
* chore: fix custom client example
* we can't use nightly features on stable
* update flake inputs
* include gcc and glib to flake dev shell
* update expected stderr outputs server_fn/tests/invalid
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
* fix: intoducing cargo all-features clippy
* fix: change check to clippy for better diagnostics
* chore: build is redundant
* fix: lint task
* feat: speed up tests by 2x with cargo-nextest
* fix: re-add flags
* fix: router example build process
* fix: correct clippy arguments
* fix: adapt examples makefile to new tools
* fix(CI): add cargo-all-features part of CI dep
* fix: just warn if there is no tests
* fix(CI): add clippy and rustfmt components
* fix: nextest in examples
* fix: clippy should not run on empty set of features in examples
(quickfix)
* fix: pin cargo-all-features installation to a branch
* fix: nextest --no-tests=warn?
* fix: do not use built-in cargo-make workflows
* fix: remove --all-targets
* fix: do not run tests in parallel in certain examples
* fix: disable nextest for examples
* fix: properly override the make task
* chore: run tests with no-deps
* fix: hackernews_islands_axum
* fix(CI): properly use --no-deps
* fix(CI): --no-deps is not supported in tests
* fix(CI): run doctests separately due to stable rust limitation in nextest
* fix(examples): makefile lint recursion
* fix(CI): run tests correctly
* fix: remove unused clear in test makefile
* fix: --no-deps positional arg in clippy
* fix: run doctests for all-features
* fix: running cargo-all-features over doctests
* fix: improve playwright makefile
documentation e.g., input.css and output.css.
Uses @tailwindcss/cli tool to build the output.css
Removes no longer needed tailwind.config.js as that configuration can be
embedded in input.css.
Removes src/style.css as it only included tailwind 3 configurations that
are no longer needed.
* Move several Into* trait impls for store fields out of stable module for wider use
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
- Enhanced error messages for better clarity when server functions do not meet the expected return type requirements.
- Added `trybuild` tests to ensure behaviour is not broken in future releases.
* fix(CI): run only the exact examples on the only examples change
* chore: format html (retrigger ci)
* fix: missing the right field
* fix(CI): add missing output key
* fix(CI): properly set matrix directory for test-only-examples
* fix(CI): missing bracket token
* fix(CI): do not run semver check on examples only tests
* fix(CI): semver logic
* chore: optimize CI
* chore: fomratting makefile to retrigger ci
* fix: run tests against both stable and nightly toolchains
* chore: simplify workflow
* fix(CI): remove unused inputs
* fix(CI): correctly indicating erased mode
* fix(CI): replace input with the name
* fix(CI): fixed logic of running cargo cmake on workspace on each example
and memeber!
* fix(CI): organized, simplified and unified CI jobs
* chore: change CI name
* fix(CI): keyword
* fix(CI): wait for dependent jobs
* feat: switch `extract()` helper to use `ServerFnErrorErr`
This provides much better compatibility with server functions using
custom errors while still working as before with server functions using
`ServerFnError`
* style: apply autofix diff
* chore: no need to filter out "nightly" feature as of #3735
* fix: enable hot-reload only if in all debug mode, nightly feature and
nightly rustc
* fix: do not build/test against nightly feature alone
* fix: don not test against nightly feature alone
* fix: add trace-components to denylist
* fix(CI): pre-install tailwind and sass
* chore: retrigger CI
* fix(CI): properly set `erased_mode` in examples
* fix(CI): workflow logic install deps locally
* chore(CI): organized tailwind and sass versions
* fix: properly ignore nightly features on stable rustc
* fix(CI): typo
* fix: missing cfg attr
* fix: properly gate nightly module in `reactive_graph``
* fix: complete `rustc_nightly` cfg gate
* fix(CI): making even more space for CI workers
* fix: missing one more rustc_nightly gated feature
* fix: maximize the speed of cargo-all-features by limiting combinations to 2
* Split up the server function expansion into multiple functions
* Use LitStr instead of Literal
* Refactor out more of the expansion
* Fix the parse example
* Expose some of the parsed server function arguments and body fields
* mark ServerFnBody as non_exhaustive
* [autofix.ci] apply automated fixes
* Fix server function paths
* rename from impl
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
- Also as reported in #3719, which has an actual minimum example.
- The "quicker" test had a reset but that runs into timing issue with
the way this reset is done too soon after resource usage, so leaving
this out and we will just trust the bigger counters.
* Erased mode in CI
* Trigger CI
* Rename dev_mode erased_mode plus add to more matrices
* nested routes in separate component fix
* Fix lint
* Small fixes
* Fixes
* proc-macro rustflags cross-compilation workaround with internal erasure feature for leptos_macro
* Re-trigger CI
* fix unrelated doc CI and remove unneeded IntoAttribute trait
* Fix StaticVec rebuild() fn
* Conflict fixes
* Maybe fix
* Bump example toolchain
integrations/actix
leptos/server
oco
server_fn
This is a defensive PR - Putting the crate definition into the root
workspcace makes it less likely to get difficult to trace version
slip bugs.
This does not help where sede_json is optional so care manual review
is required.
The bugfix is related to access to a signal
"
bevy3d_ui-b20a0a6a298e7144.js:2637 At src/routes/demo1.rs:24:23, you access a
reactive_graph::signal::read::ReadSignal<bevy3d_ui::demos::bevydemo1::scene::Scene>
(defined at src/routes/demo1.rs:19:39) outside a reactive tracking context.
This might mean your app is not responding to changes in signal values in the way you expect.
"
The solution is to use .get_untracked() inside an "effect" block.
Lots of small clippy fixes.
Also here are the crates that have been bumped
-wasm-bindgen = "0.2.92"
-wasm-bindgen-test = "0.3.42"
-web-sys = "0.3.69"
+wasm-bindgen = "0.2.100"
+wasm-bindgen-test = "0.3.50"
+web-sys = "0.3.77"
* Resolved this warning see while running cargo outdated
warning: Feature js of package getrandom has been obsolete in version 0.3.1
Now using the feature "wasm_js"
* the crate "getrandom" needs special configuration.
* getrandom_backend - a more generic config.
* Removed the crate getrandom
* Minor: "wasm-bindgen" - Moved the crate definition up to the root workspace
This synchronizes the version number amongst all sub-projects.
[Where the definition is "optional" manual adjustment is still required]
* [autofix.ci] apply automated fixes
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat: Allow disabling server fn hash and customizing the default prefix
Allow configuring the default prefix for server function API routes. This is useful to
override the default prefix (`/api`) for all server functions without needing to manually
specify via `#[server(prefix = "...")]` on every server function.
Also, allow disabling appending the server functions' hashes to the end of their API names.
This is useful when an app's client side needs a stable server API. For example, shipping
the CSR WASM binary in a Tauri app. Tauri app releases are dependent on each platform's
distribution method (e.g., the Apple App Store or the Google Play Store), which typically
are much slower than the frequency at which a website can be updated. In addition, it's
common for users to not have the latest app version installed. In these cases, the CSR WASM
app would need to be able to continue calling the backend server function API, so the API
path needs to be consistent and not have a hash appended.
* Mark public structs as `#[non_exhaustive]` and add doc comments
* Minor refactor to pull the fn hash logic out of the `path` statement
* feat: Use module path in prefix for server fn API route
Allow including the module path of the server function in the API route. This
is an alternative strategy to prevent duplicate server function API routes
(the default strategy is to add a hash to the end of the route). Each element
of the module path will be separated by a `/`. For example, a server function
with a fully qualified name of `parent:🧒:server_fn` would have an API
route of `/api/parent/child/server_fn` (possibly with a different prefix and
a hash suffix depending on the values of the other server fn configs).
* Fix `enable_hash` if statement
* Add missing import
this unifies the code path. The alternative will *move* arguments passed
to the debug_warn! macro only when compiling without debug_assertions.
This version will instead always use additional format arguments by reference.
This change allows users to clone RouteChildren and call .into_inner() on it to get an owned version of <Children>, and then pass on the original RouteChildren to an underlying Routes, or ParentRoute component.
This helps users snapshot all possible routes in an application.
I also thought about making the implementation more generic, for example over
T: AsRef<str>, but that would change the associated types and be a breaking change
it would also conflict with the `Option` impl due to the orphan rules.
Maybe there is something to be done with Borrow or Deref in a future release?
The `file_and_error_handler` provided by leptos_axum does not provide
the app's custom state in the context. This means that when the fallback
handler tries to render the app, the app will not be able to access the
custom state (and will panic if it uses `expect_context`).
The handle defined by `file_and_error_handler` has access to the custom
state from Axum, so we can pass the custom state from Axum into the
Leptos handler using the `additional_context` parameter of
`handle_response_inner`.
* rc2 version any_spawner
Signed-off-by: Darwin Boersma <darwin@sadlark.com>
* reexport full any_spawner crate
Signed-off-by: Darwin Boersma <darwin@sadlark.com>
---------
Signed-off-by: Darwin Boersma <darwin@sadlark.com>
* feat: add `autofix.ci` to address formatting issues and possible clippy
fixes
* fix: initial run of `autofix.ci` script to prevent PR pollution at first
run
* fix: typo and indent issue in `autofix.yml`
* fix: run `autofix.ci` over members with no features
* fix: remove examples and benchmarks from dependabot search path
* chore: update/upgrade deps to prevent dependabot PR pollution at first
run
* fix: increase number of pull requests from dependabot as the workspace
is pretty big
* fix: revert rkyv version as it was unexpectedly downgraded
* fix: tower in example
* fix: ensure we check memos the first time a dependency uses them, even if the dependency always runs on its first run (closes#3181)
* Correct expected counter values down due to #3182
- As #3182 fixed the issue where superfluous resource fetches happened
when hydration happened inside a nested component, the expected values
for the counters are down to where they actually are supposed to be.
---------
Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
* feat: WIP wasi integrations crate
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* feat(server_fn): add generic types
This commit adds `From` implementations for the
`Req` and `Res` types using abstraction that are deemed
"platform-agnostic".
Indeed, both the `http` and `bytes` crates contains types
that allows us to represent HTTP Request and Response,
while being capable to target unconventional platforms
(they even have `no-std` support). This allows the
server_fn functions to target new platforms,
for example, the `wasm32-wasip*` targets.
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(server_fn): generic types cleanup
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* feat(integrations/wasi): make WASI a first-class citizen of leptos server-side
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* WIP: chore(any_spawner): make the futures::Executor runable
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* fix(server_fn): include `generic` in axum.
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(any_spawner): some clippy suggestions
I ran clippy in really annoying mode since I am still
learning Rust and I want to write clean idiomatic code.
I took suggestions that I thought made sense, if any
maintainers think those are *too much*, I can relax
those changes:
* Use `core` instead of `std` to ease migration to `no_std`
(https://rust-lang.github.io/rust-clippy/master/index.html#/std_instead_of_core)
* Add documentation on exported types and statics
* Bring some types in, with `use`
* Add `#[non_exhaustive]` on types we are not sure we
won't extend (https://rust-lang.github.io/rust-clippy/master/index.html#exhaustive_enums)
* Add `#[inline]` to help the compiler when doing
cross-crate compilation and Link-Time optimization
is not enabled. (https://rust-lang.github.io/rust-clippy/master/index.html#/missing_inline_in_public_items)
* Use generic types instead of anonymous `impl` params
so callers can use the `::<>` turbofish syntax (https://rust-lang.github.io/rust-clippy/master/index.html#/impl_trait_in_params)
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(leptos_wasi): fine-tune linter and clean-up
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* feat(leptos_wasi): better handling of server fn with form
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: cargo fmt
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: remove custom clippy
Remove clippy crate rules since it
seems to make tests fails in tests.
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: use `wasi` crate
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: revert changes to any_spawner
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: simpler crate features + cleanup
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* feat(any_spawner): add local custom executor
This commit adds a single-thread "local"
custom executor, which is useful for environments
like `wasm32` targets.
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* feat(leptos_wasi): async runtime
This commit adds a single-threaded
async runtime for `wasm32-wasip*`
targets.
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* feat(leptos_wasi): error handling
This commit adds error types for the users
to implement better error handling.
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: migrate integration off-tree
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(ci): fix formatting
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore: remove ref to leptos_wasi in Cargo.toml
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(ci): fix fmt
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(ci): remove explicit into_inter()
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* chore(ci): make generic mutually exclusive with other options
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
---------
Signed-off-by: Enzo "raskyld" Nocera <enzo@nocera.eu>
* test: first cut of the instrumented suspense_tests
- Based on initial concepts developed for reproducing #2937 and others,
streamlined and instrumented for e2e testing and refined for inclusion
as a standalone module to be plugged into some other App.
* First cut of the fixtures and tests
* Actually make it work properly
- Instead of using examples, just feed it the table because examples
will rerun the whole scenario from scratch, which isn't what we are
trying to test here.
- Provide a basic example with item listing to show how this will work.
* Use ticketing system to disambiguate CSR calls
- Keep all SSR calls on ticket 0 as a means to disambiguate them from
CSR calls. For the mean time the focus of tests isn't on that
behavior but this may be modified when suitable.
* Update the baseline fixtures
- Given the new understanding, the scenerios all being the baseline
tests they are now moved into one file.
- Have the checks against all calls at once for better diff output,
and reword the new scenerios into more idomatic gherkin.
- Streamline the steps and provide additional ones that will help with
feature definitions.
* Translate the reproduction steps into Gherkin
* Comment out logging to avoid output interference.
* Be able to reset CSR counters everywhere
- Done by providing a button directly on the top level component with
the navigation footer. This will be useful for the next test.
* Test showing difference between hydrate and CSR
- Specifically, under hydrated load, resources that shouldn't need
refetching gets refetched, while CSR does not show this issue.
* added two-way data binding to dom elements
* added two-way data binding to radio groups
* moved bind into reactive_graph mod
* chore: use `::leptos` absolute path in macro
* chore: move `Group` into the reactive module, as that's the only place it has meaning
* feat: use new `bind:` syntax
* chore: update for non-generic rendering
* chore: ignore this test
---------
Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
* Enable CDN support for assets
This enables setting a root for a url, especially relating to generated .wasm, .js, and .css files.
This allows using a CDN for static assets.
* fix lint
* Add support for JS and WASM file name hashing
* Add `<HashedStylesheet />`
* Update `<HashedStylesheet />`
* Whoops
* Fix formatting
* My IDE is just refusing to work apparently
* I hate my IDE
* Don't run the doctest
* Just remove the example, I don't know enough about doctest for this
Per https://docs.rs/quote/latest/quote/macro.quote.html#hygiene
regarding token hygiene.
> Any interpolated tokens preserve the Span information provided by their
> `ToTokens` implementation.
In many instances, the procedural macros are spanning tokens with their
own span redundantly.
This attribute seems to be missing in the attribute
table on Mozilla Docs, however does appear in the
compatibility table lower down.
This attribute is also frequently used by temml,
a common generator for mathml content.
* Use `let()` syntax for bindings
This lets users use destructuring when binding more complex values, and we also get better IDE support.
* Update rstml
* 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>
* 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.
* 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
* 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.
* Add beginner tip to ErrorBoundary
This might seem simple, but the nuances of types and traits confuse many people learning the language.
* edit
* Update error_boundary.rs
* edits
* ignore error block
* Added name span to .build in component_to_tokens
* Added #[allow(unreachable_code)] to leptos::component_view inside component_to_tokens
* Added span to name reference in component_to_tokens
* Added span to leptos::component_props_builder in component_to_tokens
* Added span to props in component_to_tokens
* Added span to "on" method in events component_to_tokens
* Added spans in directive_call_from_attribute_node
* Added spans in fragment_to_tokens and it's ssr version
* Added span to props in slot_to_tokens
* Added span to the whole slot quote
* Changed slots's name span to last slot node
* Added span to the slot vec
* Added #[allow(unreachable_code)] to `.into()` in slot_to_tokens
* Added span to `.build()` in slot_to_tokens
* Added span for the whole component
* Added span to "clone:" directive
* Added span to ".children()"
* Removed unused "_span" param from fragment_to_tokens and fragment_to_tokens_ssr
* Removed unnecessary parenthesis around values in `attribute_to_tokens`
* Removed unnecessary curly braces around value in `spread_attrs`
* Removed unnecessary parenthesis around children in `element_to_tokens`
* Added catch-all span to element_to_tokens
* Formatted `quote!` according to official guidelines
* Updated view/snapshots in leptos_macro
* Added span to spread props in element_to_tokens_ssr
* Removed unnecessary curly braces in element_to_token_ssr
* Updated view/snapshots in leptos_macro
* Added view macro tests to leptos_macro
* Fixed clippy warnings in view macro output
* Updated view snapshots in tests
* Fixed expected_one_let_bind_got_none test in leptos_macro
* Removed snapshot tests in leptos_macro/tests/ui/view
---------
Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
Manually reviewed the changes. All look like reasonable nudges.
A summary :-
In one place removed a redundant call to .clone().
In two places, now using clone_from() which clippy says
**MAY** be an optimisation.
It's normal to have a `NotFound` page with a wildcard path like this
```
<Routes>
...
<Route path="*any" view=NotFound>
</Routes>
```
In `ssr` mode, most servers do a `first match win` approach, so we
should register server functions before view routes, or else a wildcard
route would block all api requests.
https://discord.com/channels/1031524867910148188/1218508054442545185
Signed-off-by: 司芳源 <sify21@163.com>
non-serializable struct.
This prevents it from being returned in the
get_user() API, and prevents it from being unintentionally returned on any
new API the end-user may create on top of this example code.
Delegated event listeners do not support adding more than one event listener of the same type. This can cause confusion if two listeners are added, as one is silently dropped.
Instead of using `Closure::once_into_js`, this uses `into_js_value`,
which uses weak references to clean up the closure when Javascript no
longer has need of it.
It would be nice to make this (and the similar interval function) drop
the callback promptly when cancelled, but I don't think that's
possible while keeping the handles Copy.
Fixes#2330
Co-authored-by: Robert Macomber <robertm@mox>
In client-side navigation we now handle redirects returned from
server functions by resolving the location against the current
origin as a base. The base is only relevant if the location
doesn't already include an origin. This fixes cross-origin
redirects.
Note: in order to handle redirects in the same way as the browser
would handle them, we need to use the server function's URL
(typically `<origin>/api/something`) as a base. I leave this as
a TODO for a future leptos version, because it probably
requires changing the signature of the `server_fn` redirect hook.
In order to not be affected by a future breaking change, users
should already start making sure that their redirect locations
either include an origin or at least start with a single slash
(e.g. `Location: /foo`).
Otherwise user gets:
```
use_head() is being called without a MetaContext being provided. We'll automatically create and provide one, but if this is being called in a child route it may cause bugs. To be safe, you should provide_meta_context() somewhere in the root of the app.
```
* fix(ci): address clippy issue
* fix(ci): add missing nightly specifications
* fix(ci): set all nightly references
* chore(ci): do not lint example crates
Note that this is a minimal implementation and will __not__ allow the
user to `expect_state` if they have external calls to rendering their
app (i.e. using `render_app_to_*` directly).
This warning appears in the browser's console log.
```
hackernews.js:933 At src/routes/stories.rs:39:17, you are reading a resource in `hydrate` mode outside a <Suspense/> or <Transition/>. This can cause hydration mismatch errors and loses out on a significant performance optimization. To fix this issue, you can either:
1. Wrap the place where you read the resource in a <Suspense/> or <Transition/> component, or
2. Switch to using create_local_resource(), which will wait to load the resource until the app is hydrated on the client side. (This will have worse performance in most cases.)
```
This warning is seen in the browsers console window.
```
counter_isomorphic.js:1068 At src/counters.rs:138:17, you are reading a resource in `hydrate` mode outside a <Suspense/> or <Transition/>. This can cause hydration mismatch errors and loses out on a significant performance optimization. To fix this issue, you can either:
1. Wrap the place where you read the resource in a <Suspense/> or <Transition/> component, or
2. Switch to using create_local_resource(), which will wait to load the resource until the app is hydrated on the client side. (This will have worse performance in most cases.)
```
Two derived signals "value" and "error_msg" need to be wrapped in a <Suspense> block.
"value" falls back to just the initial text.
"error" uses the default fallback.
* chore: update to axum 0.7
Removed http, since it's included in axum, and replaced hyper by http-body-util, which is a smaller.
* chore: update samples to work with nre axum
Missing sessions_axum_auth, pending PR merge.
* chore: all dependencies update to axum 0.7
* chore: cargo fmt
* chore: fix doctests
* chore: Fix example that in reality doesn't use axum.
Fixed anyway.
* chore: more examples support for axum 0.7
* Small tweak
* retain trailing slashes in paths but leave matching trail-slash-insensitive
* fix: Allow trailing slashes to remain in leptos_path.
* Better handling for trailing slashes. (#2154)
This adds a trailing_slash option to <Router> and <Route>.
By default, this option is backward compatible with current Leptos
behavior, but users can opt into two new modes for handling trailing
slashes.
* cargo fmt
* Fix redirect routes for wildcard patterns.
* Clippy fixies
* (Re)Reduce the scope of PossibleBranchContext's internals.
* Test real code, not copied code.
* Test TrailingSlash redirects.
* Fixes and more tests for matching "" && "/".
This path is the exception to the rule and *should* be treated
as equivalent regardless of its trailing slash.
* cargo fmt
---------
Co-authored-by: Tadas Dailyda <tadas@dailyda.com>
The root cause is the family of leptos modules requiring both versions 0.10.5 and 0.11.0
This PR will fix that. ( Also needs a bump to 0.12.0 )
```
warning: multiple versions for dependency `itertools`: 0.10.5, 0.11.0
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
note: the lint level is defined here
--> src/lib.rs:4:9
|
4 | #![warn(clippy::cargo)]
| ^^^^^^^^^^^^^
= note: `#[warn(clippy::multiple_crate_versions)]` implied by `#[warn(clippy::cargo)]`
```
I've always hated the get_todos function and I
wanted to change it badly. Added a .env file
containing the db url for sqlx-cli, and cleaned
up with leptosfmt
Co-authored-by: j0lol <me@j0.lol>
* fix: add support for placing attributes on server functions
Adding instrumentation to server functions is not straightforward (requires calling out to another ssr-only function which is instrumented). This commit adds all attributes on the server function to both the generated front end and back end functions. If those attributes are only desirable on the backend say, a user can always wrap their attribute in `#[cfg_attr(feature = "ssr", ..)]`.
* nit: formatting in example cargo
Update helix configuration for the newest version.
To be consistent, adding the `server` ignore entry to helix as well.
Also sorting the parameters alphabetically.
* added Callback to documentation and examples.
Also reduced code duplication in Callback implementation.
* added back the closure event callback example
This PR adds the ability to use `create_local_resource` instead of
`create_resource`. This will run the create resource locally on the
system and therefore its result type does not need to be `Seriaziable`.
Closes#1567
This allows form submission with checkbox inputs to work.
For example:
let doit = create_server_action::<DoItSFn>();
<ActionForm action=doit>
<input type="checkbox" name="is_good" value="true"/>
<input type="submit"/>
</ActionForm>
#[server(DoItSFn, "/api")]
pub async fn doit(#[server(default)] is_good: bool) -> Result<(), ServerFnError> {}
If is_good is absent in the request to the server API,
`Default::default()` is used instead.
warning: Rust code block is empty
--> leptos_reactive/src/memo.rs:209:9
|
209 | /// ```
| ^^^
|
= note: `#[warn(rustdoc::invalid_rust_codeblocks)]` on by default
help: mark blocks that do not contain Rust code as text
|
209 | /// ```text
| ++++
These lint warnings.
warning: /home/martin/build/leptos/leptos/Cargo.toml: dependency (serde) specified without providing a local path, Git repository, version, or workspace dependency to use. This will be considered an error in future versions
warning: /home/martin/build/leptos/leptos/Cargo.toml: dependency (serde_json) specified without providing a local path, Git repository, version, or workspace dependency to use. This will be considered an error in future versions
Handle all error codes 401-499 in addition to the
400 and 500-599 that were already handled.
In addition, handle them all in the same way
and improve the error message.
* Updated client reloading to use window.location.protocol/host to determine websocket connection. Added optional config reload_external_port to provide further control of the client websocket connection. These changes allow reloading while accessing the served site from outside of localhost.
On the latest lifetime we're getting the following warning in the server
macro:
warning: `&` without an explicit lifetime name cannot be used here
--> src/login.rs:19:1
|
19 | #[server(Login, "/api")]
| ^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #115010 <https://github.com/rust-lang/rust/issues/115010>
= note: this warning originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
* fix: suppress warning about non-reactivity when calling `.refetch()` (occurs in e.g., async blocks in actions)
* fix: don't reenter reactivity if these are nested
* build(examples): pull up compile tasks
* build(examples): set toolchain for compiles tasks
* build(examples): set toolchain for build and check
* build(examples): set toolchain of other examples
* Update 23_ssr_modes.md
Fixed grammar, added the section anchor links
* Fixed a broken link
The github link doesn't get properly rendered in the Leptos book site. Make the book work, 'break' the github link.
* Update 26_extractors.md
Fixed broken Axum links. Added an Axum extract function doc link for consistency (had Actix, but not Axum before)
In order to facilitate its usage in other structs, which might derive
various traits, such as `serde::Serialize` or `PartialEq`, implement
them for the `leptos_config::Env` enum.
Signed-off-by: Filip Dutescu <filip@hucksy.dev>
Currently, `leptos::LeptosOptions` are deserialized (and serialized)
to `snake_case`, since, by default, `serde` uses the name of the field
as-is. The options given via `.toml` files are given using `kebab-case`.
This behaviour leads to problems if you wish to use
`leptos::LeptosOptions` as the type of a field in your own config,
which uses the `kebab-case` syntax, as it will not provide any values
to that field.
These changes switch out the previous mechanism of manually replacing
the `-` character to the `_` character in order for serialization to
work. In its place, it uses `serde`'s `#[serde(rename_all = ...)]`
macro to handle the naming convention.
In case other naming conventions are used, a workaround would be to
define a user-owned mirror struct of `leptos::LeptosOptions`, which
would contain the required fields, deserialize it as desired and'
convert it to the latter via, for example, the `From<T>` trait.
Closes: #1295
Signed-off-by: Filip Dutescu <filip@hucksy.dev>
* nix-flake: use follows for `rust-overlay` (..)
This removes the extra nixpkgs dependency by re-using the nixpkgs
already defined.
https://nixos.wiki/wiki/Flakeshttps://web.archive.org/web/20230621091703/https://nixos.wiki/wiki/Flakes
> # The `follows` keyword in inputs is used for inheritance.
> # Here, `inputs.nixpkgs` of sops-nix is kept consistent with the `inputs.nixpkgs` of
> # the current flake, to avoid problems caused by different versions of nixpkgs.
* nix-flake: use nix overlays for rustc/cargo (..)
This allows the same nightly rust version to be used across the
flake (vs. strictly in the `devShell`).
* nix-flake: add `mdbook` to the development shell (..)
bumped `nixpkgs` to the latest unstable to pull in mdbook version `0.4.30`.
* nix-flake: expose all rust tools in dev environment (..)
The `oxalica / rust-overlay` docs expose all tools in the development
environment, so we should do the same:
https://github.com/oxalica/rust-overlay#use-in-devshell-for-nix-develop
---------
Co-authored-by: Jay Querie <jay@querie.cc>
This requires state to be provided via context using a special handler, but allows for extractors that use this state, rather than only `()`, as previously.
The change in indentation makes the PR hard to review
so I will discuss the change in conversational language
Two "if"'s checks were merged into one "if"
this
- if let Some(expr) = node.value() {
- if let syn::Expr::Tuple(tuple) = expr {
becomes
+ if let Some(Tuple(tuple)) = node.value() {
This patch just clears the warnings listed below and ensures we get the benefits of a better package manger function.
```console
warning: some crates are on edition 2021 which defaults to `resolver = "2"`, but virtual workspaces default to `resolver = "1"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
```console
Rewrites the algorithm behind the `<For/>` component to create a more robust keyed list implementation, with the potential for future additional optimizations related to grouping moved ranges.
Closes#533.
* add "async routing" feature that waits for async resources to resolve before navigating
* add support for Outlet
* add `<RoutingProgress/>` component
* Feat: Upgrade to new local version of syn-rsx
* chore: Make macro more IDE friendly
1. Add quotation to RawText node.
2. Replace vec! macro with [].to_vec().
Cons:
1. Temporary remove allow(unused_braces) from expressions, to allow completion after dot in rust-analyzer.
* chore: Change dependency from syn-rsx to rstml
* chore: Fix value_to_string usage, pr comments, and fmt.
* chore: ignore playwright output
* fix: could not run playwright test
* test: should see the welcome message
* build: clean playwright output
* build: run playwright web tests
* build: setup e2e dependencies
This configures a hook to run the tailwindcss CLI when a build is triggered or retriggered via Trunk watch. It eliminates the need to run the tailwindcss manually.
* build: improve task names
* build: add clean-examples task
Make it easy to clean all the cargo and trunk files in the examples.
* build: clean after verify
Adjust the intro in the Dynamic Attributes page to include
the recent `dynamic style` feature. Also reorder a little given
the order of the page body that follows.
- [Common Bugs](https://github.com/leptos-rs/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
## `nightly` Note
Most of the examples assume you’re using `nightly` version of Rust. For this, you can either set your toolchain globally or on per-project basis.
To set `nightly` as a default toolchain for all projects (and add the ability to compile Rust to WebAssembly, if you haven’t already):
```
rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-unknown
```
If you'd like to use `nightly` only in your Leptos project however, add [`rust-toolchain.toml`](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file) file with the following content:
```toml
[toolchain]
channel="nightly"
targets=["wasm32-unknown-unknown"]
```
If you’re on `stable`, note the following:
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.2", features = ["stable"] }`
2.`nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
[`cargo-leptos`](https://github.com/leptos-rs/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our starter templates for [Actix](https://github.com/leptos-rs/start) or [Axum](https://github.com/leptos-rs/start-axum).
```bash
cargo install cargo-leptos
cargo leptos new --git https://github.com/leptos-rs/start
cargo leptos new --git https://github.com/leptos-rs/start-axum
cd[your project name]
cargo leptos watch
```
@@ -109,7 +107,7 @@ Open browser to [http://localhost:3000/](http://localhost:3000/).
### What’s up with the name?
_Leptos_ (λεπτός) is an ancient Greek word meaning “thin, light, refine, fine-grained.” To me, a classicist and not a dog owner, it evokes the lightweight reactive system that powers the framework. I've since learned the same word is at the root of the medical term “leptospirosis,” a blood infection that affects humans and animals... My bad. No dogs were harmed in the creation of this framework.
_Leptos_ (λεπτός) is an ancient Greek word meaning “thin, light, refined, fine-grained.” To me, a classicist and not a dog owner, it evokes the lightweight reactive system that powers the framework. I've since learned the same word is at the root of the medical term “leptospirosis,” a blood infection that affects humans and animals... My bad. No dogs were harmed in the creation of this framework.
### Is it production ready?
@@ -117,7 +115,7 @@ People usually mean one of three things by this question.
1.**Are the APIs stable?** i.e., will I have to rewrite my whole app from Leptos 0.1 to 0.2 to 0.3 to 0.4, or can I write it now and benefit from new features and updates as new versions come?
The APIs are basically settled. We’re adding new features, but we’re very happy with where the type system and patterns have landed. I would not expect major breaking changes to your code to adapt to future releases. The sorts of breaking changes that we discuss are things like “Oh yeah, that function should probably take `cx` as its argument...” not major changes to the way you write your application.
The APIs are basically settled. We’re adding new features, but we’re very happy with where the type system and patterns have landed. I would not expect major breaking changes to your code to adapt to future releases, in terms of architecture.
2.**Are there bugs?**
@@ -127,48 +125,38 @@ Yes, I’m sure there are. You can see from the state of our issue tracker over
This may be the big one: “production ready” implies a certain orientation to a library: that you can basically use it, without any special knowledge of its internals or ability to contribute. Everyone has this at some level in their stack: for example I (@gbj) don’t have the capacity or knowledge to contribute to something like `wasm-bindgen` at this point: I simply rely on it to work.
There are several people in the community using Leptos right now for internal apps at work, who have also become significant contributors. I think this is the right level of production use for now. There may be missing features that you need, and you may end up building them! But for internal apps, if you’re willing to build and contribute missing pieces along the way, the framework is definitely usable right now.
There are several people in the community using Leptos right now for many websites at work, who have also become significant contributors. There may be missing features that you need, and you may end up building them! But, if you're willing to contribute a few missing pieces along the way, the framework is most definitely usable for production applications, especially given the ecosystem of libraries that have sprung up around it.
### Can I use this for native GUI?
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive native any GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
Sure! Obviously the `view` macro is for generating DOM nodes but you can use the reactive system to drive any native GUI toolkit that uses the same kind of object-oriented, event-callback-based framework as the DOM pretty easily. The principles are the same:
- Use signals, derived signals, and memos to create your reactive system
- Create GUI widgets
- Use event listeners to update signals
- Create effects to update the UI
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
The 0.7 update originally set out to create a "generic rendering" approach that would allow us to reuse most of the same view logic to do all of the above. Unfortunately, this has had to be shelved for now due to difficulties encountered by the Rust compiler when building larger-scale applications with the number of generics spread throughout the codebase that this required. It's an approach I'm looking forward to exploring again in the future; feel free to reach out if you're interested in this kind of work.
### How is this different from Yew/Dioxus?
### How is this different from Yew?
On the surface level, these libraries may seem similar. Yew is, of course, the most mature Rust library for web UI development and has a huge ecosystem. Dioxus is similar in many ways, being heavily inspired by React. Here are some conceptual differences between Leptos and these frameworks:
Yew is the most-used library for Rust web UI development, but there are several differences between Yew and Leptos, in philosophy, approach, and performance.
- **VDOM vs. fine-grained:** Yew is built on the virtual DOM (VDOM) model: state changes cause components to re-render, generating a new virtual DOM tree. Yew diffs this against the previous VDOM, and applies those patches to the actual DOM. Component functions rerun whenever state changes. Leptos takes an entirely different approach. Components run once, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is. (Dioxus has made huge advances in performance with its recent 0.3 release, and is now roughly on par with Leptos.)
- **Mental model:** Adopting fine-grained reactivity also tends to simplify the mental model. There are no surprising component re-renders because there are no re-renders. You can call functions, create timeouts, etc. within the body of your component functions because they won’t be re-run. You don’t need to think about manual dependency tracking for effects; fine-grained reactivity tracks dependencies automatically.
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is.
- **Server integration:** Yew was created in an erain which browser-rendered single-page apps (SPAs) were the dominant paradigm. While Leptos supports client-side rendering, it also focuses on integrating with the server side of your application via server functions and multiple modes of serving HTML, including out-of-order streaming.
### How is this different from Dioxus?
Like Leptos, Dioxus is a framework for building UIs using web technologies. However, there are significant differences in approach and features.
- **VDOM vs. fine-grained:** While Dioxus has a performant virtual DOM (VDOM), it still uses coarse-grained/component-scoped reactivity: changing a stateful value reruns the component function and diffs the old UI against the new one. Leptos components use a different mental model, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
- **Web vs. desktop priorities:** Dioxus uses Leptos server functions in its fullstack mode, but does not have the same `<Suspense>`-based support for things like streaming HTML rendering, or share the same focus on holistic web performance. Leptos tends to prioritize holistic web performance (streaming HTML rendering, smaller WASM binary sizes, etc.), whereas Dioxus has an unparalleled experience when building desktop apps, because your application logic runs as a native Rust binary.
### How is this different from Sycamore?
Conceptually, these two frameworks are very similar: because both are built on fine-grained reactivity, most apps will end up looking very similar between the two, and Sycamore or Leptos apps will both look a lot like SolidJS apps, in the same way that Yew or Dioxus can look a lot like React.
Sycamore and Leptos are both heavily influenced by SolidJS. At this point, Leptos has a larger community and ecosystem and is more actively developed. Other differences:
There are some practical differences that make a significant difference:
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
- **Server integration:** Leptos provides primitives that encourage HTML streaming and allow for easy async integration and RPC calls, even without WASM enabled, making it easy to opt into integrations between your frontend and backend code without pushing you toward any particular metaframework patterns.
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use [`create_rw_signal`](https://docs.rs/leptos/latest/leptos/fn.create_rw_signal.html) to give a unified read/write signal.)_
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrappers for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.
- **Templating DSLs:** Sycamore uses a custom templating language for its views, while Leptos uses a JSX-like template format.
- **`'static` signals:** One of Leptos’s main innovations was the creation of `Copy + 'static` signals, which have excellent ergonomics. Sycamore is in the process of adopting the same pattern, but this is not yet released.
- **Perseus vs. server functions:** The Perseus metaframework provides an opinionated way to build Sycamore apps that include server functionality. Leptos instead provides primitives like server functions in the core of the framework.
Clicking the button twice will cause a panic, because of the nested signal *read*. Calling the `update` function on `resources` immediately takes out a mutable borrow on `resources`, then updates the `resource` signal—which re-runs the effect that reads from the signals, which tries to immutably access `resources` and panics. It's the nested update here which causes a problem, because the inner update triggers and effect that tries to read both signals while the outer is still updating.
Clicking the button twice will cause a panic, because of the nested signal _read_. Calling the `update` function on `resources` immediately takes out a mutable borrow on `resources`, then updates the `resource` signal—which re-runs the effect that reads from the signals, which tries to immutably access `resources` and panics. It's the nested update here which causes a problem, because the inner update triggers and effect that tries to read both signals while the outer is still updating.
You can fix this fairly easily by using the [`Scope::batch()`](https://docs.rs/leptos/latest/leptos/struct.Scope.html#method.batch) method:
You can fix this fairly easily by using the [`batch()`](https://docs.rs/leptos/latest/leptos/fn.batch.html) method:
```rust
letupdate=move|id: usize|{
cx.batch(move||{
batch(move||{
resources.update(|resources|{
resources
.entry(id)
.or_insert_with(||create_rw_signal(cx,0))
.or_insert_with(||create_rw_signal(0))
.update(|amount|*amount+=1)
})
});
@@ -83,11 +83,11 @@ Many DOM attributes can be updated either by setting an attribute on the DOM nod
This means that in practice, attributes like `value` or `checked` on an `<input/>` element only update the _default_ value for the `<input/>`. If you want to reactively update the value, you should use `prop:value` instead to set the `value` property.
This project contains the core of a new introductory guide to Leptos.
The Leptos book is now available at [https://book.leptos.dev](https://book.leptos.dev).
It is built using `mdbook`. You can view a local copy by installing `mdbook`
```bash
cargo install mdbook
```
and run the book with
```
mdbook serve
```
It should be available at `http://localhost:3000`.
The source code for the book has moved to [https://github.com/leptos-rs/book](https://github.com/leptos-rs/book). Please open issues or make PRs in that repository.
There are two basic paths to getting started with Leptos:
1. Client-side rendering with [Trunk](https://trunkrs.dev/)
2. Full-stack rendering with [`cargo-leptos`](https://github.com/leptos-rs/cargo-leptos)
For the early examples, it will be easiest to begin with Trunk. We’ll introduce
`cargo-leptos` a little later in this series.
If you don’t already have it installed, you can install Trunk by running
```bash
cargo install trunk
```
Create a basic Rust binary project
```bash
cargo init leptos-tutorial
```
> We recommend using `nightly` Rust, as it enables [a few nice features](https://github.com/leptos-rs/leptos#nightly-note). To use `nightly` Rust with WebAssembly, you can run
>
> ```bash
> rustup toolchain install nightly
> rustup default nightly
> rustup target add wasm32-unknown-unknown
> ```
`cd` into your new `leptos-tutorial` project and add `leptos` as a dependency
```bash
cargo add leptos
```
Create a simple `index.html` in the root of the `leptos-tutorial` directory
```html
<!DOCTYPE html>
<html>
<head></head>
<body></body>
</html>
```
And add a simple “Hello, world!” to your `main.rs`
Believe it or not, we’ve made it this far without having mentioned half of the reactive system: effects.
Leptos is built on a fine-grained reactive system, which means that individual reactive values (“signals,” sometimes known as observables) trigger rerunning the code that reacts to them (“effects,” sometimes known as observers). These two halves of the reactive system are inter-dependent. Without effects, signals can change within the reactive system but never be observed in a way that interacts with the outside world. Without signals, effects run once but never again, as there’s no observable value to subscribe to.
[`create_effect`](https://docs.rs/leptos_reactive/latest/leptos_reactive/fn.create_effect.html) takes a function as its argument. It immediately runs the function. If you access any reactive signal inside that function, it registers the fact that the effect depends on that signal with the reactive runtime. Whenever one of the signals that the effect depends on changes, the effect runs again.
```rust
let(a,set_a)=create_signal(cx,0);
let(b,set_b)=create_signal(cx,0);
create_effect(cx,move|_|{
// immediately prints "Value: 0" and subscribes to `a`
log::debug!("Value: {}",a());
});
```
The effect function is called with an argument containing whatever value it returned the last time it ran. On the initial run, this is `None`.
By default, effects **do not run on the server**. This means you can call browser-specific APIs within the effect function without causing issues. If you need an effect to run on the server, use [`create_isomorphic_effect`](https://docs.rs/leptos_reactive/latest/leptos_reactive/fn.create_isomorphic_effect.html).
## Autotracking and Dynamic Dependencies
If you’re familiar with a framework like React, you might notice one key difference. React and similar frameworks typically require you to pass a “dependency array,” an explicit set of variables that determine when the effect should rerun.
Because Leptos comes from the tradition of synchronous reactive programming, we don’t need this explicit dependency list. Instead, we automatically track dependencies depending on which signals are accessed within the effect.
This has two effects (no pun intended). Dependencies are
1.**Automatic**: You don’t need to maintain a dependency list, or worry about what should or shouldn’t be included. The framework simply tracks which signals might cause the effect to rerun, and handles it for you.
2.**Dynamic**: The dependency list is cleared and updated every time the effect runs. If your effect contains a conditional (for example), only signals that are used in the current branch are tracked. This means that effects rerun the absolute minimum number of times.
> If this sounds like magic, and if you want a deep dive into how automatic dependency tracking works, [check out this video](https://www.youtube.com/watch?v=GWB3vTWeLd4). (Apologies for the low volume!)
## Effects as Zero-Cost-ish Abstraction
While they’re not a “zero-cost abstraction” in the most technical sense—they require some additional memory use, exist at runtime, etc.—at a higher level, from the perspective of whatever expensive API calls or other work you’re doing within them, effects are a zero-cost abstraction. They rerun the absolute minimum number of times necessary, given how you’ve described them.
Imagine that I’m creating some kind of chat software, and I want people to be able to display their full name, or just their first name, and to notify the server whenever their name changes:
If `use_last` is `true`, effect should rerun whenever `first`, `last`, or `use_last` changes. But if I toggle `use_last` to `false`, a change in `last` will never cause the full name to change. In fact, `last` will be removed from the dependency list until `use_last` toggles again. This saves us from sending multiple unnecessary requests to the API if I change `last` multiple times while `use_last` is still `false`.
## To `create_effect`, or not to `create_effect`?
Effects are intended to run _side-effects_ of the system, not to synchronize state _within_ the system. In other words: don’t write to signals within effects.
If you need to define a signal that depends on the value of other signals, use a derived signal or [`create_memo`](https://docs.rs/leptos_reactive/latest/leptos_reactive/fn.create_memo.html).
If you need to synchronize some reactive value with the non-reactive world outside—like a web API, the console, the filesystem, or the DOM—create an effect.
> If you’re curious for more information about when you should and shouldn’t use `create_effect`, [check out this video](https://www.youtube.com/watch?v=aQOFJQ2JkvQ) for a more in-depth consideration!
## Effects and Rendering
We’ve managed to get this far without mentioning effects because they’re built into the Leptos DOM renderer. We’ve seen that you can create a signal and pass it into the `view` macro, and it will update the relevant DOM node whenever the signal changes:
```rust
let(count,set_count)=create_signal(cx,0);
view!{cx,
<p>{count}</p>
}
```
This works because the framework essentially creates an effect wrapping this update. You can imagine Leptos translating this view into something like this:
```rust
let(count,set_count)=create_signal(cx,0);
// create a DOM element
letp=create_element("p");
// create an effect to reactively update the text
create_effect(cx,move|prev_value|{
// first, access the signal’s value and convert it to a string
lettext=count().to_string();
// if this is different from the previous value, update the node
ifprev_value!=Some(text){
p.set_text_content(&text);
}
// return this value so we can memoize the next update
text
});
```
Every time `count` is updated, this effect wil rerun. This is what allows reactive, fine-grained updates to the DOM.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/serene-thompson-40974n?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A2%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A2%7D%5D)
Then child components can access “slices” of that state with fine-grained
updates via `create_slice`. Each slice signal only updates when the particular
piece of the larger struct it accesses updates. This means you can create a single
root signal, and then take independent, fine-grained slices of it in different
components, each of which can update without notifying the others of changes.
```rust
/// A component that updates the count in the global state.
#[component]
fnGlobalStateCounter(cx: Scope)-> implIntoView{
letstate=use_context::<RwSignal<GlobalState>>(cx).expect("state to have been provided");
// `create_slice` lets us create a "lens" into the data
let(count,set_count)=create_slice(
cx,
// we take a slice *from* `state`
state,
// our getter returns a "slice" of the data
|state|state.count,
// our setter describes how to mutate that slice, given a new value
|state,n|state.count=n,
);
view!{cx,
<divclass="consumer blue">
<button
on:click=move|_|{
set_count(count()+1);
}
>
"Increment Global Count"
</button>
<br/>
<span>"Count is: "{count}</span>
</div>
}
}
```
Clicking this button only updates `state.count`, so if we create another slice
somewhere else that only takes `state.name`, clicking the button won’t cause
that other slice to update. This allows you to combine the benefits of a top-down
data flow and of fine-grained reactive updates.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/1-basic-component-forked-8bte19?selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A2%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A2%7D%5D&file=%2Fsrc%2Fmain.rs)
One of the primary downsides of deploying a Rust/WebAssembly frontend app is that splitting a WASM file into smaller chunks to be dynamically loaded is significantly more difficult than splitting a JavaScript bundle. There have been experiments like [`wasm-split`](https://emscripten.org/docs/optimizing/Module-Splitting.html) in the Emscripten ecosystem but at present there’s no way to split and dynamically load a Rust/`wasm-bindgen` binary. This means that the whole WASM binary needs to be loaded before your app becomes interactive. Because the WASM format is designed for streaming compilation, WASM files are much faster to compile per kilobyte than JavaScript files. (For a deeper look, you can [read this great article from the Mozilla team](https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/) on streaming WASM compilation.)
Still, it’s important to ship the smallest WASM binary to users that you can, as it will reduce their network usage and make your app interactive as quickly as possible.
So what are some practical steps?
## Things to Do
1. Make sure you’re looking at a release build. (Debug builds are much, much larger.)
2. Add a release profile for WASM that optimizes for size, not speed.
For a `cargo-leptos` project, for example, you can add this to your `Cargo.toml`:
```toml
[profile.wasm-release]
inherits="release"
opt-level='z'
lto=true
codegen-units=1
# ....
[package.metadata.leptos]
# ....
lib-profile-release="wasm-release"
```
This will hyper-optimize the WASM for your release build for size, while keeping your server build optimized for speed. (For a pure client-rendered app without server considerations, just use the `[profile.wasm-release]` block as your `[profile.release]`.)
3. Always serve compressed WASM in production. WASM tends to compress very well, typically shrinking to less than 50% its uncompressed size, and it’s trivial to enable compression for static files being served from Actix or Axum.
4. If you’re using nightly Rust, you can rebuild the standard library with this same profile rather than the prebuilt standard library that’s distributed with the `wasm32-unknown-unknown` target.
To do this, create a file in your project at `.cargo/config.toml`
```toml
[unstable]
build-std=["std","panic_abort","core","alloc"]
build-std-features=["panic_immediate_abort"]
```
5. One of the sources of binary size in WASM binaries can be `serde` serialization/deserialization code. Leptos uses `serde` by default to serialize and deserialize resources created with `create_resource`. You might try experimenting with the `miniserde` and `serde-lite` features, which allow you to use those crates for serialization and deserialization instead; each only implements a subset of `serde`’s functionality, but typically optimizes for size over speed.
## Things to Avoid
There are certain crates that tend to inflate binary sizes. For example, the `regex` crate with its default features adds about 500kb to a WASM binary (largely because it has to pull in Unicode table data!) In a size-conscious setting, you might consider avoiding regexes in general, or even dropping down and calling browser APIs to use the built-in regex engine instead. (This is what `leptos_router` does on the few occasions it needs a regular expression.)
In general, Rust’s commitment to runtime performance is sometimes at odds with a commitment to a small binary. For example, Rust monomorphizes generic functions, meaning it creates a distinct copy of the function for each generic type it’s called with. This is significantly faster than dynamic dispatch, but increases binary size. Leptos tries to balance runtime performance with binary size considerations pretty carefully; but you might find that writing code that uses many generics tends to increase binary size. For example, if you have a generic component with a lot of code in its body and call it with four different types, remember that the compiler could include four copies of that same code. Refactoring to use a concrete inner function or helper can often maintain performance and ergonomics while reducing binary size.
## A Final Thought
Remember that in a server-rendered app, JS bundle size/WASM binary size affects only _one_ thing: time to interactivity on the first load. This is very important to a good user experience—nobody wants to click a button three times and have it do nothing because the interactive code is still loading—but it is not the only important measure.
It’s especially worth remembering that streaming in a single WASM binary means all subsequent navigations are nearly instantaneous, depending only on any additional data loading. Precisely because your WASM binary is _not_ bundle split, navigating to a new route does not require loading additional JS/WASM, as it does in nearly every JavaScript framework. Is this copium? Maybe. Or maybe it’s just an honest trade-off between the two approaches!
Always take the opportunity to optimize the low-hanging fruit in your application. And always test your app under real circumstances with real user network speeds and devices before making any heroic efforts.
A [Resource](https://docs.rs/leptos/latest/leptos/struct.Resource.html) is a reactive data structure that reflects the current state of an asynchronous task, allowing you to integrate asynchronous `Future`s into the synchronous reactive system. Rather than waiting for its data to load with `.await`, you transform the `Future` into a signal that returns `Some(T)` if it has resolved, and `None` if it’s still pending.
You do this by using the [`create_resource`](https://docs.rs/leptos/latest/leptos/fn.create_resource.html) function. This takes two arguments (other than the ubiquitous `cx`):
1. a source signal, which will generate a new `Future` whenever it changes
2. a fetcher function, which takes the data from that signal and returns a `Future`
Here’s an example
```rust
// our source signal: some synchronous, local state
let(count,set_count)=create_signal(cx,0);
// our resource
letasync_data=create_resource(cx,
count,
// every time `count` changes, this will run
|value|asyncmove{
log!("loading data from API");
load_data(value).await
},
);
```
To create a resource that simply runs once, you can pass a non-reactive, empty source signal:
To access the value you can use `.read(cx)` or `.with(cx, |data| /* */)`. These work just like `.get()` and `.with()` on a signal—`read` clones the value and returns it, `with` applies a closure to it—but with two differences
1. For any `Resource<_, T>`, they always return `Option<T>`, not `T`: because it’s always possible that your resource is still loading.
2. They take a `Scope` argument. You’ll see why in the next chapter, on `<Suspense/>`.
So, you can show the current state of a resource in your view:
Resources also provide a `refetch()` method that allows you to manually reload the data (for example, in response to a button click) and a `loading()` method that returns a `ReadSignal<bool>` indicating whether the resource is currently loading or not.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/10-async-resources-4z0qt3?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A3%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A3%7D%5D)
That’s not _so_ bad, but it’s kind of annoying. What if we could invert the flow of control?
The [`<Suspense/>`](https://docs.rs/leptos/latest/leptos/fn.Suspense.html) component lets us do exactly that. You give it a `fallback` prop and children, one or more of which usually involves reading from a resource. Reading from a resource “under” a `<Suspense/>` (i.e., in one of its children) registers that resource with the `<Suspense/>`. If it’s still waiting for resources to load, it shows the `fallback`. When they’ve all loaded, it shows the children.
Every time one of the resources is reloading, the `"Loading..."` fallback will show again.
This inversion of the flow of control makes it easier to add or remove individual resources, as you don’t need to handle the matching yourself. It also unlocks some massive performance improvements during server-side rendering, which we’ll talk about during a later chapter.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/11-suspense-907niv?file=%2Fsrc%2Fmain.rs)
You’ll notice in the `<Suspense/>` example that if you keep reloading the data, it keeps flickering back to `"Loading..."`. Sometimes this is fine. For other times, there’s [`<Transition/>`](https://docs.rs/leptos/latest/leptos/fn.Suspense.html).
`<Transition/>` behaves exactly the same as `<Suspense/>`, but instead of falling back every time, it only shows the fallback the first time. On all subsequent loads, it continues showing the old data until the new data are ready. This can be really handy to prevent the flickering effect, and to allow users to continue interacting with your application.
This example shows how you can create a simple tabbed contact list with `<Transition/>`. When you select a new tab, it continues showing the current contact until the new data loads. This can be a much better user experience than constantly falling back to a loading message.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/12-transition-sn38sd?selection=%5B%7B%22endColumn%22%3A15%2C%22endLineNumber%22%3A2%2C%22startColumn%22%3A15%2C%22startLineNumber%22%3A2%7D%5D&file=%2Fsrc%2Fmain.rs)
We’ve talked about how to load `async` data with resources. Resources immediately load data and work closely with `<Suspense/>` and `<Transition/>` components to show whether data is loading in your app. But what if you just want to call some arbitrary `async` function and keep track of what it’s doing?
Well, you could always use [`spawn_local`](https://docs.rs/leptos/latest/leptos/fn.spawn_local.html). This allows you to just spawn an `async` task in a synchronous environment by handing the `Future` off to the browser (or, on the server, Tokio or whatever other runtime you’re using). But how do you know if it’s still pending? Well, you could just set a signal to show whether it’s loading, and another one to show the result...
All of this is true. Or you could use the final `async` primitive: [`create_action`](https://docs.rs/leptos/latest/leptos/fn.create_action.html).
Actions and resources seem similar, but they represent fundamentally different things. If you’re trying to load data by running an `async` function, either once or when some other value changes, you probably want to use `create_resource`. If you’re trying to occasionally run an `async` function in response to something like a user clicking a button, you probably want to use `create_action`.
Say we have some `async` function we want to run.
```rust
asyncfnadd_todo(new_title: &str)-> Uuid{
/* do some stuff on the server to add a new todo */
}
```
`create_action` takes a reactive `Scope` and an `async` function that takes a reference to a single argument, which you could think of as its “input type.”
> The input is always a single type. If you want to pass in multiple arguments, you can do it with a struct or tuple.
>
> ```rust
> // if there's a single argument, just use that
> let action1 = create_action(cx, |input: &String| {
> let input = input.clone();
> async move { todo!() }
> });
>
> // if there are no arguments, use the unit type `()`
> Because the action function takes a reference but the `Future` needs to have a `'static` lifetime, you’ll usually need to clone the value to pass it into the `Future`. This is admittedly awkward but it unlocks some powerful features like optimistic UI. We’ll see a little more about that in future chapters.
So in this case, all we need to do to create an action is
```rust
letadd_todo=create_action(cx,|input: &String|{
letinput=input.to_owned();
asyncmove{add_todo(&input).await}
});
```
Rather than calling `add_todo` directly, we’ll call it with `.dispatch()`, as in
```rust
add_todo.dispatch("Some value".to_string());
```
You can do this from an event listener, a timeout, or anywhere; because `.dispatch()` isn’t an `async` function, it can be called from a synchronous context.
Actions provide access to a few signals that synchronize between the asynchronous action you’re calling and the synchronous reactive system:
This makes it easy to track the current state of your request, show a loading indicator, or do “optimistic UI” based on the assumption that the submission will succeed.
```rust
letinput_ref=create_node_ref::<Input>(cx);
view!{cx,
<form
on:submit=move|ev|{
ev.prevent_default();// don't reload the page...
letinput=input_ref.get().expect("input to exist");
add_todo.dispatch(input.value());
}
>
<label>
"What do you need to do?"
<inputtype="text"
node_ref=input_ref
/>
</label>
<buttontype="submit">"Add Todo"</button>
</form>
// use our loading state
<p>{move||pending().then("Loading...")}</p>
}
```
Now, there’s a chance this all seems a little over-complicated, or maybe too restricted. I wanted to include actions here, alongside resources, as the missing piece of the puzzle. In a real Leptos app, you’ll actually most often use actions alongside server functions, [`create_server_action`](https://docs.rs/leptos/latest/leptos/fn.create_server_action.html), and the [`<ActionForm/>`](https://docs.rs/leptos_router/latest/leptos_router/fn.ActionForm.html) component to create really powerful progressively-enhanced forms. So if this primitive seems useless to you... Don’t worry! Maybe it will make sense later. (Or check out our [`todo_app_sqlite`](https://github.com/leptos-rs/leptos/blob/main/examples/todo_app_sqlite/src/todo.rs) example now.)
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/10-async-resources-forked-hgpfp0?selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A4%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A4%7D%5D&file=%2Fsrc%2Fmain.rs)
One of our core contributors said to me recently: “I never used closures this often
until I started using Leptos.” And it’s true. Closures are at the heart of any Leptos
application. It sometimes looks a little silly:
```rust
// a signal holds a value, and can be updated
let(count,set_count)=create_signal(cx,0);
// a derived signal is a function that accesses other signals
letdouble_count=move||count()*2;
letcount_is_odd=move||count()&1==1;
lettext=move||ifcount_is_odd(){
"odd"
}else{
"even"
};
// an effect automatically tracks the signals it depends on
// and reruns when they change
create_effect(cx,move|_|{
log!("text = {}",text());
});
view!{cx,
<p>{move||text().to_uppercase()}</p>
}
```
Closures, closures everywhere!
But why?
## Functions and UI Frameworks
Functions are at the heart of every UI framework. And this makes perfect sense. Creating a user interface is basically divided into two phases:
1. initial rendering
2. updates
In a web framework, the framework does some kind of initial rendering. Then it hands control back over to the browser. When certain events fire (like a mouse click) or asynchronous tasks finish (like an HTTP request finishing), the browser wakes the framework back up to update something. The framework runs some kind of code to update your user interface, and goes back asleep until the browser wakes it up again.
The key phrase here is “runs some kind of code.” The natural way to “run some kind of code” at an arbitrary point in time—in Rust or in any other programming language—is to call a function. And in fact every UI framework is based on rerunning some kind of function over and over:
1. virtual DOM (VDOM) frameworks like React, Yew, or Dioxus rerun a component or render function over and over, to generate a virtual DOM tree that can be reconciled with the previous result to patch the DOM
2. compiled frameworks like Angular and Svelte divide your component templates into “create” and “update” functions, rerunning the update function when they detect a change to the component’s state
3. in fine-grained reactive frameworks like SolidJS, Sycamore, or Leptos, _you_ define the functions that rerun
That’s what all our components are doing.
Take our typical `<SimpleCounter/>` example in its simplest form:
The `SimpleCounter` function itself runs once. The `value` signal is created once. The framework hands off the `increment` function to the browser as an event listener. When you click the button, the browser calls `increment`, which updates `value` via `set_value`. And that updates the single text node represented in our view by `{value}`.
Closures are key to reactivity. They provide the framework with the ability to rerun the smallest possible unit of your application in responsive to a change.
So remember two things:
1. Your component function is a setup function, not a render function: it only runs once.
2. For values in your view template to be reactive, they must be functions: either signals (which implement the `Fn` traits) or closures.
This is pretty straightforward: when the user is logged in, we want to show `children`. Until if the user is not logged in, we want to show `fallback`. And while we’re waiting to find out, we just render `()`, i.e., nothing.
In other words, we want to pass the children of `<WhenLoaded/>`_through_ the `<Suspense/>` component to become the children of the `<Show/>`. This is what I mean by “projection.”
This won’t compile.
```
error[E0507]: cannot move out of `fallback`, a captured variable in an `Fn` closure
error[E0507]: cannot move out of `children`, a captured variable in an `Fn` closure
```
The problem here is that both `<Suspense/>` and `<Show/>` need to be able to construct their `children` multiple times. The first time you construct `<Suspense/>`’s children, it would take ownership of `fallback` and `children` to move them into the invocation of `<Show/>`, but then they're not available for future `<Suspense/>` children construction.
## The Details
> Feel free to skip ahead to the solution.
If you want to really understand the issue here, it may help to look at the expanded `view` macro. Here’s a cleaned-up version:
```rust
Suspense(
cx,
::leptos::component_props_builder(&Suspense)
.fallback(||())
.children({
// fallback and children are moved into this closure
Box::new(move|cx|{
{
// fallback and children captured here
leptos::Fragment::lazy(||{
vec![
(Show(
cx,
::leptos::component_props_builder(&Show)
.when(||true)
// but fallback is moved into Show here
.fallback(fallback)
// and children is moved into Show here
.children(children)
.build(),
)
.into_view(cx)),
]
})
}
})
})
.build(),
)
```
All components own their props; so the `<Show/>` in this case can’t be called because it only has captured references to `fallback` and `children`.
## Solution
However, both `<Suspense/>` and `<Show/>` take `ChildrenFn`, i.e., their `children` should implement the `Fn` type so they can be called multiple times with only an immutable reference. This means we don’t need to own `children` or `fallback`; we just need to be able to pass `'static` references to them.
We can solve this problem by using the [`store_value`](https://docs.rs/leptos/latest/leptos/fn.store_value.html) primitive. This essentially stores a value in the reactive system, handing ownership off to the framework in exchange for a reference that is, like signals, `Copy` and `'static`, which we can access or modify through certain methods.
At the top level, we store both `fallback` and `children` in the reactive scope owned by `LoggedIn`. Now we can simply move those references down through the other layers into the `<Show/>` component and call them there.
## A Final Note
Note that this works because `<Show/>` and `<Suspense/>` only need an immutable reference to their children (which `.with_value` can give it), not ownership.
In other cases, you may need to project owned props through a function that takes `ChildrenFn` and therefore needs to be called more than once. In this case, you may find the `clone:` helper in the`view` macro helpful.
Even with `name=name.clone()`, this gives the error
```
cannot move out of `name`, a captured variable in an `Fn` closure
```
It’s captured through multiple levels of children that need to run more than once, and there’s no obvious way to clone it _into_ the children.
In this case, the `clone:` syntax comes in handy. Calling `clone:name` will clone `name`_before_ moving it into `<Inner/>`’s children, which solves our ownership issue.
```rust
view!{cx,
<Outer>
<Innerclone:name>
<Inmostname=name.clone()/>
</Inner>
</Outer>
}
```
These issues can be a little tricky to understand or debug, because of the opacity of the `view` macro. But in general, they can always be solved.
Anyone creating a website or application soon runs into the question of styling. For a small app, a single CSS file is probably plenty to style your user interface. But as an application grows, many developers find that plain CSS becomes increasingly hard to manage.
Some frontend frameworks (like Angular, Vue, and Svelte) provide built-in ways to scope your CSS to particular components, making it easier to manage styles across a whole application without styles meant to modify one small component having a global effect. Other frameworks (like React or Solid) don’t provide built-in CSS scoping, but rely on libraries in the ecosystem to do it for them. Leptos is in this latter camp: the framework itself has no opinions about CSS at all, but provides a few tools and primitives that allow others to build styling libraries.
Here are a few different approaches to styling your Leptos app, other than plain CSS.
## TailwindCSS: Utility-first CSS
[TailwindCSS](https://tailwindcss.com/) is a popular utility-first CSS library. It allows you to style your application by using inline utility classes, with a custom CLI tool that scans your files for Tailwind class names and bundles the necessary CSS.
This allows you to write components like this:
```rust
#[component]
fnHome(cx: Scope)-> implIntoView{
let(count,set_count)=create_signal(cx,0);
view!{cx,
<mainclass="my-0 mx-auto max-w-3xl text-center">
<h2class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
<pclass="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
It can be a little complicated to set up the Tailwind integration at first, but you can check out our two examples of how to use Tailwind with a [client-side-rendered `trunk` application](https://github.com/leptos-rs/leptos/tree/main/examples/tailwind_csr_trunk) or with a [server-rendered `cargo-leptos` application](https://github.com/leptos-rs/leptos/tree/main/examples/tailwind). `cargo-leptos` also has some [built-in Tailwind support](https://github.com/leptos-rs/cargo-leptos#site-parameters) that you can use as an alternative to Tailwind’s CLI.
## Stylers: Compile-time CSS Extraction
[Stylers](https://github.com/abishekatp/stylers) is a compile-time scoped CSS library that lets you declare scoped CSS in the body of your component. Stylers will extract this CSS at compile time into CSS files that you can then import into your app, which means that it doesn’t add anything to the WASM binary size of your application.
This allows you to write components like this:
```rust
usestylers::style;
#[component]
pubfnApp(cx: Scope)-> implIntoView{
letstyler_class=style!{"App",
#two{
color: blue;
}
div.one{
color: red;
content: raw_str(r#"\hello"#);
font: "1.3em/1.2"Arial,Helvetica,sans-serif;
}
div{
border: 1pxsolidblack;
margin: 25px50px75px100px;
background-color: lightblue;
}
h2{
color: purple;
}
@mediaonlyscreenand(max-width: 1000px){
h3{
background-color: lightblue;
color: blue
}
}
};
view!{cx,class=styler_class,
<divclass="one">
<h1id="two">"Hello"</h1>
<h2>"World"</h2>
<h2>"and"</h2>
<h3>"friends!"</h3>
</div>
}
}
```
## Styled: Runtime CSS Scoping
[Styled](https://github.com/eboody/styled) is a runtime scoped CSS library that integrates well with Leptos. It lets you declare scoped CSS in the body of your component function, and then applies those styles at runtime.
```rust
usestyled::style;
#[component]
pubfnMyComponent(cx: Scope)-> implIntoView{
letstyles=style!(
div{
background-color: red;
color: white;
}
);
styled::view!{cx,styles,
<div>"This text should be red with white text."</div>
}
}
```
## Contributions Welcome
Leptos has no opinions on how you style your website or app, but we’re very happy to provide support to any tools you’re trying to create to make it easier. If you’re working on a CSS or styling approach that you’d like to add to this list, please let us know!
First things first, make sure you’ve added the `leptos_router` package to your dependencies.
> It’s important that the router is a separate package from `leptos` itself. This means that everything in the router can be defined in user-land code. If you want to create your own router, or use no router, you’re completely free to do that!
And import the relevant types from the router, either with something like
Routing behavior is provided by the [`<Router/>`](https://docs.rs/leptos_router/latest/leptos_router/fn.Router.html) component. This should usually be somewhere near the root of your application, the rest of the app.
> You shouldn’t try to use multiple `<Router/>`s in your app. Remember that the router drives global state: if you have multiple routers, which ones decides what to do when the URL changes?
Let’s start with a simple `<App/>` component using the router:
```rust
useleptos::*;
useleptos_router::*;
#[component]
pubfnApp(cx: Scope)-> implIntoView{
view!{
<Router>
<nav>
/* ... */
</nav>
<main>
/* ... */
</main>
</Router>
}
}
```
## Defining `<Routes/>`
The [`<Routes/>`](https://docs.rs/leptos_router/latest/leptos_router/fn.Routes.html) component is where you define all the routes to which a user can navigate in your application. Each possible route is defined by a [`<Route/>`](https://docs.rs/leptos_router/latest/leptos_router/fn.Route.html) component.
You should place the `<Routes/>` component at the location within your app where you want routes to be rendered. Everything outside `<Routes/>` will be present on every page, so you can leave things like a navigation bar or menu outside the `<Routes/>`.
```rust
useleptos::*;
useleptos_router::*;
#[component]
pubfnApp(cx: Scope)-> implIntoView{
view!{
<Router>
<nav>
/* ... */
</nav>
<main>
// all our routes will appear inside <main>
<Routes>
/* ... */
</Routes>
</main>
</Router>
}
}
```
Individual routes are defined by providing children to `<Routes/>` with the `<Route/>` component. `<Route/>` takes a `path` and a `view`. When the current location matches `path`, the `view` will be created and displayed.
The `path` can include
- a static path (`/users`),
- dynamic, named parameters beginning with a colon (`/:id`),
- and/or a wildcard beginning with an asterisk (`/user/*any`)
The `view` is a function that takes a `Scope` and returns a view.
> The router scores each route to see how good a match it is, so you can define your routes in any order.
Now if you navigate to `/` or to `/users` you’ll get the home page or the `<Users/>`. If you go to `/users/3` or `/blahblah` you’ll get a user profile or your 404 page (`<NotFound/>`). On every navigation, the router determines which `<Route/>` should be matched, and therefore what content should be displayed where the `<Routes/>` component is defined.
There’s a certain amount of duplication here: `/users` and `/users/:id`. This is fine for a small app, but you can probably already tell it won’t scale well. Wouldn’t it be nice if we could nest these routes?
But wait. We’ve just subtly changed what our application does.
The next section is one of the most important in this entire routing section of the guide. Read it carefully, and feel free to ask questions if there’s anything you don’t understand.
# Nested Routes as Layout
Nested routes are a form of layout, not a method of route definition.
Let me put that another way: The goal of defining nested routes is not primarily to avoid repeating yourself when typing out the paths in your route definitions. It is actually to tell the router to display multiple `<Route/>`s on the page at the same time, side by side.
- If I go to `/users/3`, the path matches `<Users/>` and `<UserProfile/>`.
- If I go to `/users`, the path matches `<Users/>` and `<NoUser/>`.
When I use nested routes, in other words, each **path** can match multiple **routes**: each URL can render the views provided by multiple `<Route/>` components, at the same time, on the same page.
This may be counter-intuitive, but it’s very powerful, for reasons you’ll hopefully see in a few minutes.
## Why Nested Routing?
Why bother with this?
Most web applications contain levels of navigation that correspond to different parts of the layout. For example, in an email app you might have a URL like `/contacts/greg`, which shows a list of contacts on the left of the screen, and contact details for Greg on the right of the screen. The contact list and the contact details should always appear on the screen at the same time. If there’s no contact selected, maybe you want to show a little instructional text.
You can go even deeper. Say you want to have tabs for each contact’s address, email/phone, and your conversations with them. You can add _another_ set of nested routes inside `:id`:
> The main page of the [Remix website](https://remix.run/), a React framework from the creators of React Router, has a great visual example if you scroll down, with three levels of nested routing: Sales > Invoices > an invoice.
## `<Outlet/>`
Parent routes do not automatically render their nested routes. After all, they are just components; they don’t know exactly where they should render their children, and “just stick at at the end of the parent component” is not a great answer.
Instead, you tell a parent component where to render any nested components with an `<Outlet/>` component. The `<Outlet/>` simply renders one of two things:
- if there is no nested route that has been matched, it shows nothing
- if there is a nested route that has been matched, it shows its `view`
That’s all! But it’s important to know and to remember, because it’s a common source of “Why isn’t this working?” frustration. If you don’t provide an `<Outlet/>`, the nested route won’t be displayed.
```rust
#[component]
pubfnContactList(cx: Scope)-> implIntoView{
letcontacts=todo!();
view!{cx,
<divstyle="display: flex">
// the contact list
<Foreach=contacts
key=|contact|contact.id
view=|cx,contact|todo!()
>
// the nested child, if any
// don’t forget this!
<Outlet/>
</div>
}
}
```
## Nested Routing and Performance
All of this is nice, conceptually, but again—what’s the big deal?
Performance.
In a fine-grained reactive library like Leptos, it’s always important to do the least amount of rendering work you can. Because we’re working with real DOM nodes and not diffing a virtual DOM, we want to “rerender” components as infrequently as possible. Nested routing makes this extremely easy.
Imagine my contact list example. If I navigate from Greg to Alice to Bob and back to Greg, the contact information needs to change on each navigation. But the `<ContactList/>` should never be rerendered. Not only does this save on rendering performance, it also maintains state in the UI. For example, if I have a search bar at the top of `<ContactList/>`, navigating from Greg to Alice to Bob won’t clear the search.
In fact, in this case, we don’t even need to rerender the `<Contact/>` component when moving between contacts. The router will just reactively update the `:id` parameter as we navigate, allowing us to make fine-grained updates. As we navigate between contacts, we’ll update single text nodes to change the contact’s name, address, and so on, without doing _any_ additional rerendering.
> This sandbox includes a couple features (like nested routing) discussed in this section and the previous one, and a couple we’ll cover in the rest of this chapter. The router is such an integrated system that it makes sense to provide a single example, so don’t be surprised if there’s anything you don’t understand.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/16-router-fy4tjv?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A3%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A3%7D%5D)
Static paths are useful for distinguishing between different pages, but almost every application wants to pass data through the URL at some point.
There are two ways you can do this:
1. named route **params** like `id` in `/users/:id`
2. named route **queries** like `q` in `/search?q=Foo`
Because of the way URLs are built, you can access the query from _any_`<Route/>` view. You can access route params from the `<Route/>` that defines them or any of its nested children.
Accessing params and queries is pretty simple with a couple of hooks:
- [`use_query`](https://docs.rs/leptos_router/latest/leptos_router/fn.use_query.html) or [`use_query_map`](https://docs.rs/leptos_router/latest/leptos_router/fn.use_query_map.html)
- [`use_params`](https://docs.rs/leptos_router/latest/leptos_router/fn.use_params.html) or [`use_params_map`](https://docs.rs/leptos_router/latest/leptos_router/fn.use_query_map.html)
Each of these comes with a typed option (`use_query` and `use_params`) and an untyped option (`use_query_map` and `use_params_map`).
The untyped versions hold a simple key-value map. To use the typed versions, derive the [`Params`](https://docs.rs/leptos_router/0.2.3/leptos_router/trait.Params.html) trait on a struct.
> `Params` is a very lightweight trait to convert a flat key-value map of strings into a struct by applying `FromStr` to each field. Because of the flat structure of route params and URL queries, it’s significantly less flexible than something like `serde`; it also adds much less weight to your binary.
```rust
useleptos::*;
useleptos_router::*;
#[derive(Params)]
structContactParams{
id: usize
}
#[derive(Params)]
structContactSearch{
q: String
}
```
> Note: The `Params` derive macro is located at `leptos::Params`, and the `Params` trait is at `leptos_router::Params`. If you avoid using glob imports like `use leptos::*;`, make sure you’re importing the right one for the derive macro.
Now we can use them in a component. Imagine a URL that has both params and a query, like `/contacts/:id?q=Search`.
The typed versions return `Memo<Result<T>, _>`. It’s a Memo so it reacts to changes in the URL. It’s a `Result` because the params or query need to be parsed from the URL, and may or may not be valid.
```rust
letparams=use_params::<ContactParams>(cx);
letquery=use_query::<ContactSearch>(cx);
// id: || -> usize
letid=move||{
params.with(|params|{
params
.map(|params|params.id)
.unwrap_or_default()
})
};
```
The untyped versions return `Memo<ParamsMap>`. Again, it’s memo to react to changes in the URL. [`ParamsMap`](https://docs.rs/leptos_router/0.2.3/leptos_router/struct.ParamsMap.html) behaves a lot like any other map type, with a `.get()` method that returns `Option<&String>`.
```rust
letparams=use_params_map(cx);
letquery=use_query_map(cx);
// id: || -> Option<String>
letid=move||{
params.with(|params|params.get("id").cloned())
};
```
This can get a little messy: deriving a signal that wraps an `Option<_>` or `Result<_>` can involve a couple steps. But it’s worth doing this for two reasons:
1. It’s correct, i.e., it forces you to consider the cases, “What if the user doesn’t pass a value for this query field? What if they pass an invalid value?”
2. It’s performant. Specifically, when you navigate between different paths that match the same `<Route/>` with only params or the query changing, you can get fine-grained updates to different parts of your app without rerendering. For example, navigating between different contacts in our contact-list example does a targeted update to the name field (and eventually contact info) without needing to replacing or rerender the wrapping `<Contact/>`. This is what fine-grained reactivity is for.
> This is the same example from the previous section. The router is such an integrated system that it makes sense to provide a single example highlighting multiple features, even if we haven’t explain them all yet.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/16-router-fy4tjv?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A3%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A3%7D%5D)
Client-side navigation works perfectly fine with ordinary HTML `<a>` elements. The router adds a listener that handles every click on a `<a>` element and tries to handle it on the client side, i.e., without doing another round trip to the server to request HTML. This is what enables the snappy “single-page app” navigations you’re probably familiar with from most modern web apps.
The router will bail out of handling an `<a>` click under a number of situations
- the click event has had `prevent_default()` called on it
- the <kbd>Meta</kbd>, <kbd>Alt</kbd>, <kbd>Ctrl</kbd>, or <kbd>Shift</kbd> keys were held during click
- the `<a>` has a `target` or `download` attribute, or `rel="external"`
- the link has a different origin from the current location
In other words, the router will only try to do a client-side navigation when it’s pretty sure it can handle it, and it will upgrade every `<a>` element to get this special behavior.
The router also provides an [`<A>`](https://docs.rs/leptos_router/latest/leptos_router/fn.A.html) component, which does two additional things:
1. Correctly resolves relative nested routes. Relative routing with ordinary `<a>` tags can be tricky. For example, if you have a route like `/post/:id`, `<A href="1">` will generate the correct relative route, but `<a href="1">` likely will not (depending on where it appears in your view.) `<A/>` resolves routes relative to the path of the nested route within which it appears.
2. Sets the `aria-current` attribute to `page` if this link is the active link (i.e., it’s a link to the page you’re on). This is helpful for accessibility and for styling. For example, if you want to set the link a different color if it’s a link to the page you’re currently on, you can match this attribute with a CSS selector.
> Once again, this is the same example. Check out the relative `<A/>` components, and take a look at the CSS in `index.html` to see the ARIA-based styling.
[Click to open CodeSandbox.](https://codesandbox.io/p/sandbox/16-router-fy4tjv?file=%2Fsrc%2Fmain.rs&selection=%5B%7B%22endColumn%22%3A1%2C%22endLineNumber%22%3A3%2C%22startColumn%22%3A1%2C%22startLineNumber%22%3A3%7D%5D)
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.