Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/
lazy.rs

1//! Implements lazy fetching of components
2
3// A simple wrapper is easy to implement. This module exists to support message passing and more
4// involved logic
5
6use std::cell::RefCell;
7use std::future::Future;
8use std::rc::Rc;
9
10use crate::html::Scope;
11use crate::scheduler::Shared;
12use crate::suspense::Suspension;
13use crate::virtual_dom::VComp;
14use crate::{BaseComponent, Context};
15
16type ScopeRef<C> = Shared<Option<Scope<C>>>;
17
18#[derive(Debug)]
19struct CompVTableImpl<C: BaseComponent> {
20    /// The way to create a component from properties and a way to reference it later.
21    /// It is important that we return a structure that already captures the vtable to
22    /// the component's functionality (Mountable), so that the linker doesn't see that
23    /// the main module references C's BaseComponent impl.
24    wrap_html: fn(Rc<C::Properties>, ScopeRef<C>) -> VComp,
25}
26/// Component vtable for a component.
27///
28/// Return `LazyVTable::<YourComponent>::vtable()` from your implementation of
29/// [`LazyComponent::fetch`] after resolving it.
30pub struct LazyVTable<C: BaseComponent> {
31    imp: &'static CompVTableImpl<C>,
32}
33impl<C: BaseComponent> std::fmt::Debug for LazyVTable<C> {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("LazyVTable")
36            .field("vtable", &(self.imp as *const _))
37            .finish()
38    }
39}
40impl<C: BaseComponent> Clone for LazyVTable<C> {
41    fn clone(&self) -> Self {
42        *self
43    }
44}
45impl<C: BaseComponent> Copy for LazyVTable<C> {}
46
47impl<C: BaseComponent> LazyVTable<C> {
48    /// Returns the singleton vtable for a component.
49    ///
50    /// Return this from [`LazyComponent::fetch`] for your lazy component.
51    pub fn vtable() -> LazyVTable<C> {
52        fn wrap_html<C: BaseComponent>(
53            props: Rc<C::Properties>,
54            scope_ref: Shared<Option<Scope<C>>>,
55        ) -> VComp {
56            VComp::new_with_ref(props, scope_ref)
57        }
58        LazyVTable {
59            imp: &const {
60                CompVTableImpl {
61                    wrap_html: wrap_html::<C>,
62                }
63            },
64        }
65    }
66}
67/// Implement this trait to support lazily loading a component.
68///
69/// Used in conjunction with the [`Lazy`] component.
70pub trait LazyComponent: 'static {
71    /// The component that is lazily being fetched
72    type Underlying: BaseComponent;
73    /// Fetch the component's impl
74    fn fetch() -> impl Future<Output = LazyVTable<Self::Underlying>> + Send;
75}
76
77enum LazyState<C: BaseComponent> {
78    Pending(Suspension),
79    #[allow(unused)] // Only constructed with feature csr or ssr
80    Created(LazyVTable<C>),
81}
82
83impl<C: BaseComponent> std::fmt::Debug for LazyState<C> {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        match self {
86            Self::Pending(arg0) => f.debug_tuple("Pending").field(arg0).finish(),
87            Self::Created(arg0) => f.debug_tuple("Created").field(arg0).finish(),
88        }
89    }
90}
91
92/// Wrapper for a lazily fetched component
93///
94/// This component suspends as long as the underlying component is still being fetched,
95/// then behaves as the underlying component itself.
96pub struct Lazy<C: LazyComponent> {
97    inner_scope: ScopeRef<C::Underlying>,
98    // messages sent to the component before the inner_scope is set are buffered
99    message_buffer: RefCell<Vec<<C::Underlying as BaseComponent>::Message>>,
100    state: Shared<LazyState<C::Underlying>>,
101}
102
103impl<C: LazyComponent> std::fmt::Debug for Lazy<C> {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        f.debug_struct("Lazy")
106            .field("inner_scope", &self.inner_scope)
107            .field("message_buffer", &"...")
108            .field("state", &self.state)
109            .finish()
110    }
111}
112
113impl<C: LazyComponent> BaseComponent for Lazy<C> {
114    type Message = <C::Underlying as BaseComponent>::Message;
115    type Properties = <C::Underlying as BaseComponent>::Properties;
116
117    fn create(ctx: &Context<Self>) -> Self {
118        let host_scope = ctx.link().clone();
119        let state = Rc::<RefCell<_>>::new_cyclic(move |state| {
120            let state = state.clone();
121            let suspension = Suspension::from_future(async move {
122                // Ignore error in case receiver was dropped
123                let vtable = C::fetch().await;
124                #[cfg(any(feature = "ssr", feature = "csr"))]
125                if let Some(state) = state.upgrade() {
126                    *state.borrow_mut() = LazyState::Created(vtable);
127                    // force a re-render with this new state (without a message exchange)
128                    host_scope.schedule_render();
129                }
130                let _ = (host_scope, state, vtable);
131            });
132            RefCell::new(LazyState::Pending(suspension))
133        });
134        Self {
135            inner_scope: Rc::default(),
136            message_buffer: RefCell::default(),
137            state,
138        }
139    }
140
141    fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
142        if let Some(inner) = self.inner_scope.borrow().as_ref() {
143            inner.send_message(msg);
144        } else {
145            self.message_buffer.borrow_mut().push(msg);
146        }
147        false
148    }
149
150    fn changed(&mut self, _: &Context<Self>, _old_props: &Self::Properties) -> bool {
151        true
152    }
153
154    fn view(&self, ctx: &Context<Self>) -> crate::HtmlResult {
155        match &*self.state.borrow() {
156            LazyState::Pending(suspension) => Err(suspension.clone().into()),
157            LazyState::Created(lazy_vtable) => {
158                let comp =
159                    (lazy_vtable.imp.wrap_html)(ctx.rc_props().clone(), self.inner_scope.clone());
160                Ok(comp.into())
161            }
162        }
163    }
164
165    fn rendered(&mut self, _: &Context<Self>, first_render: bool) {
166        if first_render {
167            let inner = self.inner_scope.borrow();
168            let inner = inner.as_ref().expect("lazy component to have rendered");
169            inner.send_message_batch(std::mem::take(&mut *self.message_buffer.borrow_mut()));
170        } else {
171            #[cfg(debug_assertions)]
172            assert!(
173                self.message_buffer.borrow().is_empty(),
174                "no message in buffer after first render"
175            );
176        }
177    }
178
179    fn destroy(&mut self, _: &Context<Self>) {}
180
181    fn prepare_state(&self) -> Option<String> {
182        None
183    }
184}
185
186/// Make a component accessible as a lazily loaded component in a separate wasm module
187#[doc(hidden)]
188#[macro_export]
189macro_rules! __declare_lazy_component {
190    ($comp:ty as $lazy_name:ident in $module:ident) => {
191        struct Proxy;
192        impl $crate::lazy::LazyComponent for Proxy {
193            type Underlying = $comp;
194
195            async fn fetch() -> $crate::lazy::LazyVTable<Self::Underlying> {
196                #[$crate::lazy::wasm_split::wasm_split($module, wasm_split_path = $crate::lazy::wasm_split)]
197                fn split_fetch() -> $crate::lazy::LazyVTable<$comp> {
198                    $crate::lazy::LazyVTable::<$comp>::vtable()
199                }
200                struct F(
201                    ::std::option::Option<
202                        ::std::pin::Pin<
203                            ::std::boxed::Box<
204                                dyn ::std::future::Future<Output = $crate::lazy::LazyVTable<$comp>>
205                                    + ::std::marker::Send,
206                            >,
207                        >,
208                    >,
209                );
210                impl Future for F {
211                    type Output = $crate::lazy::LazyVTable<$comp>;
212
213                    fn poll(
214                        mut self: ::std::pin::Pin<&mut Self>,
215                        cx: &mut ::std::task::Context<'_>,
216                    ) -> ::std::task::Poll<Self::Output> {
217                        self.0
218                            .get_or_insert_with(|| ::std::boxed::Box::pin(split_fetch()))
219                            .as_mut()
220                            .poll(cx)
221                    }
222                }
223                static CACHE: $crate::lazy::LazyCell<$crate::lazy::LazyVTable<$comp>, F> =
224                    $crate::lazy::LazyCell::new(F(None));
225                *::std::pin::Pin::static_ref(&CACHE).await.get_ref()
226            }
227        }
228        type $lazy_name = $crate::lazy::Lazy<Proxy>;
229    };
230}
231#[doc(hidden)]
232pub use ::async_once_cell::Lazy as LazyCell;
233#[doc(hidden)]
234pub use ::wasm_split_helpers as wasm_split;
235
236pub use crate::__declare_lazy_component as declare_lazy_component;