Open futursolo opened 2 years ago
The previously proposed StatefulSwitch
approach has some limitations:
To avoid these limitations, a new crate, tentatively named yew-link
, is introduced.
LinkedState
A LinkedState
trait is added to declare a linked state:
trait LinkedState: Serializable + Deserializable {
#[cfg(feature = "ssr")]
type Context: 'static;
type Input: 'static + PartialEq + Serializable + Deserializable;
type Error: 'static + Error + Serializable + Deserializable;
#[cfg(feature = "ssr")]
fn get_linked_state(&self, ctx: &Self::Context, input: &Self::Input) -> Box<dyn Future<Output = Result<Self, Self::Error>>>;
}
// We need a #[linked_state] so that it automatically:
// - Adds `#[async_trait(?Send)]`
// - Strips `Context` and `get_linked_state ` from CSR bundle
#[linked_state]
impl LinkedState for MyState {
type Context = ServerContext;
type Input = ();
type Error = Infallible;
async fn get_linked_state(&self, ctx: &Self::Context, input: &Self::Input) -> Result<Self, Self::Error> {
todo!("implementation omitted")
}
}
RemoteLinkProvider
This is a context provider that links linked states to a remote endpoint.
#[function_component]
fn App() -> Html {
html! {
<RemoteLinkProvider endpoint="https://api.my-server.com/my-stateful-route-endpoint">
// ... children
</RemoteLinkProvider>
}
}
LocalLinkProvider
Similar to RemoteLinkProvider
, but resolves linked states locally. This is usually used in SSR.
LinkedStateService
A tower service to create an endpoint to resolve linked states.
use_linked_state
To link a state, developers can use the use_linked_state
hook.
This can be used in any component that is a child of a LinkProvider
.
It is not limited at route level.
#[hook]
pub fn use_linked_state<T>(input: T::Input) -> SuspensionResult<Result<T, T::Error>>
where
T: LinkedState + 'static
{
todo!("implementation omitted")
}
The initial linked state is carried to the client side with use_prepared_state
/ use_transitive_state
and any subsequent states are fetched by the LinkProvider.
#[derive(Properties, PartialEq)]
pub struct MyCompProps {
pub page_id: u32,
}
#[function_component]
pub fn MyComp(props: &MyCompProps) -> HtmlResult {
let page_content = use_linked_state::<PageState>(props.page_id)?.unwrap();
<div>{page_content.inner}</div>
}
This issue outlines a proposal to add a set of hooks that can be used to carry states / artifacts created during the server-side rendering to the client side for hydration and subsequent rendering and a new crate that links a state to the server.
yew
These hooks are usually not used by end-users but:
use_prepared_state
usage:
This hook executes an async closure that creates a state from deps and returns a
SuspensionResult<Option<Rc<ReturnType>>>
.(I am aware that async closure is not stable, but proc macro can rewrite it. We need to extract the return type from the closure for client side rendering as the closure itself is not present in the client side bundle.)
Both the return type and deps are sent to client-side with serde. (I am currently leaning towards
bincode
as its smaller thanserde_json
and used byyew-agent
.)During client-side rendering hydration, it will return
Ok(None)
if:The deps is needed so that if the deps used to generate state changes it can automatically invalidate the server-side rendered state.
Err(Suspension)
Err(Suspension)
Ok(Some(Rc<ReturnType>))
deps == server_side_deps
)Ok(Some(Rc<ReturnType>))
deps != server_side_deps
)Ok(None)
This is a macro-based hook so that the content inside the closure can be stripped from client side rendering bundle automatically.
This can be used to collect a state created during the server-side rendering and ensures that during the hydration, the application will receive the same value.
use_transitive_state
Similar to
use_prepared_state
, but the closure is run after the server-side rendering of current component is finished but before destroy (effect stage). During server-side rendering, the component never sees the state (always returnOk(None)
).This is used to carry cache of an http client or states for a state management library so that they can collect all states created during the server-side rendering to be sent to the client side for hydration after its content is created.
Err(Suspension)
deps == server_side_deps
)Ok(Some(Rc<ReturnType>))
deps != server_side_deps
)Ok(None)
yew-router
getServerSideProps
in Next.js is opinionated about the server environment (node or edge), protocol and requires an http client.I wish a client agnostic, protocol agnostic
getServerSideProps
that is available in all supported rust platform can be established here. However, we may provide a reference implementation about how it is handled. (e.g.: tower service + gloo-net + bincode)StatefulRoutable
A StatefulRoutable trait is added:
Users can then implement a server that sends the route state to the client side.
We can also provide a tower service to help users to implement an endpoint.
StatefulSwitch
A stateful routable is combined with a StatefulSwitch (should be declared inside a ):
Caveats
In this implementation, for all StatefulRoutable variants, the State type is shared.
However, it may be better to map each Routable variant to a different state type. i.e.:
Route::MyAccount
->MyAccountState
,Route::Article
->ArticleState
I am not sure whether this is possible with current Rust typing system.