Skip to content

Releases: leptos-rs/leptos

v0.7.0-rc1

05 Nov 02:02
Compare
Choose a tag to compare

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...

Read more

v0.7.0-rc0

22 Oct 01:30
@gbj gbj
Compare
Choose a tag to compare
v0.7.0-rc0 Pre-release
Pre-release

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...

Read more

v0.6.15

18 Sep 20:51
@gbj gbj
Compare
Choose a tag to compare

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

14 Aug 02:43
Compare
Choose a tag to compare

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 and web-sys for leptos 0.6 by @sabify in #2830

New Contributors

Full Changelog: v0.6.13...v0.6.14

v0.7.0-beta

24 Jul 13:44
@gbj gbj
Compare
Choose a tag to compare
v0.7.0-beta Pre-release
Pre-release

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 to signal (like channel)
  • create_rw_signal to RwSignal::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 takes LeptosOptions as an argument
  • use leptos::mount::hydrate_body (hydration) instead of leptos::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...

Read more

v0.6.13

24 Jul 12:11
@gbj gbj
7e75801
Compare
Choose a tag to compare

This release mostly includes a series of small bugfixes (see below), but also includes a fix for the annoying issues we'd been having with rust-analyzer (#2527).

What's Changed

New Contributors

Full Changelog: v0.6.12...v0.6.13

0.7.0-alpha

29 Jun 12:08
@gbj gbj
Compare
Choose a tag to compare
0.7.0-alpha Pre-release
Pre-release

This is a release made from #2607, now that the 0.7 branch has reached a relatively stable state, and all of the examples are in working condition.

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

My goal between now and the end of July is to fill in the remaining missing pieces and release a beta version.

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 #preview 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(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.

Known missing or not-yet-implemented APIs

  • local resources (resources that only run on the client, not server)
  • <ProtectedRoute/>
  • Render individual Suspend(_) without a <Suspense/>
  • Nested router set_is_routing
  • Attribute spreading for attributes on <Body/> etc. instead of Vec<AnyAttribute>
  • Equivalent to a user implementing IntoView on an arbitrary struct
  • Figuring out the interaction between Lazy routes and hydration (what do you do while hydrating if the code for the route hasn't been lazy-loaded yet?)
  • path!() macro to make route definitions easier in router
  • Reactive channel implementation for single-use signals
  • blocking resources
  • partially-blocked SSR
  • Thread-local arena that yields Copy but !Send for !Send data (like browser things)
  • global class support
  • hot reloading (and therefore view markers)
  • static routes

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 to signal (like channel)
  • create_rw_signal to RwSignal::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 ...

Read more

v0.6.12

02 Jun 18:27
@gbj gbj
2ef27cb
Compare
Choose a tag to compare

This is mainly a maintenance release, but includes a couple new features that I want to point out:

impl Trait in Component Props

You can now use impl Trait syntax directly in component props, rather than explicitly specifying a generic and a where clause

before

#[component]
fn ProgressBar<F>(#[prop(default = 100)] max: u16, progress: F) -> impl IntoView
where
    F: Fn() -> i32 + 'static,
{
    view! {
        <progress
            max=max
            value=progress
        />
    }
}

after

#[component]
fn ProgressBar(
    #[prop(default = 100)] max: u16,
    progress: impl Fn() -> i32 + 'static,
) -> impl IntoView {
    view! {
        <progress
            max=max
            value=progress
        />
    }
}

Support spreading dynamic attributes from one component to another

In the following code Bar doesn't currently inherit attributes from Foo when it spreads its attributes. PR #2534 fixes this.

fn main() {
    let (count, set_count) = create_signal(0);

    mount_to_body(move || {
        view! {
            <Foo
                attr:hello=move || count.get().to_string()
            />

            <button on:click=move|_| { set_count.update(|count| *count += 1) }>"+ count"</button>
        }
    });
}

#[component]
fn Foo(#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>) -> impl IntoView {
    view! {
        <Bar {..attrs} />
    }
}

#[component]
fn Bar(#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>) -> impl IntoView {
    view! {
        <div {..attrs}>"hello world"</div>
    }
}

Complete Changelog

New Contributors

Full Changelog: v0.6.11...v0.6.12

v0.7.0-preview2

29 Apr 01:13
@gbj gbj
9353316
Compare
Choose a tag to compare

The -preview here is intended to convey: Here is a mostly-working but pre-alpha release of what I've been working on for the last six months or so. This work can be found in the leptos-0.7 branch. Enough work has been done that many (but not all) of the examples in the repo are functioning. This release is a fairly complete rewrite of the internals of the entire framework. You should expect both missing APIs and bugs.

Note the following:

  • You probably cannot just drop 0.7.0-preview2 to the Cargo.toml of an existing app and expect it to work
  • Imports have moved around a bit, to help improve discoverability, including moving from use leptos::*; to use leptos::prelude::*; and then using modules and reexports more sanely from the main crate
  • I've created a 0.7.0-preview playground that includes the setup of a basic app with comments. You should be able to expand from there.
  • There are lots of missing docs. These are easier to fill in going forward than to keep up to date during really active development.

Examples that Work

The following examples in the repo are known to work and can be useful to learn from

Notable Changes

  • I'm trying to avoid using features to change behavior as much as possible; see examples for Cargo.toml setup
  • use leptos::prelude::* instead of use leptos::*
  • Reactive system is now Send/Sync. In general for values being stored in signals this means you need to use Arc on occasion instead of Rc, etc.
    • For browser-only types stored inside signals I've tended to use the send_wrapper crate. Better ergonomics here are an open question
  • The renderer and IntoView trait work quite differently from before. In general, for type-erased views (.into_view() previously) you can use .into_any() or the Either:: types. Storing a View in a signal, cloning it, etc. are not viable approaches any more: store data in signals and rendering views, rather than storing views in signals.
  • There are Arc equivalents to each signal type (ArcRwSignal, ArcMemo, etc.) which manage their lifecycle via reference counting rather than the reactive ownership graph. This can be used for things like iterating over nested signals (see counters example) and pushing signals "up" in the tree, rather than using the manual .dispose() and owner manipulation patterns
  • Continuing to move names toward more typical Rust naming patterns (RwSignal::new(), signal() instead of create_signal() to match channel(), etc.)
  • Suspense now uses actual async -- see examples
  • The router uses a more statically-typed/compile-time route segment matching pattern. I have plans for a path!() macro to parse the old strings into this format but it's not implemented.
  • Route matching is now "first match wins," rather than using a scoring algorithm. I think this should work similarly to server-side route matching, but is not exactly how the old system worked.

Known Missing APIs

  • Actix integration (the playground uses Axum)
  • on: on components (e82227a)
  • spreading attributes onto components (e82227a)
  • MaybeSignal
  • Signal::with()
  • cargo-leptos hot reloading
  • anything animated (AnimatedShow, AnimatedRoutes, AnimatedOutlet)
  • Portals
  • slots
  • islands?
  • ... will add more here

Steps before -alpha

  • Migrate remaining examples
  • Missing features (above)
  • Update docs
  • Update tests
  • Add a !Send/!Sync thread-local arena for browser-type signals

What's Helpful?

Try things out, see what breaks, see what feels good. How can we improve the module imports? What are pain points? etc. Feel free to comment here or on Discord in the #preview channel,

v0.6.11

11 Apr 19:56
@gbj gbj
Compare
Choose a tag to compare

The primary purpose of this release is that it includes a fix for an unfortunate memory leak when using leptos_router on the server.

Also included are

  • the ability to spread both attributes and event handlers onto an element (see the new spread example for the full set of possibilities)
  • implementing IntoView directly for Rc<str>
  • massive improvements to the spans for error reporting in the view macro
  • migrating all our examples to use the stable features/syntax by default, to reduce confusion for new users

It's important to me to say that all three of the new features above were implemented by community members. This release brings us to over 250 total contributors over time, not to mention everyone who's done work on docs, templates, or libraries that exist outside this repo. Thank you to everyone who's been involved in this project so far.

What's Changed

New Contributors

Full Changelog: v0.6.10...v0.6.11