Releases: leptos-rs/leptos
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...
v0.7.0-rc1
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/rc0, 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...
v0.7.0-rc0
This is the first 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, 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 standar...
v0.6.15
Belated release notes for 0.6.15. This was a quick patch release to incorporate two changes, one to improve rust-analyzer support and the other to switch from the unmaintained proc-macro-error
to proc-macro-error2
per RUSTSEC.
What's Changed
- [0.6] fix: Rust-Analyzer hover information / redundant spans by @chrisp60 in #2840
- leptos 0.6: Switch to
proc-macro-error2
to address unmaintained security advisory. by @azriel91 in #2935
Full Changelog: v0.6.14...v0.6.15
v0.6.14
Hello everyone, The biggest change in this update is to handle wasm-bindgen 0.2.93 and web_sys 0.3.70 Thanks to @sabify and @maccesch for those PRs. As always, let us know if there's issues.
What's Changed
- fix: untrack children in Portal to avoid re-triggering it accidentally (closes #2693) by @gbj in #2713
- chore: fix some comments by @renshuncui in #2712
- Mnior: As of rust1.80: cargo clippy now reports doc indentation issues. by @martinfrances107 in #2728
- chore(ci): update nightly by @gbj in #2755
- chore: update gloo-net and reqwest to http 1.0 (closes #2688) (leptos 0.6) by @sabify in #2751
- fix: update
wasm-bindgen
andweb-sys
for leptos 0.6 by @sabify in #2830
New Contributors
- @renshuncui made their first contribution in #2712
Full Changelog: v0.6.13...v0.6.14
v0.7.0-beta
This is a release made from #2607, with updates, bugfixes, and improvements since 0.7.0-alpha.
Most of the release notes for this beta are copied over from the alpha.
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
I am going on vacation for a couple weeks, and my hope is to give some time over the summer for people to really test, explore, and try to migrate.
Getting Started
0.7 works with the current cargo-leptos
version. If you want to start exploring, I've created 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)
Please use the PR (#2607) or the #07-beta
channel in our Discord for feedback or questions.
Generally speaking, the leptos_0.7
branch will include the latest bug fixes, but any given commit may be broken (sorry!)
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.track(),
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 current 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
.
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 glob imports of almost everything that's currently exported from the root.
- use leptos::*;
+ use leptos::prelude::*;
Likewise, the router exposes things via leptos_router::components
and leptos_router::hooks
. rust-analyzer can help fix imports fairly well.
I'm hoping for feedback on the new module structure, whether it makes sense, and any improvements. I have not done too much work to sort through the reexports, look at how docs look, etc. yet.
Naming
We're migrating away from create_
naming toward more idiomatic Rust naming patterns:
create_signal
tosignal
(likechannel
)create_rw_signal
toRwSignal::new()
- etc.
I've left some of the current functions in, marked deprecated; others may have been missed, but should be easy to find via docs.rs.
Type erasure and view types
One of the major changes in this release is replacing the View
enum with statically-typed views, which is where most of the binary size savings come from. If you need to branch and return one of several types, you can either use one of the Either
enums in leptos::either
, or you can use .into_any()
to erase the type. Generally speaking the compiler can do its job better if you maintain more type information so the Either
types should be preferred, but AnyView
is not bad to use when needed.
// Either
if some_condition {
Either::Left(view! { <p>"Foo"</p> })
} else {
Either::Right("Bar")
}
// .into_any()
if some_condition {
view! { <p>"Foo"</p> }.into_any()
} else {
"Bar".into_any()
}
Boilerplate
There have been changes to the SSR and hydration boilerplate, which include (but aren't limited to)
get_configuration
is sync (remove the.await
)- you provide the app shell
.leptos_routes
no longer takesLeptosOptions
as an argument- use
leptos::mount::hydrate_body
(hydration) instead ofleptos::mount::mount_to_body
(which is now CSR-specific) - ... and probably more
Check the starter templates for a good setup.
Route definitions
The patterns for route d...