leptos_sync_ssr/
lib.rs

1//! This crate provides helpers to synchronize the access of Leptos resources
2//! during server-side rendering (SSR) within the Leptos frameworks.  This is
3//! achieved by providing locking primitives, which are then built into the
4//! provided components and signals, such that when they are used together in
5//! the prescribed manner, it would allow the affected resources to resolve in
6//! the expected order.  This enables components defined earlier in the view
7//! tree to wait on data that may or may not be provided by components defined
8//! later down the view tree, ultimately ensuring that hydration would happen
9//! correctly.
10//!
11//! ## Use case
12//!
13//! A fairly common user interface design pattern known as [portlets](
14//! https://en.wikipedia.org/wiki/Portlet) may be implemented in Leptos
15//! using a struct with data fields to be rendered be available in through a
16//! reactive signal behind a context, with the renderer being a component
17//! that would reactively read the value from that reactive signal such that
18//! it may be updated as required.  This is not a problem under client-side
19//! rendering (CSR) for Leptos, but for SSR with hydration, this is a whole
20//! other story.
21//!
22//! If the rendering component is situated in the view tree before the
23//! component that may write to it, as in the case of a typical "breadcrumb"
24//! component, this creates the complication where the signal may not be set
25//! in time by the time the breadcrumb component was streamed out to the
26//! client with the default data.  Furthermore, the hydration script may
27//! contain the expected data, and when used to hydrate the rendered markup
28//! which used the default data from earlier, this mismatch wil result in
29//! hydration error.
30//!
31//! There are multiple solutions to this problem, and this crate provides a
32//! number of them, when combined together, addresses the rendering and
33//! hydration issues, without bringing in new problems that the individual
34//! solution would bring when used in isolation.
35//!
36//! # Example
37//!
38//! This package does in fact implement [`portlets`](crate::portlet) as a
39//! module, where a minimum amount of user code is required to set up the
40//! scenario as described above.  The following is an example on how that
41//! module may be used:
42//!
43//! ```
44//! use leptos::prelude::*;
45//! use leptos_router::{
46//!     components::{Route, Router, Routes},
47//!     path,
48//! };
49//! use leptos_sync_ssr::{component::SyncSsrSignal, portlet::PortletCtx};
50//! #
51//! # #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]
52//! # struct Blog;
53//!
54//! #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]
55//! struct Breadcrumbs {
56//!     // fields...
57//! }
58//!
59//! // The main `App` - note how the `SyncSsrSignal` is placed.
60//! #[component]
61//! fn App() -> impl IntoView {
62//!     view! {
63//!         <Router>
64//!             <SyncSsrSignal setup=|| {
65//!                 <PortletCtx<Breadcrumbs>>::provide();
66//!             }>
67//!                 <header>
68//!                     // Note how the breadcrumb comes before anything that
69//!                     // might set the signal for it.
70//!                     <ShowBreadcrumbs/>
71//!                 </header>
72//!                 <article>
73//!                     <Routes fallback=|| ()>
74//!                         <Route path=path!("") view=HomePage/>
75//!                         <Route path=path!("/blog/:id") view=BlogView/>
76//!                         // plus other routes
77//!                     </Routes>
78//!                 </article>
79//!             </SyncSsrSignal>
80//!         </Router>
81//!     }
82//! }
83//!
84//! // This implements the rendering for `BreadCrumbs`
85//! impl IntoRender for Breadcrumbs {
86//!     type Output = AnyView;
87//!
88//!     fn into_render(self) -> Self::Output {
89//!         view! {
90//!             todo!()
91//!         }
92//!         .into_any()
93//!     }
94//! }
95//!
96//! #[component]
97//! fn ShowBreadcrumbs() -> impl IntoView {
98//!     <PortletCtx<Breadcrumbs>>::render()
99//! }
100//!
101//! // This renders the blog, but also sets the breadcrumbs as appropriate.
102//! #[component]
103//! fn BlogView() -> impl IntoView {
104//!     // assuming this was provided/defined elsewhere
105//!     let blog = expect_context::<ArcResource<Result<Blog, ServerFnError>>>();
106//!     let nav_ctx = expect_context::<PortletCtx<Breadcrumbs>>();
107//!
108//!     view! {
109//!         // This ensures `PortletCtx<Breadcrumbs>` is updated with data provided by
110//!         // `authors`.
111//!         {nav_ctx.set_with(move || {
112//!             let blog = blog.clone();
113//!             async move {
114//!                 blog.await
115//!                     // TODO conversion of the blog info to `Breadcrumbs` type
116//!                     .map(|blog| todo!())
117//!                     .ok()
118//!                 // Once this returns, the breadcrumb will render on SSR.
119//!             }
120//!         })}
121//!         <div>
122//!             // Other components/elements.
123//!         </div>
124//!     }
125//! }
126//!
127//! #[component]
128//! fn HomePage() -> impl IntoView {
129//!     // Note how the homepage doesn't set breadcrumbs - it then shouldn't
130//!     // show the breadcrumbs, and that lack of write signal creation/use
131//!     // wouldn't cause a deadlock.
132//!     view! {
133//!         todo!()
134//!     }
135//! }
136//! ```
137//!
138//! For a more complete example on something similar to above, [`nav_portlet`](
139//! https://github.com/metatoaster/leptos_sync_ssr/tree/main/example/nav_portlet)
140//! provides a great demonstration on how this might work in action, and
141//! that there is also an alternative implementation called [`nav_portlet_alt`](
142//! https://github.com/metatoaster/leptos_sync_ssr/tree/main/example/nav_portlet_alt)
143//! that use the other set of primitives to achieve a similar effect.
144//! The documentation under individual modules and types also include further
145//! explanation, usage examples, and does go more into the implementation details,
146//! please check them out.
147//!
148//! # Feature Flags
149#![cfg_attr(
150    feature = "document-features",
151    cfg_attr(doc, doc = ::document_features::document_features!())
152)]
153
154pub mod component;
155#[cfg(feature = "portlet")]
156pub mod portlet;
157mod ready;
158pub mod signal;
159
160#[cfg(test)]
161mod tests;
162
163pub use ready::{
164    CoReady, CoReadyCoordinator, CoReadySubscription, Ready, ReadyHandle, ReadySubscription,
165};