1use 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 wrap_html: fn(Rc<C::Properties>, ScopeRef<C>) -> VComp,
25}
26pub 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 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}
67pub trait LazyComponent: 'static {
71 type Underlying: BaseComponent;
73 fn fetch() -> impl Future<Output = LazyVTable<Self::Underlying>> + Send;
75}
76
77enum LazyState<C: BaseComponent> {
78 Pending(Suspension),
79 #[allow(unused)] 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
92pub struct Lazy<C: LazyComponent> {
97 inner_scope: ScopeRef<C::Underlying>,
98 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 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 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#[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;