WIP diffing templates

This commit is contained in:
Evan Almloff committed 2022-12-10 21:18:44 -06:00
1 parent 6b19229b53
commit 2419a2ae9d
1 file changed
+208 -76
+208 -76
View File
@@ -19,7 +19,7 @@ mod hot_reloading_context;
mod ifmt;
mod node;
use std::{borrow::Borrow, hash::Hash};
use std::{collections::HashMap, hash::Hash};
// Re-export the namespaces into each other
pub use component::*;
@@ -39,9 +39,7 @@ use syn::{
};
// interns a object into a static object, resusing the value if it already exists
fn intern<'a, T: Eq + Hash + Send + Sync + ?Sized + 'static>(
s: impl Into<Intern<T>>,
) -> &'static T {
fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
s.into().as_ref()
}
@@ -63,7 +61,7 @@ impl<Ctx: HotReloadingContext> CallBody<Ctx> {
/// the previous_location is the location of the previous template at the time the template was originally compiled.
pub fn update_template(
&self,
template: Option<&CallBody<Ctx>>,
template: Option<CallBody<Ctx>>,
location: &'static str,
) -> Option<Template> {
let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer {
@@ -129,22 +127,19 @@ pub struct TemplateRenderer<'a, Ctx: HotReloadingContext = Empty> {
impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
fn update_template(
&mut self,
previous_call: Option<&CallBody<Ctx>>,
previous_call: Option<CallBody<Ctx>>,
location: &'static str,
) -> Option<Template<'static>> {
let mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
let mut context: DynamicContext<Ctx> = DynamicContext::default();
let roots: Vec<_> = self
.roots
.iter()
.enumerate()
.map(|(idx, root)| {
context.current_path.push(idx as u8);
let out = context.update_node(root);
context.current_path.pop();
out
})
.collect();
let mut roots = Vec::new();
for (idx, root) in self.roots.iter().enumerate() {
context.current_path.push(idx as u8);
roots.push(context.update_node(root, mapping.as_ref())?);
context.current_path.pop();
}
Some(Template {
name: location,
@@ -229,6 +224,97 @@ impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
}
}
#[derive(Debug, Default)]
struct DynamicMapping {
attribute_to_idx: HashMap<ElementAttrNamed, Vec<usize>>,
last_attribute_idx: usize,
node_to_idx: HashMap<BodyNode, Vec<usize>>,
last_element_idx: usize,
}
impl DynamicMapping {
fn from(nodes: Vec<BodyNode>) -> Self {
let mut new = Self::default();
for node in nodes {
new.add_node(node);
}
println!("{:#?}", new);
new
}
fn get_attribute_idx(&self, attr: &ElementAttrNamed) -> Option<usize> {
self.attribute_to_idx
.get(attr)
.and_then(|idxs| idxs.last().copied())
}
fn get_node_idx(&self, node: &BodyNode) -> Option<usize> {
self.node_to_idx
.get(node)
.and_then(|idxs| idxs.last().copied())
}
fn insert_attribute(&mut self, attr: ElementAttrNamed) -> usize {
let idx = self.last_attribute_idx;
self.last_attribute_idx += 1;
self.attribute_to_idx
.entry(attr)
.or_insert_with(Vec::new)
.push(idx);
idx
}
fn insert_node(&mut self, node: BodyNode) -> usize {
let idx = self.last_element_idx;
self.last_element_idx += 1;
self.node_to_idx
.entry(node)
.or_insert_with(Vec::new)
.push(idx);
idx
}
fn add_node(&mut self, node: BodyNode) {
match node {
BodyNode::Element(el) => {
for attr in el.attributes {
match &attr.attr {
ElementAttr::CustomAttrText { value, .. }
| ElementAttr::AttrText { value, .. }
if value.is_static() => {}
ElementAttr::AttrExpression { .. }
| ElementAttr::AttrText { .. }
| ElementAttr::CustomAttrText { .. }
| ElementAttr::CustomAttrExpression { .. }
| ElementAttr::EventTokens { .. } => {
self.insert_attribute(attr);
}
}
}
for child in el.children {
self.add_node(child);
}
}
BodyNode::Text(text) if text.is_static() => {}
BodyNode::RawExpr(_)
| BodyNode::Text(_)
| BodyNode::ForLoop(_)
| BodyNode::IfChain(_)
| BodyNode::Component(_) => {
self.insert_node(node);
}
}
}
}
// As we create the dynamic nodes, we want to keep track of them in a linear fashion
// We'll use the size of the vecs to determine the index of the dynamic node in the final output
pub struct DynamicContext<'a, Ctx: HotReloadingContext> {
@@ -256,52 +342,38 @@ impl<'a, Ctx: HotReloadingContext> Default for DynamicContext<'a, Ctx> {
}
impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
fn update_node(&mut self, root: &'a BodyNode) -> TemplateNode<'static> {
fn update_node(
&mut self,
root: &'a BodyNode,
mapping: Option<&DynamicMapping>,
) -> Option<TemplateNode<'static>> {
match root {
BodyNode::Element(el) => {
// dynamic attributes
// [0]
// [0, 1]
// [0, 1]
// [0, 1]
// [0, 1, 2]
// [0, 2]
// [0, 2, 1]
let element_name_rust = el.name.to_string();
let static_attrs: Vec<TemplateAttribute<'static>> = el
.attributes
.iter()
.map(|attr| match &attr.attr {
let mut static_attrs = Vec::new();
for attr in &el.attributes {
match &attr.attr {
ElementAttr::AttrText { name, value } if value.is_static() => {
let value = value.source.as_ref().unwrap();
let attribute_name_rust = name.to_string();
let (name, namespace) =
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
.unwrap_or((intern(attribute_name_rust.as_str()), None));
TemplateAttribute::Static {
static_attrs.push(TemplateAttribute::Static {
name,
namespace,
value: intern(value.value().as_str()),
// name: dioxus_elements::#el_name::#name.0,
// namespace: dioxus_elements::#el_name::#name.1,
// value: #value,
// todo: we don't diff these so we never apply the volatile flag
// volatile: dioxus_elements::#el_name::#name.2,
}
})
}
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
let value = value.source.as_ref().unwrap();
TemplateAttribute::Static {
static_attrs.push(TemplateAttribute::Static {
name: intern(name.value().as_str()),
namespace: None,
value: intern(value.value().as_str()),
// todo: we don't diff these so we never apply the volatile flag
// volatile: dioxus_elements::#el_name::#name.2,
}
})
}
ElementAttr::AttrExpression { .. }
@@ -309,41 +381,39 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
| ElementAttr::CustomAttrText { .. }
| ElementAttr::CustomAttrExpression { .. }
| ElementAttr::EventTokens { .. } => {
let ct = self.dynamic_attributes.len();
let ct = match mapping {
Some(mapping) => mapping.get_attribute_idx(attr)?,
None => self.dynamic_attributes.len(),
};
self.dynamic_attributes.push(attr);
self.attr_paths.push(self.current_path.clone());
TemplateAttribute::Dynamic { id: ct }
static_attrs.push(TemplateAttribute::Dynamic { id: ct })
}
})
.collect();
}
}
let children: Vec<_> = el
.children
.iter()
.enumerate()
.map(|(idx, root)| {
self.current_path.push(idx as u8);
let out = self.update_node(root);
self.current_path.pop();
out
})
.collect();
let mut children = Vec::new();
for (idx, root) in el.children.iter().enumerate() {
self.current_path.push(idx as u8);
children.push(self.update_node(root, mapping)?);
self.current_path.pop();
}
let (tag, namespace) = Ctx::map_element(&element_name_rust)
.unwrap_or((intern(element_name_rust.as_str()), None));
TemplateNode::Element {
Some(TemplateNode::Element {
tag,
namespace,
attrs: intern(static_attrs.into_boxed_slice()),
children: intern(children.as_slice()),
}
})
}
BodyNode::Text(text) if text.is_static() => {
let text = text.source.as_ref().unwrap();
TemplateNode::Text {
Some(TemplateNode::Text {
text: intern(text.value().as_str()),
}
})
}
BodyNode::RawExpr(_)
@@ -351,14 +421,17 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
| BodyNode::ForLoop(_)
| BodyNode::IfChain(_)
| BodyNode::Component(_) => {
let ct = self.dynamic_nodes.len();
let ct = match mapping {
Some(mapping) => mapping.get_node_idx(root)?,
None => self.dynamic_nodes.len(),
};
self.dynamic_nodes.push(root);
self.node_paths.push(self.current_path.clone());
match root {
Some(match root {
BodyNode::Text(_) => TemplateNode::DynamicText { id: ct },
_ => TemplateNode::Dynamic { id: ct },
}
})
}
}
}
@@ -367,16 +440,6 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
match root {
BodyNode::Element(el) => {
let el_name = &el.name;
// dynamic attributes
// [0]
// [0, 1]
// [0, 1]
// [0, 1]
// [0, 1, 2]
// [0, 2]
// [0, 2, 1]
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
ElementAttr::AttrText { name, value } if value.is_static() => {
let value = value.source.as_ref().unwrap();
@@ -466,7 +529,7 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
}
#[test]
fn template() {
fn create_template() {
let input = quote! {
svg {
width: 100,
@@ -549,3 +612,72 @@ fn template() {
},
)
}
#[test]
fn diff_template() {
let input = quote! {
svg {
width: 100,
height: "100px",
"width2": 100,
"height2": "100px",
p {
"hello world"
}
(0..10).map(|i| rsx!{"{i}"}),
}
};
struct Mock;
impl HotReloadingContext for Mock {
fn map_attribute(
element_name_rust: &str,
attribute_name_rust: &str,
) -> Option<(&'static str, Option<&'static str>)> {
match element_name_rust {
"svg" => match attribute_name_rust {
"width" => Some(("width", Some("style"))),
"height" => Some(("height", Some("style"))),
_ => None,
},
_ => None,
}
}
fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
match element_name_rust {
"svg" => Some(("svg", Some("svg"))),
_ => None,
}
}
}
let call_body1: CallBody<Mock> = syn::parse2(input).unwrap();
let template = call_body1.update_template(None, "testing").unwrap();
dbg!(template);
// scrambling the attributes should not cause a full rebuild
let input = quote! {
div {
"width2": 100,
height: "100px",
"height2": "100px",
// width: 100,
(0..10).map(|i| rsx!{"{i}"}),
p {
"hello world"
}
}
};
let call_body2: CallBody<Mock> = syn::parse2(input).unwrap();
let template = call_body2
.update_template(Some(call_body1), "testing")
.unwrap();
dbg!(template);
}