WIP diffing templates
This commit is contained in:
1 parent
6b19229b53
commit
2419a2ae9d
1 file changed
+208
-76
+208
-76
@@ -19,7 +19,7 @@ mod hot_reloading_context;
|
|||||||
mod ifmt;
|
mod ifmt;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
||||||
use std::{borrow::Borrow, hash::Hash};
|
use std::{collections::HashMap, hash::Hash};
|
||||||
|
|
||||||
// Re-export the namespaces into each other
|
// Re-export the namespaces into each other
|
||||||
pub use component::*;
|
pub use component::*;
|
||||||
@@ -39,9 +39,7 @@ use syn::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
// interns a object into a static object, resusing the value if it already exists
|
// interns a object into a static object, resusing the value if it already exists
|
||||||
fn intern<'a, T: Eq + Hash + Send + Sync + ?Sized + 'static>(
|
fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
|
||||||
s: impl Into<Intern<T>>,
|
|
||||||
) -> &'static T {
|
|
||||||
s.into().as_ref()
|
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.
|
/// the previous_location is the location of the previous template at the time the template was originally compiled.
|
||||||
pub fn update_template(
|
pub fn update_template(
|
||||||
&self,
|
&self,
|
||||||
template: Option<&CallBody<Ctx>>,
|
template: Option<CallBody<Ctx>>,
|
||||||
location: &'static str,
|
location: &'static str,
|
||||||
) -> Option<Template> {
|
) -> Option<Template> {
|
||||||
let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer {
|
let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer {
|
||||||
@@ -129,22 +127,19 @@ pub struct TemplateRenderer<'a, Ctx: HotReloadingContext = Empty> {
|
|||||||
impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
|
impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
|
||||||
fn update_template(
|
fn update_template(
|
||||||
&mut self,
|
&mut self,
|
||||||
previous_call: Option<&CallBody<Ctx>>,
|
previous_call: Option<CallBody<Ctx>>,
|
||||||
location: &'static str,
|
location: &'static str,
|
||||||
) -> Option<Template<'static>> {
|
) -> Option<Template<'static>> {
|
||||||
|
let mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
|
||||||
|
|
||||||
let mut context: DynamicContext<Ctx> = DynamicContext::default();
|
let mut context: DynamicContext<Ctx> = DynamicContext::default();
|
||||||
|
|
||||||
let roots: Vec<_> = self
|
let mut roots = Vec::new();
|
||||||
.roots
|
for (idx, root) in self.roots.iter().enumerate() {
|
||||||
.iter()
|
context.current_path.push(idx as u8);
|
||||||
.enumerate()
|
roots.push(context.update_node(root, mapping.as_ref())?);
|
||||||
.map(|(idx, root)| {
|
context.current_path.pop();
|
||||||
context.current_path.push(idx as u8);
|
}
|
||||||
let out = context.update_node(root);
|
|
||||||
context.current_path.pop();
|
|
||||||
out
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Some(Template {
|
Some(Template {
|
||||||
name: location,
|
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
|
// 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
|
// 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> {
|
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> {
|
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 {
|
match root {
|
||||||
BodyNode::Element(el) => {
|
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 element_name_rust = el.name.to_string();
|
||||||
|
|
||||||
let static_attrs: Vec<TemplateAttribute<'static>> = el
|
let mut static_attrs = Vec::new();
|
||||||
.attributes
|
for attr in &el.attributes {
|
||||||
.iter()
|
match &attr.attr {
|
||||||
.map(|attr| match &attr.attr {
|
|
||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||||
let value = value.source.as_ref().unwrap();
|
let value = value.source.as_ref().unwrap();
|
||||||
let attribute_name_rust = name.to_string();
|
let attribute_name_rust = name.to_string();
|
||||||
let (name, namespace) =
|
let (name, namespace) =
|
||||||
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
||||||
.unwrap_or((intern(attribute_name_rust.as_str()), None));
|
.unwrap_or((intern(attribute_name_rust.as_str()), None));
|
||||||
TemplateAttribute::Static {
|
static_attrs.push(TemplateAttribute::Static {
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
value: intern(value.value().as_str()),
|
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() => {
|
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
||||||
let value = value.source.as_ref().unwrap();
|
let value = value.source.as_ref().unwrap();
|
||||||
TemplateAttribute::Static {
|
static_attrs.push(TemplateAttribute::Static {
|
||||||
name: intern(name.value().as_str()),
|
name: intern(name.value().as_str()),
|
||||||
namespace: None,
|
namespace: None,
|
||||||
value: intern(value.value().as_str()),
|
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 { .. }
|
ElementAttr::AttrExpression { .. }
|
||||||
@@ -309,41 +381,39 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
|
|||||||
| ElementAttr::CustomAttrText { .. }
|
| ElementAttr::CustomAttrText { .. }
|
||||||
| ElementAttr::CustomAttrExpression { .. }
|
| ElementAttr::CustomAttrExpression { .. }
|
||||||
| ElementAttr::EventTokens { .. } => {
|
| 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.dynamic_attributes.push(attr);
|
||||||
self.attr_paths.push(self.current_path.clone());
|
self.attr_paths.push(self.current_path.clone());
|
||||||
TemplateAttribute::Dynamic { id: ct }
|
static_attrs.push(TemplateAttribute::Dynamic { id: ct })
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.collect();
|
}
|
||||||
|
|
||||||
let children: Vec<_> = el
|
let mut children = Vec::new();
|
||||||
.children
|
for (idx, root) in el.children.iter().enumerate() {
|
||||||
.iter()
|
self.current_path.push(idx as u8);
|
||||||
.enumerate()
|
children.push(self.update_node(root, mapping)?);
|
||||||
.map(|(idx, root)| {
|
self.current_path.pop();
|
||||||
self.current_path.push(idx as u8);
|
}
|
||||||
let out = self.update_node(root);
|
|
||||||
self.current_path.pop();
|
|
||||||
out
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let (tag, namespace) = Ctx::map_element(&element_name_rust)
|
let (tag, namespace) = Ctx::map_element(&element_name_rust)
|
||||||
.unwrap_or((intern(element_name_rust.as_str()), None));
|
.unwrap_or((intern(element_name_rust.as_str()), None));
|
||||||
TemplateNode::Element {
|
Some(TemplateNode::Element {
|
||||||
tag,
|
tag,
|
||||||
namespace,
|
namespace,
|
||||||
attrs: intern(static_attrs.into_boxed_slice()),
|
attrs: intern(static_attrs.into_boxed_slice()),
|
||||||
children: intern(children.as_slice()),
|
children: intern(children.as_slice()),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
BodyNode::Text(text) if text.is_static() => {
|
BodyNode::Text(text) if text.is_static() => {
|
||||||
let text = text.source.as_ref().unwrap();
|
let text = text.source.as_ref().unwrap();
|
||||||
TemplateNode::Text {
|
Some(TemplateNode::Text {
|
||||||
text: intern(text.value().as_str()),
|
text: intern(text.value().as_str()),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
BodyNode::RawExpr(_)
|
BodyNode::RawExpr(_)
|
||||||
@@ -351,14 +421,17 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
|
|||||||
| BodyNode::ForLoop(_)
|
| BodyNode::ForLoop(_)
|
||||||
| BodyNode::IfChain(_)
|
| BodyNode::IfChain(_)
|
||||||
| BodyNode::Component(_) => {
|
| 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.dynamic_nodes.push(root);
|
||||||
self.node_paths.push(self.current_path.clone());
|
self.node_paths.push(self.current_path.clone());
|
||||||
|
|
||||||
match root {
|
Some(match root {
|
||||||
BodyNode::Text(_) => TemplateNode::DynamicText { id: ct },
|
BodyNode::Text(_) => TemplateNode::DynamicText { id: ct },
|
||||||
_ => TemplateNode::Dynamic { id: ct },
|
_ => TemplateNode::Dynamic { id: ct },
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,16 +440,6 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
|
|||||||
match root {
|
match root {
|
||||||
BodyNode::Element(el) => {
|
BodyNode::Element(el) => {
|
||||||
let el_name = &el.name;
|
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 {
|
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
|
||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||||
let value = value.source.as_ref().unwrap();
|
let value = value.source.as_ref().unwrap();
|
||||||
@@ -466,7 +529,7 @@ impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn template() {
|
fn create_template() {
|
||||||
let input = quote! {
|
let input = quote! {
|
||||||
svg {
|
svg {
|
||||||
width: 100,
|
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);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user