1use std::any::{Any, TypeId};
4use std::fmt;
5use std::rc::Rc;
6
7#[cfg(feature = "ssr")]
8use futures::future::{FutureExt, LocalBoxFuture};
9#[cfg(feature = "csr")]
10use web_sys::Element;
11
12use super::Key;
13#[cfg(feature = "hydration")]
14use crate::dom_bundle::Fragment;
15#[cfg(feature = "csr")]
16use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
17use crate::html::BaseComponent;
18#[cfg(feature = "csr")]
19use crate::html::Scoped;
20#[cfg(any(feature = "ssr", feature = "csr"))]
21use crate::html::{AnyScope, Scope};
22#[cfg(feature = "ssr")]
23use crate::{feat_ssr::VTagKind, platform::fmt::BufWriter};
24
25pub struct VComp {
27 pub(crate) type_id: TypeId,
28 pub(crate) mountable: Box<dyn Mountable>,
29 pub(crate) key: Option<Key>,
30 _marker: u32,
32}
33
34impl fmt::Debug for VComp {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.debug_struct("VComp")
37 .field("type_id", &self.type_id)
38 .field("mountable", &"..")
39 .field("key", &self.key)
40 .finish()
41 }
42}
43
44impl Clone for VComp {
45 fn clone(&self) -> Self {
46 Self {
47 type_id: self.type_id,
48 mountable: self.mountable.copy(),
49 key: self.key.clone(),
50 _marker: 0,
51 }
52 }
53}
54
55pub(crate) trait Mountable {
56 fn copy(&self) -> Box<dyn Mountable>;
57
58 fn mountable_eq(&self, rhs: &dyn Mountable) -> bool;
59 fn as_any(&self) -> &dyn Any;
60
61 #[cfg(feature = "csr")]
62 fn mount(
63 self: Box<Self>,
64 root: &BSubtree,
65 parent_scope: &AnyScope,
66 parent: Element,
67 slot: DomSlot,
68 internal_ref: DynamicDomSlot,
69 ) -> Box<dyn Scoped>;
70
71 #[cfg(feature = "csr")]
72 fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot);
73
74 #[cfg(feature = "ssr")]
75 fn render_into_stream<'a>(
76 &'a self,
77 w: &'a mut BufWriter,
78 parent_scope: &'a AnyScope,
79 hydratable: bool,
80 parent_vtag_kind: VTagKind,
81 ) -> LocalBoxFuture<'a, ()>;
82
83 #[cfg(feature = "hydration")]
84 fn hydrate(
85 self: Box<Self>,
86 root: BSubtree,
87 parent_scope: &AnyScope,
88 parent: Element,
89 internal_ref: DynamicDomSlot,
90 fragment: &mut Fragment,
91 ) -> Box<dyn Scoped>;
92}
93
94pub(crate) struct PropsWrapper<COMP: BaseComponent> {
95 props: Rc<COMP::Properties>,
96}
97
98impl<COMP: BaseComponent> PropsWrapper<COMP> {
99 pub fn new(props: Rc<COMP::Properties>) -> Self {
100 Self { props }
101 }
102}
103
104impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
105 fn copy(&self) -> Box<dyn Mountable> {
106 let wrapper: PropsWrapper<COMP> = PropsWrapper {
107 props: Rc::clone(&self.props),
108 };
109 Box::new(wrapper)
110 }
111
112 fn as_any(&self) -> &dyn Any {
113 self
114 }
115
116 fn mountable_eq(&self, rhs: &dyn Mountable) -> bool {
117 rhs.as_any()
118 .downcast_ref::<Self>()
119 .map(|rhs| self.props == rhs.props)
120 .unwrap_or(false)
121 }
122
123 #[cfg(feature = "csr")]
124 fn mount(
125 self: Box<Self>,
126 root: &BSubtree,
127 parent_scope: &AnyScope,
128 parent: Element,
129 slot: DomSlot,
130 internal_ref: DynamicDomSlot,
131 ) -> Box<dyn Scoped> {
132 let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
133 scope.mount_in_place(root.clone(), parent, slot, internal_ref, self.props);
134
135 Box::new(scope)
136 }
137
138 #[cfg(feature = "csr")]
139 fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot) {
140 let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
141 scope.reuse(self.props, slot);
142 }
143
144 #[cfg(feature = "ssr")]
145 fn render_into_stream<'a>(
146 &'a self,
147 w: &'a mut BufWriter,
148 parent_scope: &'a AnyScope,
149 hydratable: bool,
150 parent_vtag_kind: VTagKind,
151 ) -> LocalBoxFuture<'a, ()> {
152 let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
153
154 async move {
155 scope
156 .render_into_stream(w, self.props.clone(), hydratable, parent_vtag_kind)
157 .await;
158 }
159 .boxed_local()
160 }
161
162 #[cfg(feature = "hydration")]
163 fn hydrate(
164 self: Box<Self>,
165 root: BSubtree,
166 parent_scope: &AnyScope,
167 parent: Element,
168 internal_ref: DynamicDomSlot,
169 fragment: &mut Fragment,
170 ) -> Box<dyn Scoped> {
171 let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
172 scope.hydrate_in_place(root, parent, fragment, internal_ref, self.props);
173
174 Box::new(scope)
175 }
176}
177
178pub struct VChild<COMP: BaseComponent> {
180 pub props: Rc<COMP::Properties>,
182 key: Option<Key>,
184}
185
186impl<COMP: BaseComponent> implicit_clone::ImplicitClone for VChild<COMP> {}
187
188impl<COMP: BaseComponent> Clone for VChild<COMP> {
189 fn clone(&self) -> Self {
190 VChild {
191 props: Rc::clone(&self.props),
192 key: self.key.clone(),
193 }
194 }
195}
196
197impl<COMP: BaseComponent> PartialEq for VChild<COMP>
198where
199 COMP::Properties: PartialEq,
200{
201 fn eq(&self, other: &VChild<COMP>) -> bool {
202 self.props == other.props
203 }
204}
205
206impl<COMP> VChild<COMP>
207where
208 COMP: BaseComponent,
209{
210 pub fn new(props: COMP::Properties, key: Option<Key>) -> Self {
212 Self {
213 props: Rc::new(props),
214 key,
215 }
216 }
217}
218
219impl<COMP> From<VChild<COMP>> for VComp
220where
221 COMP: BaseComponent,
222{
223 fn from(vchild: VChild<COMP>) -> Self {
224 VComp::new::<COMP>(vchild.props, vchild.key)
225 }
226}
227
228impl VComp {
229 pub fn new<COMP>(props: Rc<COMP::Properties>, key: Option<Key>) -> Self
231 where
232 COMP: BaseComponent,
233 {
234 VComp {
235 type_id: TypeId::of::<COMP>(),
236 mountable: Box::new(PropsWrapper::<COMP>::new(props)),
237 key,
238 _marker: 0,
239 }
240 }
241}
242
243impl PartialEq for VComp {
244 fn eq(&self, other: &VComp) -> bool {
245 self.key == other.key
246 && self.type_id == other.type_id
247 && self.mountable.mountable_eq(other.mountable.as_ref())
248 }
249}
250
251impl<COMP: BaseComponent> fmt::Debug for VChild<COMP> {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 f.write_str("VChild<_>")
254 }
255}
256
257#[cfg(feature = "ssr")]
258mod feat_ssr {
259 use super::*;
260 use crate::html::AnyScope;
261
262 impl VComp {
263 #[inline]
264 pub(crate) async fn render_into_stream(
265 &self,
266 w: &mut BufWriter,
267 parent_scope: &AnyScope,
268 hydratable: bool,
269 parent_vtag_kind: VTagKind,
270 ) {
271 self.mountable
272 .as_ref()
273 .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
274 .await;
275 }
276 }
277}
278
279#[cfg(all(test, not(target_arch = "wasm32"), feature = "ssr"))]
280mod ssr_tests {
281 use tokio::test;
282
283 use crate::prelude::*;
284 use crate::ServerRenderer;
285
286 #[test]
287 async fn test_props() {
288 #[derive(PartialEq, Properties, Debug)]
289 struct ChildProps {
290 name: String,
291 }
292
293 #[function_component]
294 fn Child(props: &ChildProps) -> Html {
295 html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
296 }
297
298 #[function_component]
299 fn Comp() -> Html {
300 html! {
301 <div>
302 <Child name="Jane" />
303 <Child name="John" />
304 <Child name="Josh" />
305 </div>
306 }
307 }
308
309 let s = ServerRenderer::<Comp>::new()
310 .hydratable(false)
311 .render()
312 .await;
313
314 assert_eq!(
315 s,
316 "<div><div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div></div>"
317 );
318 }
319}