use std::pin::Pin; use std::collections::HashMap; use std::future::Future; use futures::future::join_all; pub trait Component { fn render(self: Box) -> RenderNode; } pub enum DoctypeElem { Word(&'static str), String(&'static str), } pub enum RenderNode { Doctype(Vec), Suspense { fallback: Box, children: Pin>>> }, Component(Box), Element { name: String, attributes: HashMap, children: Vec }, Fragment { children: Vec }, TextNode { content: String, }, Null, } impl RenderNode { pub(crate) fn render_to_string(self) -> Pin>> { match self { RenderNode::Doctype(elements) => { let strings = elements.iter().map(|elem| match elem { DoctypeElem::Word(word) => word.to_string(), DoctypeElem::String(string) => format!("\"{}\"", string), }).collect::>().join(" ").clone(); Box::pin(async move { format!("", strings) }) }, RenderNode::Component(component) => { let result_root = component.render(); Box::pin(async move { result_root.render_to_string().await }) }, RenderNode::Suspense {fallback: _, children} => { Box::pin(async move { join_all(children.await.into_iter() .map(|child| child.render_to_string())).await .join("") }) }, RenderNode::Element { name, attributes, children } => { let text_attributes = attributes.into_iter() .map(|(key, value)| format!(" {key}=\"{value}\"")) .collect::>().join(""); Box::pin(async move { let rendered_children = join_all(children.into_iter() .map(|child| child.render_to_string()) .collect::>()).await.join(""); format!("<{name}{text_attributes}>{rendered_children}") }) }, RenderNode::Fragment { children } => { Box::pin(async move { join_all(children.into_iter() .map(|child| child.render_to_string())).await .join("") }) } RenderNode::TextNode { content } => Box::pin(async move { content }), RenderNode::Null => Box::pin(async move { "".to_string() }), } } } pub trait IntoRender { fn into_render(self) -> Vec; } macro_rules! impl_str { ($t:ty) => { impl IntoRender for $t { fn into_render(self) -> Vec { vec![RenderNode::TextNode { content: String::from(self) }] } } }; } impl_str!(String); impl_str!(&str); impl_str!(std::borrow::Cow<'_,str>); impl IntoRender for Vec { fn into_render(self) -> Vec { self } }