1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
use std::pin::Pin;
use std::collections::HashMap;
use std::future::Future;
use futures::future::join_all;
pub trait Component {
fn render(self: Box<Self>) -> RenderNode;
}
pub enum DoctypeElem {
Word(&'static str),
String(&'static str),
}
pub enum RenderNode {
Doctype(Vec<DoctypeElem>),
Suspense {
fallback: Box<RenderNode>,
children: Pin<Box<dyn Future<Output = Vec<RenderNode>>>>
},
Component(Box<dyn Component>),
Element {
name: String,
attributes: HashMap<String, String>,
children: Vec<RenderNode>
},
Fragment {
children: Vec<RenderNode>
},
TextNode {
content: String,
},
Null,
}
const VOID_TAGS: [&str; 14] = [
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
"track", "wbr",
];
impl RenderNode {
pub(crate) fn render_to_string(self) -> Pin<Box<dyn Future<Output = String>>> {
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::<Vec<_>>().join(" ").clone();
Box::pin(async move {
format!("<!doctype {}>", 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)| {
let corrected_key = key.replace("_", "-");
format!(" {corrected_key}=\"{value}\"")
})
.collect::<Vec<_>>()
.join("");
Box::pin(async move {
let rendered_children = join_all(children.into_iter()
.map(|child| child.render_to_string())
.collect::<Vec<_>>()).await.join("");
let is_void = VOID_TAGS.iter().any(|&s| s == name);
let has_children = rendered_children.trim() != "";
if has_children && is_void {
eprintln!("WARN: <{name}/> is a void tag, and should not have children");
}
if !has_children && is_void {
format!("<{name}{text_attributes}/>")
} else {
format!("<{name}{text_attributes}>{rendered_children}</{name}>")
}
})
},
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<RenderNode>;
}
macro_rules! impl_str {
($t:ty) => {
impl IntoRender for $t {
fn into_render(self) -> Vec<RenderNode> {
vec![RenderNode::TextNode { content: String::from(self) }]
}
}
};
}
impl_str!(String);
impl_str!(&str);
impl_str!(std::borrow::Cow<'_,str>);
impl IntoRender for Vec<RenderNode> {
fn into_render(self) -> Vec<RenderNode> {
self
}
}
impl IntoRender for RenderNode {
fn into_render(self) -> Vec<RenderNode> {
vec![self]
}
}
|