Releases: leptos-rs/leptos
0.8.0-alpha
0.8 has been planned for a while, primarily to accommodate small changes that arose during the course of testing and adopting 0.7, most of which are technically semver-breaking but should not meaningfully affect user code.
Noteworthy features:
- Axum 0.8 support. (This alone required a major version bump, as we reexport some Axum types.) (thanks to @sabify for the migration work here)
- Significant improvements to compile times when using
--cfg=erase_components
, which is useful as a dev-mode optimization (thanks to @zakstucke) - Support for the new
islands-router
features that allow a client-side routing experience while using islands (see theislands_router
example) (this one was me) - Improved server function error handling by allowing you to use any type that implements
FromServerFnError
rather than being constrained to useServerFnError
(see #3274). (Note: This will require changes if you're using a custom error type, but should be a better experience.) (thanks to @ryo33) - Support for creating WebSockets via server fns (thanks to @ealmloff)
As you can see this was a real team effort and, as always, I'm grateful for the contributions of everyone named above, and all those who made commits below. (Apologies if I missed any big features! I will add missing things to release notes as release nears.)
WebSocket Example
The WebSocket support is particularly exciting, as it allows you to call server functions using the default Rust Stream
trait from the futures
crate, and have those streams send messages over websockets without you needing to know anything about that process. The API landed in a place that feels like a great extension of the "server function" abstraction in which you can make HTTP requests as if they were ordinary async calls. The websocket stuff doesn't integrate directly with Resources/SSR (which make more sense for one-shot things) but is really easy to use:
use server_fn::{codec::JsonEncoding, BoxedStream, ServerFnError, Websocket};
// The websocket protocol can be used on any server function that accepts and returns a [`BoxedStream`]
// with items that can be encoded by the input and output encoding generics.
//
// In this case, the input and output encodings are [`Json`] and [`Json`], respectively which requires
// the items to implement [`Serialize`] and [`Deserialize`].
#[server(protocol = Websocket<JsonEncoding, JsonEncoding>)]
async fn echo_websocket(
input: BoxedStream<String, ServerFnError>,
) -> Result<BoxedStream<String, ServerFnError>, ServerFnError> {
use futures::channel::mpsc;
use futures::{SinkExt, StreamExt};
let mut input = input; // FIXME :-) server fn fields should pass mut through to destructure
// create a channel of outgoing websocket messages
// we'll return rx, so sending a message to tx will send a message to the client via the websocket
let (mut tx, rx) = mpsc::channel(1);
// spawn a task to listen to the input stream of messages coming in over the websocket
tokio::spawn(async move {
while let Some(msg) = input.next().await {
// do some work on each message, and then send our responses
tx.send(msg.map(|msg| msg.to_ascii_uppercase())).await;
}
});
Ok(rx.into())
}
#[component]
pub fn App() -> impl IntoView {
use futures::channel::mpsc;
use futures::StreamExt;
let (mut tx, rx) = mpsc::channel(1);
let latest = RwSignal::new(None);
// we'll only listen for websocket messages on the client
if cfg!(feature = "hydrate") {
spawn_local(async move {
match echo_websocket(rx.into()).await {
Ok(mut messages) => {
while let Some(msg) = messages.next().await {
latest.set(Some(msg));
}
}
Err(e) => leptos::logging::warn!("{e}"),
}
});
}
view! {
<input type="text" on:input:target=move |ev| {
tx.try_send(Ok(ev.target().value()));
}/>
<p>{latest}</p>
}
}
What's Changed
- Allow any type that implements FromServerFnError as a replacement of the ServerFnError in server_fn by @ryo33 in #3274
- impl Dispose for Callback types and add try_run to the Callable trait by @basro in #3371
- feat(breaking): allow make
PossibleRouteMatch
dyn-safe by @gbj in #3421 - chore: upgrade
axum
tov0.8
by @sabify in #3439 - feat: Add more options for generating server fn routes by @spencewenski in #3438
- change: allow
IntoFuture
forSuspend::new()
(closes #3509) by @gbj in #3532 - fix: remove
Default
impl forLeptosOptions
andConfFile
by @chrisp60 in #3522 - Fixing closing brace by @thestarmaker in #3539
- AddAnyAttr for AnyView for non-erased by @zakstucke in #3553
- "Update axum paths to 0.8 syntax" by @zakstucke in #3555
- Keep
AddAnyAttr
logic contained by @gbj in #3562 - fix: Actix stream error handling with 0.8 error types by @gbj in #3574
- RenderHtml::into_owned by @zakstucke in #3580
- Binary size wins by @zakstucke in #3566
- fix: remove extra placeholder in Vec of text nodes (closes #3583) by @gbj in #3592
- fix: occasional use-after-disposed panic in Suspense by @gbj in #3595
- projects/bevy3d_ui Migrate to leptos 0.7.7 by @martinfrances107 in #3596
- projects/bevy3d_ui: Bevy migration by @martinfrances107 in #3597
- Minor: leptos_config - Bump the "config" crate to version 0.15.8 by @martinfrances107 in #3594
- Minor: Bump itertools to "0.14.0" by @martinfrances107 in #3593
- Minor: Bumped version of convert_case to 0.7 by @martinfrances107 in #3590
- Minor: "wasm-bindgen" - Moved the crate definition up to the root workspace by @martinfrances107 in #3588
- Remove getrandom by @martinfrances107 in #3589
- Minor: Bump tokio to 1.43. by @martinfrances107 in #3600
- projects/bevy3d_ui: Bevy - Bugfix, clippy and crate bump by @martinfrances107 in #3603
- Add
invert
toOptionStoreExt
by @mahdi739 in #3534 - feat: allow pausing and resuming effects by @gbj in #3599
- Impl into for subfields by @jvdwrf in #3579
- fix: reorder pause check in new_isomorphic by @gbj in #3613
- Minor: drop create_signal form the landing page. by @martinfrances107 in #3611
- chore: update
either_of
minimum version in workspace by @gbj in #3612 - Internally erase html elements by @zakstucke in #3614
- fix: hydration of
()
by @gbj in #3615 - Put serde_json in the root workspace by @martinfrances107 in #3610
- feat: support
Option<_>
instyle:
(closes #3568) by @gbj in #3618 - fix: only render meta tags when rendered, not when created (closes #3629) by @gbj in #3630
- Erased routing, codegen opts by @zakstucke in #3623
- fix: allow decoding already-decoded URI components (closes #3606) by @gbj in #3628
- change: remove unused
Result
alias by @gbj in #3543 - chore(ci): update pinned nightly version by @gbj in #3644
- chore: fix Axum test setup by @gbj in #3651
- feat: support
IntoSplitSignal
for(Signal<T>, SignalSetter<T>)
(closes #3634) by @gbj in #3643 - feat: map and and_then for resource variants by @TERRORW0LF in #3652
- fix: avoid hydration issues with
HashedStylesheet
(closes #3633) by @gbj in #3654 - Islands router by @gbj in #3502
- Implement
Debug
forArcField
andField
by @mahdi739 in #3660 - Minor: examples/server_fns_axum - Bumped various packages (not axum). by @martinfrances107 in #3655
- Erased mode in CI by @zakstucke in #3640
- fix: tweak bounds on For for backwards-compat by @gbj in #3663
- fix: do not double-insert hash character in URLs (closes #3647) by @gbj in #3661
- fix: param segments should not match an empty string that contains only
/
separator (closes #3527) by @gbj in #3662 - fix: ensu...
v0.7.7
If you're migrating from 0.6 to 0.7, please see the 0.7.0 release notes here.
This is a small patch release including primarily bugfixes, and some small ergonomic improvements.
What's Changed
- add file_and_error_handler_with_context by @sstepanchuk in #3526
- feat: impl
From<ArcField<T>>
forField<T>
by @gbj in #3533 - Implement PatchField for Option by @iradicek in #3528
- fix: attribute type erasure nightly (closes #3536) by @gbj in #3537
- fix: emit syntax errors in components rather than swallowing them (closes #3535) by @gbj in #3538
- Fix ci by @zakstucke in #3557
- Implement
Attribute
forEither<A, B>
by @alexisfontaine in #3556 - chore(ci):
cargo install --locked
forcargo-leptos
installation by @gbj in #3559 - fix: don't use InertElement for
style:
etc. (closes #3554) by @gbj in #3558 - fix: do not hold lock on arena when dispatching Action by @gbj in #3561
- fix: Return empty iterator instead of panicking when the KeyedSubfield is disposed by @mahdi739 in #3550
- Less panic in stores by @mahdi739 in #3551
- Handle
erase_components
onEither<A, B>
by @alexisfontaine in #3572 - fix(reactive_stores_macro):
store
attribute signature error message by @DanikVitek in #3567 - feat: add
:capture
flag for events to handle them during capture phase (closes #3457) by @gbj in #3575 - Add missing
<fieldset>
attributes by @alexisfontaine in #3581 - Allows non static lifetimes in component macro by @jvdwrf in #3571
New Contributors
- @sstepanchuk made their first contribution in #3526
- @iradicek made their first contribution in #3528
- @jvdwrf made their first contribution in #3571
Full Changelog: v0.7.5...v0.7.7
v0.7.5
If you're migrating from 0.6 to 0.7, please see the 0.7.0 release notes here.
This is a small patch release including primarily bugfixes.
What's Changed
- chore: work around wasm-bindgen breakage by @gbj in #3498
- Add support for custom patch by @mscofield0 in #3449
- feat: either_or combinator by @geovie in #3417
- (wip): implement unboxing support for recursive store nodes (closes #3491) by @gbj in #3493
- fix: correctly handle
ErrorBoundary
through reactive views (closes #3487) by @gbj in #3492 - chore: restore reactivity warning at top level of components (closes #3354) by @gbj in #3499
- feat:
#[lazy]
macros to support lazy loading and code splitting by @gbj in #3477 - chore(ci): add CI for
leptos_0.8
branch by @gbj in #3500 - fix: including
node_ref
after{..}
on arbitrary components by @gbj in #3503 - Enhanced docs for reactive_stores by @dcsturman in #3508
- Adding a project detailing flexible mocking architecture inspired by hexagonal architecture for leptos app by @sjud in #3342
- issue-3467 - bumping codee version to support rkyv 8 by @thestarmaker in #3504
- docs: Fix README.md & Add MSRV badge by @DanikVitek in #3480
- update workspace dependency versions by @gbj in #3506
- feat (
either_of
): Extent API; Implement other iterator methods; Update deps by @DanikVitek in #3478 - AddAnyAttr working with erase_components by @zakstucke in #3518
- impl IntoAttributeValue for TextProp by @SleeplessOne1917 in #3517
- Fix memo recomputation by @stefnotch in #3495
- feat(callback): implement
matches
method for Callback and UnsyncCallback by @geoffreygarrett in #3520 - fix: correctly notify descendants and ancestors of store fields (closes #3523) by @gbj in #3524
- feat: allow raw identifiers in Params derive macro by @geovie in #3525
New Contributors
- @dcsturman made their first contribution in #3508
Full Changelog: v0.7.4...v0.7.5
v0.7.4
If you're migrating from 0.6 to 0.7, please see the 0.7.0 release notes here.
This is a small patch release including a couple of bugfixes,
What's Changed
- Rkyv feature should propagate properly by @thestarmaker in #3448
- fix: allow multiple overlapping notifications of AsyncDerived/LocalResource (closes #3454) by @gbj in #3455
- Derive clone for RouteChildren by @Innominus in #3462
- fix: do not overwrite SuspenseContext in adjacent Transition components (closes #3465) by @gbj in #3471
- Implement AddAnyAttr trait for Static by @geoffreygarrett in #3464
- feat: add [Arc]LocalResource::refetch method by @linw1995 in #3394
- chore: fix tracing warning mess by @gbj in #3473
- Fix: values being moved with the debug_warn! macro when compiling with not(debug_assertions) by @WorldSEnder in #3446
- docs: warn about callbacks outside the ownership tree by @tversteeg in #3442
- fix: do not use stale values in AsyncDerived if it is mutated before running by @gbj in #3475
- fix: include missing nonces on streaming script tags and on
leptos_meta
components (closes #3482) by @gbj in #3485
New Contributors
- @geoffreygarrett made their first contribution in #3464
- @linw1995 made their first contribution in #3394
- @WorldSEnder made their first contribution in #3446
Full Changelog: v0.7.3...v0.7.4
v0.7.3
If you're migrating from 0.6 to 0.7, please see the 0.7.0 release notes here.
This is a small patch release including a couple of bugfixes, as well as the ability to destructure prop value in components with a new #[prop(name = ...)]
syntax (see #3382)
#[prop(name = "data")] UserInfo { email, user_id }: UserInfo,
What's Changed
- Enable
console
feature ofweb-sys
forreactive_graph
by @alexisfontaine in #3406 - fix: correctly track updates to keyed fields in keyed iterator (closes #3401) by @gbj in #3403
- fix:
getrandom
needsjs
feature (used whennonce
feature is active) (closes #3409) by @gbj in #3410 - Provide custom state in
file_and_error_handler
by @spencewenski in #3408 - feat: allow to destructure props by @geovie in #3382
- Add missing
#[track_caller]
s by @mscofield0 in #3422 - Fix issues with island hydration when nested in a closure, and with context (closes #3419) by @gbj in #3424
- docs: remove
islands
mention fromleptos_axum
by @chrisp60 in #3423 - fix: correct ownership in redirect route hook (closes #3425) by @gbj in #3428
- fix failure on try_new() methods which should not fail by @kstep in #3436
- Add
Default
to stores by @mscofield0 in #3432 - add
Dispose
forStore
by @mscofield0 in #3429 - Tachy class: impl IntoClass for Cow<'_, str> by @sgued in #3420
- fix:
erase_components
with AttributeInterceptor by @gbj in #3435
New Contributors
Full Changelog: v0.7.2...v0.7.3
v0.7.2
If you're migrating from 0.6 to 0.7, please see the 0.7.0 release notes here.
This is a small patch release including a couple of bugfixes, importantly to the hydration of static text nodes on nightly
.
What's Changed
- Update elements.rs to include
popovertarget
andpopovertargetaction
for the<button>
element by @Figments in #3379 - fix(ci): missing glib in ci by @sabify in #3376
- docs: showcase let syntax in for_loop by @purung in #3383
- Add
From<ArcStore<T>>
forStore<T, S>
by @mscofield0 in #3389 - fix: correct span for
let:
syntax (closes #3387) by @gbj in #3391 - fix(ci): add missing glib for semver checks by @sabify in #3393
- fix: correct hydration position for static text nodes in
nightly
(closes #3395) by @gbj in #3396
New Contributors
Full Changelog: v0.7.1...v0.7.2
v0.7.1
If you're migrating from 0.6 to 0.7, please see the 0.7.0 release notes here.
This is just a small patch release, two weeks after the 0.7.0 release, geared toward fixing in bugs and filling in API holes since then.
What's Changed
- fix: prevent multiple location headers on redirect (#3298) by @veigaribo in #3311
- Remove the Send requirement on a local Action's future (fixes #3309) by @rjmac in #3310
- fix: wait for blocking resources before sending subsequent chunks (closes #3280) by @gbj in #3282
- chore: fix typo by @mahdi739 in #3320
- chore(deps): bump postcard from 1.0.10 to 1.1.1 by @dependabot in #3319
- chore(deps): bump wasm-bindgen-futures from 0.4.45 to 0.4.47 by @dependabot in #3318
- chore(deps): bump js-sys from 0.3.72 to 0.3.74 by @dependabot in #3306
- chore(deps): bump bytes from 1.8.0 to 1.9.0 by @dependabot in #3297
- chore(deps): bump url from 2.5.3 to 2.5.4 by @dependabot in #3291
- chore(deps): bump tracing from 0.1.40 to 0.1.41 by @dependabot in #3294
- chore(deps): bump wasm-bindgen from 0.2.95 to 0.2.97 by @dependabot in #3317
- chore(deps): bump rkyv from 0.8.8 to 0.8.9 by @dependabot in #3290
- chore(deps): bump hyper from 1.5.0 to 1.5.1 by @dependabot in #3265
- chore(deps): bump rustls from 0.23.16 to 0.23.18 in the cargo group across 1 directory by @dependabot in #3289
- fix: correctly swap
Vec<_>
in the DOM (closes #3321) by @gbj in #3324 - fix: correctly support
!Send
Actix APIs in server functions by @gbj in #3326 - fix: correctly mount/unmount hydrated static text nodes in nightly mode (closes #3334) by @gbj in #3336
- feat: add
scroll
prop to<A/>
component to control scrolling behavior (closes #2666) by @gbj in #3333 - Increase number of branch arms for
either!
macro by @bicarlsen in #3337 - Fix a doc typo and one broken intra-doc link by @FreezyLemon in #3341
- fix: nested keyed fields in stores (closes #3338) by @gbj in #3344
- fix: don't try to hydrate inside
<noscript>
(closes #3360) by @gbj in #3363 - feat : implemented actix multipart by @sutantodadang in #3361
- Minor typo fix in
ToChildren::to_children
docs. by @bicarlsen in #3352 - TypedChildrenFn impl Clone trait by @redforks in #3349
- Opt in locations in release mode with --cfg locations by @zakstucke in #3281
- fix docs by @mscofield0 in #3350
- Referential context: with_context and update_context by @zakstucke in #3279
- fix: Implement PatchField for
usize
by @marcuswhybrow in #3346 - Fix memo check behavior by @gbj in #3356
- feat: AttributeInterceptor component to allow passing attributes to other elements by @paul-hansen in #3340
- docs: clarify
Signal::derive()
behavior by @gbj in #3351 - fix: rebuilding of
InertElement
(closes #3368) by @gbj in #3370 - chore: reenable
cargo-semver-checks
on PRs by @gbj in #3375 - Implement signal.into_inner() by @stefnotch in #3343
- Implement IntoSplitSignal for Field. by @fiadliel in #3364
New Contributors
- @veigaribo made their first contribution in #3311
- @FreezyLemon made their first contribution in #3341
- @sutantodadang made their first contribution in #3361
- @redforks made their first contribution in #3349
- @mscofield0 made their first contribution in #3350
- @marcuswhybrow made their first contribution in #3346
- @fiadliel made their first contribution in #3364
Full Changelog: v0.7.0...v0.7.1
v0.7.0
At long last, as the culmination of more than a year of work, the 0.7 release has arrived!
0.7 is a nearly-complete rewrite of the internals of the framework, with the following goals:
- maintain backwards compatibility for as much user application code as possible
- improve the async story and fix Suspense edge cases and limitations
- reduce WASM binary size
- reduce HTML size
- faster HTML rendering
- allow signals to be sent across threads
- enhance the ergonomics of things like prop spreading and accessing the HTML shell of your application
- build the foundation for future work
- reactive stores to make nested reactivity more pleasant
- client-side routing with islands and state preservation
- integrating with native UI toolkits to create desktop applications
Getting Started
0.7 works with the current cargo-leptos
version. If you want to start exploring, there are starter templates for Axum and Actix. Each template is only three files. They show some of the boilerplate differences; for more details, see below.
Axum: cargo leptos new --git https://github.com/leptos-rs/start-axum
(repo)
Actix: cargo leptos new --git https://github.com/leptos-rs/start-actix
(repo)
New Features
.await
on resources and async
in <Suspense/>
Currently, create_resource
allows you to synchronously access the value of some async data as either None
or Some(_)
. However, it requires that you always access it this way. This has some drawbacks:
- requires that you null-check every piece of data
- makes it difficult for one resource to wait for another resource to load
Now, you can .await
a resource, and you can use async
blocks within a <Suspense/>
via the Suspend
wrapper, which makes it easier to chain two resources:
let user = Resource::new(|| (), |_| user_id());
let posts = Resource::new(
// resources still manually track dependencies (necessary for hydration)
move || user.get(),
move |_| async move {
// but you can .await a resource inside another
let user = user.await?;
get_posts(user).await
},
);
view! {
<Suspense>
// you can `.await` resources to avoid dealing with the `None` state
<p>"User ID: " {move || Suspend::new(async move {
match user.await {
// ...
}
})}</p>
// or you can still use .get() to access resources in things like component props
<For
each=move || posts.get().and_then(Result::ok).unwrap_or_default()
key=|post| post.id
let:post
>
// ...
</For>
</Suspense>
}
Reference-counted signal types
One of the awkward edge cases of current Leptos is that our Copy
arena for signals makes it possible to leak memory if you have a collection of nested signals and do not dispose them. (See 0.6 example.) 0.7 exposes ArcRwSignal
, ArcReadSignal
, etc., which are Clone
but not Copy
and manage their memory via reference counting, but can easily be converted into the copyable RwSignal
etc. This makes working with nested signal correctly much easier, without sacrificing ergonomics meaningfully. See the 0.7 counters
example for more.
.read()
and .write()
on signals
You can now use .read()
and .write()
to get immutable and mutable guards for the value of a signal, which will track/update appropriately: these work like .with()
and .update()
but without the extra closure, or like .get()
but without cloning.
let long_vec = RwSignal::new(vec![42; 1000]);
let short_vec = RwSignal::new(vec![13; 2]);
// bad: clones both Vecs
let bad_len = move || long_vec.get().len() + short_vec.get().len();
// ugly: awkward nested syntax (or a macro)
let ugly_len = move || long_vec.with(|long| short_vec.with(|short| long.len() + short.len()));
// readable but doesn't clone
let good_len = move || long_vec.read().len() + short_vec.read().len();
These should always be used for short periods of time, not stored somewhere for longer-term use, just like any guard or lock, or you can cause deadlocks or panics.
Custom HTML shell
The HTML document "shell" for server rendering is currently hardcoded as part of the server integrations, limiting your ability to customize it. Now you simply include it as part of your application, which also means that you can customize things like teh <title>
without needing to use leptos_meta
.
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<MetaTags/>
</head>
<body>
<App/>
</body>
</html>
}
}
Enhanced attribute spreading
Any valid attribute can now be spread onto any component, allowing you to extend the UI created by a component however you want. This works through multiple components: for example, if you spread attributes onto a Suspense
they will be passed through to whatever it returns.
// attributes that are spread onto a component will be applied to *all* elements returned as part of
// the component's view. to apply attributes to a subset of the component, pass them via a component prop
<ComponentThatTakesSpread
// the class:, style:, prop:, on: syntaxes work just as they do on elements
class:foo=true
style:font-weight="bold"
prop:cool=42
on:click=move |_| alert("clicked ComponentThatTakesSpread")
// props are passed as they usually are on components
some_prop=13
// to pass a plain HTML attribute, prefix it with attr:
attr:id="foo"
// or, if you want to include multiple attributes, rather than prefixing each with
// attr:, you can separate them from component props with the spread {..}
{..} // everything after this is treated as an HTML attribute
title="ooh, a title!"
{..spread_onto_component}
/>
Improved <ProtectedRoute/>
The current ProtectedRoute
component is not great: it checks the condition once, synchronously, on navigation, and so it doesn't respond to changes and can't easily be used with async data. The new ProtectedRoute
is reactive and uses Suspense so you can use resources or reactive data. There are examples of this now in router
and ssr_modes_axum
.
Two-way binding with bind:
syntax
Two-way binding allows you to pass signals directly to inputs, rather than separately managing prop:value
and on:input
to sync the signals to the inputs.
// You can use `RwSignal`s
let is_awesome = RwSignal::new(true);
let sth = RwSignal::new("one".to_string());
// And you can use split signals
let (text, set_text) = signal("Hello world".to_string());
view! {
// Use `bind:checked` and a `bool` signal for a checkbox
<input type="checkbox" bind:checked=is_awesome />
// Use `bind:group` and `String` for radio inputs
<input type="radio" value="one" bind:group=sth />
<input type="radio" value="two" bind:group=sth />
<input type="radio" value="trhee" bind:group=sth />
// Use `bind:value` and `String` for everything else
<input type="text" bind:value=(text, set_text) />
<textarea bind:value=(text, set_text) />
}
Reactive Stores
Stores are a new reactive primitive that allow you to reactively access deeply-nested fields in a struct without needing to create signals inside signals; rather, you can use plain data types, annotated with #[derive(Store)]
, and then access fields with reactive getters/setters.
Updating one subfield of a Store
does not trigger effects only listening to a sibling field; listening to one field of a store does not track the sibling fields.
Stores are most useful for nested data structures, so a succinct example is difficult, but the stores
example shows a complete use case.
Support the View Transition API for router animations
The Routes
/FlatRoutes
component now have a transition
prop. Setting this to true
will cause the router to use the browser's View Transition API during navigation. You can control animations during navigation using CSS classes. Which animations are used can be controlled using classes that the router will set on the <html>
element: .routing-progress
while navigating, .router-back
during a back navigation, and .router-outlet-{n}
for the depth of the outlet that is being changed (0
for the root page changing, 1
for the first Outlet
, etc.) The router
example uses this API.
Note: View Transitions are not supported on all browsers, but have been accepted as a standard and can be polyfilled. Using a built-in browser API is much better in the long term than our bug-prone and difficult-to-maintain custom implementation.
Breaking Changes
Imports
I'm reorganizing the module structure to improve docs and discoverability. We will still have a prelude that can be used for...
v0.7.0-rc3
A very minor release to facilitate leptos-wasi, a new base integration crate used by the Spin and new WasmCloud integrations!
What's Changed
- chore(deps): bump proc-macro2 from 1.0.91 to 1.0.92 by @dependabot in #3276
- Version any_spawner alongside other crates, reexport CustomExecutor by @ogghead in #3284
- Fix help message for island macro by @NiklasEi in #3287
- docs: improve line location of hydration error message when using
view!
macro by @gbj in #3293
Full Changelog: v0.7.0-rc2...v0.7.0-rc3
v0.7.0-rc2
This is a release candidate for 0.7.0.
This means that APIs are understood to be set for 0.7.0, except to the extent that bugs found during the series of release candidates require breaking changes to fix.
Most of the release notes for this
rc
are copied over from the alpha/beta/rc1, but the hope is for this to be a complete picture of changes. Please let me know with any missing breaking changes or features so that can be added.
0.7 is a nearly-complete rewrite of the internals of the framework, with the following goals:
- maintain backwards compatibility for as much user application code as possible
- improve the async story and fix Suspense edge cases and limitations
- reduce WASM binary size
- reduce HTML size
- faster HTML rendering
- allow signals to be sent across threads
- enhance the ergonomics of things like prop spreading and accessing the HTML shell of your application
- build the foundation for future work
- reactive stores to make nested reactivity more pleasant
- client-side routing with islands and state preservation
- integrating with native UI toolkits to create desktop applications
Getting Started
0.7 works with the current cargo-leptos
version. If you want to start exploring, there are starter templates for Axum and Actix. Each template is only three files. They show some of the boilerplate differences; for more details, see below.
Axum: cargo leptos new --git https://github.com/leptos-rs/start-axum-0.7
(repo)
Actix: cargo leptos new --git https://github.com/leptos-rs/start-actix-0.7
(repo)
New Features
.await
on resources and async
in <Suspense/>
Currently, create_resource
allows you to synchronously access the value of some async data as either None
or Some(_)
. However, it requires that you always access it this way. This has some drawbacks:
- requires that you null-check every piece of data
- makes it difficult for one resource to wait for another resource to load
Now, you can .await
a resource, and you can use async
blocks within a <Suspense/>
via the Suspend
wrapper, which makes it easier to chain two resources:
let user = Resource::new(|| (), |_| user_id());
let posts = Resource::new(
// resources still manually track dependencies (necessary for hydration)
move || user.get(),
move |_| async move {
// but you can .await a resource inside another
let user = user.await?;
get_posts(user).await
},
);
view! {
<Suspense>
// you can `.await` resources to avoid dealing with the `None` state
<p>"User ID: " {move || Suspend::new(async move {
match user.await {
// ...
}
})}</p>
// or you can still use .get() to access resources in things like component props
<For
each=move || posts.get().and_then(Result::ok).unwrap_or_default()
key=|post| post.id
let:post
>
// ...
</For>
</Suspense>
}
Reference-counted signal types
One of the awkward edge cases of current Leptos is that our Copy
arena for signals makes it possible to leak memory if you have a collection of nested signals and do not dispose them. (See 0.6 example.) 0.7 exposes ArcRwSignal
, ArcReadSignal
, etc., which are Clone
but not Copy
and manage their memory via reference counting, but can easily be converted into the copyable RwSignal
etc. This makes working with nested signal correctly much easier, without sacrificing ergonomics meaningfully. See the 0.7 counters
example for more.
.read()
and .write()
on signals
You can now use .read()
and .write()
to get immutable and mutable guards for the value of a signal, which will track/update appropriately: these work like .with()
and .update()
but without the extra closure, or like .get()
but without cloning.
let long_vec = RwSignal::new(vec![42; 1000]);
let short_vec = RwSignal::new(vec![13; 2]);
// bad: clones both Vecs
let bad_len = move || long_vec.get().len() + short_vec.get().len();
// ugly: awkward nested syntax (or a macro)
let ugly_len = move || long_vec.with(|long| short_vec.with(|short| long.len() + short.len()));
// readable but doesn't clone
let good_len = move || long_vec.read().len() + short_vec.read().len();
These should always be used for short periods of time, not stored somewhere for longer-term use, just like any guard or lock, or you can cause deadlocks or panics.
Custom HTML shell
The HTML document "shell" for server rendering is currently hardcoded as part of the server integrations, limiting your ability to customize it. Now you simply include it as part of your application, which also means that you can customize things like teh <title>
without needing to use leptos_meta
.
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<MetaTags/>
</head>
<body>
<App/>
</body>
</html>
}
}
Enhanced attribute spreading
Any valid attribute can now be spread onto any component, allowing you to extend the UI created by a component however you want. This works through multiple components: for example, if you spread attributes onto a Suspense
they will be passed through to whatever it returns.
// attributes that are spread onto a component will be applied to *all* elements returned as part of
// the component's view. to apply attributes to a subset of the component, pass them via a component prop
<ComponentThatTakesSpread
// the class:, style:, prop:, on: syntaxes work just as they do on elements
class:foo=true
style:font-weight="bold"
prop:cool=42
on:click=move |_| alert("clicked ComponentThatTakesSpread")
// props are passed as they usually are on components
some_prop=13
// to pass a plain HTML attribute, prefix it with attr:
attr:id="foo"
// or, if you want to include multiple attributes, rather than prefixing each with
// attr:, you can separate them from component props with the spread {..}
{..} // everything after this is treated as an HTML attribute
title="ooh, a title!"
{..spread_onto_component}
/>
Improved <ProtectedRoute/>
The current ProtectedRoute
component is not great: it checks the condition once, synchronously, on navigation, and so it doesn't respond to changes and can't easily be used with async data. The new ProtectedRoute
is reactive and uses Suspense so you can use resources or reactive data. There are examples of this now in router
and ssr_modes_axum
.
Two-way binding with bind:
syntax
Two-way binding allows you to pass signals directly to inputs, rather than separately managing prop:value
and on:input
to sync the signals to the inputs.
// You can use `RwSignal`s
let is_awesome = RwSignal::new(true);
let sth = RwSignal::new("one".to_string());
// And you can use split signals
let (text, set_text) = signal("Hello world".to_string());
view! {
// Use `bind:checked` and a `bool` signal for a checkbox
<input type="checkbox" bind:checked=is_awesome />
// Use `bind:group` and `String` for radio inputs
<input type="radio" value="one" bind:group=sth />
<input type="radio" value="two" bind:group=sth />
<input type="radio" value="trhee" bind:group=sth />
// Use `bind:value` and `String` for everything else
<input type="text" bind:value=(text, set_text) />
<textarea bind:value=(text, set_text) />
}
Reactive Stores
Stores are a new reactive primitive that allow you to reactively access deeply-nested fields in a struct without needing to create signals inside signals; rather, you can use plain data types, annotated with #[derive(Store)]
, and then access fields with reactive getters/setters.
Updating one subfield of a Store
does not trigger effects only listening to a sibling field; listening to one field of a store does not track the sibling fields.
Stores are most useful for nested data structures, so a succinct example is difficult, but the stores
example shows a complete use case.
Support the View Transition API for router animations
The Routes
/FlatRoutes
component now have a transition
prop. Setting this to true
will cause the router to use the browser's View Transition API during navigation. You can control animations during navigation using CSS classes. Which animations are used can be controlled using classes that the router will set on the <html>
element: .routing-progress
while navigating, .router-back
during a back navigation, and .router-outlet-{n}
for the depth of the outlet that is being changed (0
for the root page changing, 1
for the first Outlet
, etc.) The router
example uses this API.
Note: View Transitions are not supported on all browsers, but have been accepted as a standard an...