Skip to content

v0.8.0-rc3

Pre-release
Pre-release
Compare
Choose a tag to compare
@gbj gbj released this 26 Apr 20:15
· 348 commits to main since this release

Release notes copied from 0.8.0-alpha/beta/rc1/. Changelog relative to 0.8.0-rc2. This release includes some bugfixes that required one additional release.

0.8 has been planned for a while, primarily to accommodate small changes that arose during the course of testing and adopting 0.7, most of which are technically semver-breaking but should not meaningfully affect user code.

If we don't hear any feedback about issues with this rc2 release, we will plan to release it as 0.8.0 in the next week or so.

Noteworthy features:

  • Axum 0.8 support. (This alone required a major version bump, as we reexport some Axum types.) (thanks to @sabify for the migration work here)
  • Significant improvements to compile times when using --cfg=erase_components, which is useful as a dev-mode optimization (thanks to @zakstucke) This is the default setting for debug mode in future releases of cargo-leptos, and can be set up manually for use with Trunk. (See docs here.)
  • Support for the new islands-router features that allow a client-side routing experience while using islands (see the islands_router example) (this one was me)
  • Improved server function error handling by allowing you to use any type that implements FromServerFnError rather than being constrained to use ServerFnError (see #3274). (Note: This will require changes if you're using a custom error type, but should be a better experience.) (thanks to @ryo33)
  • Support for creating WebSockets via server fns (thanks to @ealmloff)
  • Changes to make custom errors significantly more ergonomic when using server functions
  • LocalResource no longer exposes a SendWrapper in the API for the types it returns. (Breaking change: this will require removing some .as_deref() and so on when using LocalResource, but ends up with a much better API.)
  • Significantly improved DX/bugfixes for thread-local Actions.

As you can see this was a real team effort and, as always, I'm grateful for the contributions of everyone named above, and all those who made commits below.

WebSocket Example

The WebSocket support is particularly exciting, as it allows you to call server functions using the default Rust Stream trait from the futures crate, and have those streams send messages over websockets without you needing to know anything about that process. The API landed in a place that feels like a great extension of the "server function" abstraction in which you can make HTTP requests as if they were ordinary async calls. The websocket stuff doesn't integrate directly with Resources/SSR (which make more sense for one-shot things) but is really easy to use:

use server_fn::{codec::JsonEncoding, BoxedStream, ServerFnError, Websocket};

// The websocket protocol can be used on any server function that accepts and returns a [`BoxedStream`]
// with items that can be encoded by the input and output encoding generics.
//
// In this case, the input and output encodings are [`Json`] and [`Json`], respectively which requires
// the items to implement [`Serialize`] and [`Deserialize`].
#[server(protocol = Websocket<JsonEncoding, JsonEncoding>)]
async fn echo_websocket(
    input: BoxedStream<String, ServerFnError>,
) -> Result<BoxedStream<String, ServerFnError>, ServerFnError> {
    use futures::channel::mpsc;
    use futures::{SinkExt, StreamExt};
    let mut input = input; // FIXME :-) server fn fields should pass mut through to destructure

    // create a channel of outgoing websocket messages 
    // we'll return rx, so sending a message to tx will send a message to the client via the websocket
    let (mut tx, rx) = mpsc::channel(1);

    // spawn a task to listen to the input stream of messages coming in over the websocket 
    tokio::spawn(async move {
        while let Some(msg) = input.next().await {
            // do some work on each message, and then send our responses 
            tx.send(msg.map(|msg| msg.to_ascii_uppercase())).await;
        }
    });

    Ok(rx.into())
}

#[component]
pub fn App() -> impl IntoView {
    use futures::channel::mpsc;
    use futures::StreamExt;
    let (mut tx, rx) = mpsc::channel(1);
    let latest = RwSignal::new(None);

    // we'll only listen for websocket messages on the client
    if cfg!(feature = "hydrate") {
        spawn_local(async move {
            match echo_websocket(rx.into()).await {
                Ok(mut messages) => {
                    while let Some(msg) = messages.next().await {
                        latest.set(Some(msg));
                    }
                }
                Err(e) => leptos::logging::warn!("{e}"),
            }
        });
    }

    view! {
        <input type="text" on:input:target=move |ev| {
            tx.try_send(Ok(ev.target().value()));
        }/>
        <p>{latest}</p>
    }
}

What's Changed

  • fix: close Actix websocket stream when browser disconnects (closes #3865) by @gbj in #3866
  • Error boundary fixes by @gbj in #3870
  • Forward lint attributes used with #[component] macro by @sathish-pv in #3864
  • Complete the migration of examples to Tailwind 4 by @nnmm in #3861
  • fix: Use stabilized ClipboardEvent by @feathecutie in #3849
  • Added header generation method to BrowserResponse by @rakshith-ravi in #3873
  • Prevent ScopedFuture stopping owner cleanup by @zakstucke in #3863
  • feat: enhancing ByteStream error handling by @sabify in #3869
  • fix: send/receive websocket data by @sabify in #3848
  • feat(examples): add WebSocket example by @sabify in #3853
  • chore: put TextProp in the prelude (closes #3877) by @huuff in #3879
  • fix(examples): websocket example tests fail on latency by @sabify in #3880
  • fix: correctly calculate starting index for first new key (closes #3828) by @gbj in #3878
  • fix: remove event listeners from Suspense fallback during SSR (closes #3871) by @gbj in #3882

New Contributors

Full Changelog: v0.8.0-rc2...v0.8.0-rc3