glory_hot_reload/
node.rs

1use crate::parsing::is_component_node;
2use anyhow::Result;
3use quote::ToTokens;
4use rstml::node::{Node, NodeAttribute};
5use serde::{Deserialize, Serialize};
6
7// A lightweight virtual DOM structure we can use to hold
8// the state of a Glory view macro template. This is because
9// `syn` types are `!Send` so we can't store them as we might like.
10// This is only used to diff view macros for hot reloading so it's very minimal
11// and ignores many of the data types.
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub enum LNode {
14    Fragment(Vec<LNode>),
15    Text(String),
16    Element {
17        name: String,
18        attrs: Vec<(String, LAttributeValue)>,
19        children: Vec<LNode>,
20    },
21    // don't need anything; skipped during patching because it should
22    // contain its own view macros
23    Component {
24        name: String,
25        props: Vec<(String, String)>,
26        children: Vec<LNode>,
27    },
28    DynChild(String),
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
32pub enum LAttributeValue {
33    Boolean,
34    Static(String),
35    // safely ignored
36    Dynamic,
37    Noop,
38}
39
40impl LNode {
41    pub fn parse_view(nodes: Vec<Node>) -> Result<LNode> {
42        let mut out = Vec::new();
43        for node in nodes {
44            LNode::parse_node(node, &mut out)?;
45        }
46        if out.len() == 1 {
47            Ok(out.pop().unwrap())
48        } else {
49            Ok(LNode::Fragment(out))
50        }
51    }
52
53    pub fn parse_node(node: Node, views: &mut Vec<LNode>) -> Result<()> {
54        match node {
55            Node::Fragment(frag) => {
56                for child in frag.children {
57                    LNode::parse_node(child, views)?;
58                }
59            }
60            Node::Text(text) => {
61                views.push(LNode::Text(text.value_string()));
62            }
63            Node::Block(block) => {
64                let code = block.into_token_stream();
65                let code = code.to_string();
66                views.push(LNode::DynChild(code));
67            }
68            Node::Element(el) => {
69                if is_component_node(&el) {
70                    let name = el.name().to_string();
71                    let mut children = Vec::new();
72                    for child in el.children {
73                        LNode::parse_node(child, &mut children)?;
74                    }
75                    views.push(LNode::Component {
76                        name,
77                        props: el
78                            .open_tag
79                            .attributes
80                            .into_iter()
81                            .filter_map(|attr| match attr {
82                                NodeAttribute::Attribute(attr) => Some((attr.key.to_string(), format!("{:#?}", attr.value()))),
83                                _ => None,
84                            })
85                            .collect(),
86                        children,
87                    });
88                } else {
89                    let name = el.name().to_string();
90                    let mut attrs = Vec::new();
91
92                    for attr in el.open_tag.attributes {
93                        if let NodeAttribute::Attribute(attr) = attr {
94                            let name = attr.key.to_string();
95                            if let Some(value) = attr.value_literal_string() {
96                                attrs.push((name, LAttributeValue::Static(value)));
97                            } else {
98                                attrs.push((name, LAttributeValue::Dynamic));
99                            }
100                        }
101                    }
102
103                    let mut children = Vec::new();
104                    for child in el.children {
105                        LNode::parse_node(child, &mut children)?;
106                    }
107
108                    views.push(LNode::Element { name, attrs, children });
109                }
110            }
111            _ => {}
112        }
113        Ok(())
114    }
115
116    pub fn to_html(&self) -> String {
117        match self {
118            LNode::Fragment(frag) => frag.iter().map(LNode::to_html).collect(),
119            LNode::Text(text) => text.to_owned(),
120            LNode::Component { name, .. } => format!(
121                "<!--<{name}>--><pre>&lt;{name}/&gt; will load once Rust code \
122                 has been compiled.</pre><!--</{name}>-->"
123            ),
124            LNode::DynChild(_) => "<!--<DynChild>--><pre>Dynamic content will \
125                                   load once Rust code has been \
126                                   compiled.</pre><!--</DynChild>-->"
127                .to_string(),
128            LNode::Element { name, attrs, children } => {
129                // this is naughty, but the browsers are tough and can handle it
130                // I wouldn't do this for real code, but this is just for dev mode
131                let is_self_closing = children.is_empty();
132
133                let attrs = attrs
134                    .iter()
135                    .filter_map(|(name, value)| match value {
136                        LAttributeValue::Boolean => Some(format!("{name} ")),
137                        LAttributeValue::Static(value) => Some(format!("{name}=\"{value}\" ")),
138                        LAttributeValue::Dynamic => None,
139                        LAttributeValue::Noop => None,
140                    })
141                    .collect::<String>();
142
143                let children = children.iter().map(LNode::to_html).collect::<String>();
144
145                if is_self_closing {
146                    format!("<{name} {attrs}/>")
147                } else {
148                    format!("<{name} {attrs}>{children}</{name}>")
149                }
150            }
151        }
152    }
153}