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> Clone for VChild<COMP> {
187 fn clone(&self) -> Self {
188 VChild {
189 props: Rc::clone(&self.props),
190 key: self.key.clone(),
191 }
192 }
193}
194
195impl<COMP: BaseComponent> PartialEq for VChild<COMP>
196where
197 COMP::Properties: PartialEq,
198{
199 fn eq(&self, other: &VChild<COMP>) -> bool {
200 self.props == other.props
201 }
202}
203
204impl<COMP> VChild<COMP>
205where
206 COMP: BaseComponent,
207{
208 pub fn new(props: COMP::Properties, key: Option<Key>) -> Self {
210 Self {
211 props: Rc::new(props),
212 key,
213 }
214 }
215}
216
217impl<COMP> From<VChild<COMP>> for VComp
218where
219 COMP: BaseComponent,
220{
221 fn from(vchild: VChild<COMP>) -> Self {
222 VComp::new::<COMP>(vchild.props, vchild.key)
223 }
224}
225
226impl VComp {
227 pub fn new<COMP>(props: Rc<COMP::Properties>, key: Option<Key>) -> Self
229 where
230 COMP: BaseComponent,
231 {
232 VComp {
233 type_id: TypeId::of::<COMP>(),
234 mountable: Box::new(PropsWrapper::<COMP>::new(props)),
235 key,
236 _marker: 0,
237 }
238 }
239}
240
241impl PartialEq for VComp {
242 fn eq(&self, other: &VComp) -> bool {
243 self.key == other.key
244 && self.type_id == other.type_id
245 && self.mountable.mountable_eq(other.mountable.as_ref())
246 }
247}
248
249impl<COMP: BaseComponent> fmt::Debug for VChild<COMP> {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 f.write_str("VChild<_>")
252 }
253}
254
255#[cfg(feature = "ssr")]
256mod feat_ssr {
257 use super::*;
258 use crate::html::AnyScope;
259
260 impl VComp {
261 #[inline]
262 pub(crate) async fn render_into_stream(
263 &self,
264 w: &mut BufWriter,
265 parent_scope: &AnyScope,
266 hydratable: bool,
267 parent_vtag_kind: VTagKind,
268 ) {
269 self.mountable
270 .as_ref()
271 .render_into_stream(w, parent_scope, hydratable, parent_vtag_kind)
272 .await;
273 }
274 }
275}
276
277#[cfg(all(test, not(target_arch = "wasm32"), feature = "ssr"))]
278mod ssr_tests {
279 use tokio::test;
280
281 use crate::prelude::*;
282 use crate::ServerRenderer;
283
284 #[test]
285 async fn test_props() {
286 #[derive(PartialEq, Properties, Debug)]
287 struct ChildProps {
288 name: String,
289 }
290
291 #[function_component]
292 fn Child(props: &ChildProps) -> Html {
293 html! { <div>{"Hello, "}{&props.name}{"!"}</div> }
294 }
295
296 #[function_component]
297 fn Comp() -> Html {
298 html! {
299 <div>
300 <Child name="Jane" />
301 <Child name="John" />
302 <Child name="Josh" />
303 </div>
304 }
305 }
306
307 let s = ServerRenderer::<Comp>::new()
308 .hydratable(false)
309 .render()
310 .await;
311
312 assert_eq!(
313 s,
314 "<div><div>Hello, Jane!</div><div>Hello, John!</div><div>Hello, Josh!</div></div>"
315 );
316 }
317}