mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
feat: support diffing inside component children in hot-reload (#690)
This commit is contained in:
parent
3bd52fcc9d
commit
f2ac412253
4 changed files with 84 additions and 17 deletions
|
@ -2,10 +2,6 @@ use crate::node::{LAttributeValue, LNode};
|
|||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// TODO: insertion and removal code are still somewhat broken
|
||||
// namely, it will tend to remove and move or mutate nodes,
|
||||
// which causes a bit of a problem for DynChild etc.
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OldChildren(IndexMap<LNode, Vec<usize>>);
|
||||
|
||||
|
@ -58,7 +54,7 @@ impl LNode {
|
|||
.collect(),
|
||||
},
|
||||
LNode::Text(_)
|
||||
| LNode::Component(_, _)
|
||||
| LNode::Component { .. }
|
||||
| LNode::DynChild(_) => ReplacementNode::Html(self.to_html()),
|
||||
},
|
||||
}
|
||||
|
@ -80,10 +76,19 @@ impl LNode {
|
|||
child.add_old_children(new_path, positions);
|
||||
}
|
||||
}
|
||||
// only need to insert dynamic content, as these might change
|
||||
LNode::Component(_, _) | LNode::DynChild(_) => {
|
||||
// need to insert dynamic content and children, as these might change
|
||||
LNode::DynChild(_) => {
|
||||
positions.0.insert(self.clone(), path);
|
||||
}
|
||||
LNode::Component { children, .. } => {
|
||||
positions.0.insert(self.clone(), path.clone());
|
||||
|
||||
for (idx, child) in children.iter().enumerate() {
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(idx);
|
||||
child.add_old_children(new_path, positions);
|
||||
}
|
||||
}
|
||||
// can just create text nodes, whatever
|
||||
LNode::Text(_) => {}
|
||||
}
|
||||
|
@ -148,6 +153,28 @@ impl LNode {
|
|||
.collect()
|
||||
}
|
||||
// components + dynamic context: no patches
|
||||
(
|
||||
LNode::Component {
|
||||
name: old_name,
|
||||
children: old_children,
|
||||
..
|
||||
},
|
||||
LNode::Component {
|
||||
name: new_name,
|
||||
children: new_children,
|
||||
..
|
||||
},
|
||||
) if old_name == new_name => {
|
||||
let mut path = path.to_vec();
|
||||
path.push(0);
|
||||
path.push(0);
|
||||
LNode::diff_children(
|
||||
&path,
|
||||
old_children,
|
||||
new_children,
|
||||
orig_children,
|
||||
)
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,11 @@ pub enum LNode {
|
|||
},
|
||||
// don't need anything; skipped during patching because it should
|
||||
// contain its own view macros
|
||||
Component(String, Vec<(String, String)>),
|
||||
Component {
|
||||
name: String,
|
||||
props: Vec<(String, String)>,
|
||||
children: Vec<LNode>,
|
||||
},
|
||||
DynChild(String),
|
||||
}
|
||||
|
||||
|
@ -71,9 +75,14 @@ impl LNode {
|
|||
}
|
||||
Node::Element(el) => {
|
||||
if is_component_node(&el) {
|
||||
views.push(LNode::Component(
|
||||
el.name.to_string(),
|
||||
el.attributes
|
||||
let mut children = Vec::new();
|
||||
for child in el.children {
|
||||
LNode::parse_node(child, &mut children)?;
|
||||
}
|
||||
views.push(LNode::Component {
|
||||
name: el.name.to_string(),
|
||||
props: el
|
||||
.attributes
|
||||
.into_iter()
|
||||
.filter_map(|attr| match attr {
|
||||
Node::Attribute(attr) => Some((
|
||||
|
@ -83,7 +92,8 @@ impl LNode {
|
|||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
children,
|
||||
});
|
||||
} else {
|
||||
let name = el.name.to_string();
|
||||
let mut attrs = Vec::new();
|
||||
|
@ -125,7 +135,7 @@ impl LNode {
|
|||
match self {
|
||||
LNode::Fragment(frag) => frag.iter().map(LNode::to_html).collect(),
|
||||
LNode::Text(text) => text.to_owned(),
|
||||
LNode::Component(name, _) => format!(
|
||||
LNode::Component { name, .. } => format!(
|
||||
"<!--<{name}>--><pre><{name}/> will load once Rust code \
|
||||
has been compiled.</pre><!--</{name}>-->"
|
||||
),
|
||||
|
|
|
@ -2,7 +2,8 @@ console.log("[HOT RELOADING] Connected to server.");
|
|||
function patch(json) {
|
||||
try {
|
||||
const views = JSON.parse(json);
|
||||
for (const [id, patches] of views) {
|
||||
for (const [id, patches] of views) {
|
||||
console.log("[HOT RELOAD]", patches);
|
||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT),
|
||||
open = `leptos-view|${id}|open`,
|
||||
close = `leptos-view|${id}|close`;
|
||||
|
@ -250,7 +251,33 @@ function patch(json) {
|
|||
node: walker.currentNode
|
||||
});
|
||||
} else if (walker.currentNode.nodeType == Node.COMMENT_NODE) {
|
||||
if (walker.currentNode.textContent.trim().startsWith("leptos-view")) {
|
||||
if (walker.currentNode.textContent.trim().startsWith("leptos-view")) {
|
||||
if (walker.currentNode.textContent.trim().endsWith("-children|open")) {
|
||||
const startingName = walker.currentNode.textContent.trim();
|
||||
const componentName = startingName.replace("-children|open").replace("leptos-view|");
|
||||
const endingName = `leptos-view|${componentName}-children|close`;
|
||||
let start = walker.currentNode;
|
||||
let depth = 1;
|
||||
|
||||
while (walker.nextNode()) {
|
||||
if (walker.currentNode.textContent.trim() == endingName) {
|
||||
depth--;
|
||||
} else if (walker.currentNode.textContent.trim() == startingName) {
|
||||
depth++;
|
||||
}
|
||||
|
||||
if(depth == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let end = walker.currentNode;
|
||||
actualChildren.push({
|
||||
type: "fragment",
|
||||
start: start.nextSibling,
|
||||
end: end.previousSibling,
|
||||
children: childrenFromRange(start.parentElement, start.nextSibling, end.previousSibling)
|
||||
});
|
||||
}
|
||||
} else if (walker.currentNode.textContent.trim() == "<() />") {
|
||||
actualChildren.push({
|
||||
type: "unit",
|
||||
|
@ -326,7 +353,7 @@ function patch(json) {
|
|||
return actualChildren;
|
||||
}
|
||||
|
||||
function childAtPath(element, path) {
|
||||
function childAtPath(element, path) {
|
||||
if (path.length == 0) {
|
||||
return element;
|
||||
} else if (element.children) {
|
||||
|
|
|
@ -1102,6 +1102,9 @@ pub(crate) fn component_to_tokens(
|
|||
let children = if node.children.is_empty() {
|
||||
quote! {}
|
||||
} else {
|
||||
let marker = format!("<{component_name}/>-children");
|
||||
let view_marker = quote! { .with_view_marker(#marker) };
|
||||
|
||||
let children = fragment_to_tokens(
|
||||
cx,
|
||||
span,
|
||||
|
@ -1120,7 +1123,7 @@ pub(crate) fn component_to_tokens(
|
|||
.children({
|
||||
#(#clonables)*
|
||||
|
||||
Box::new(move |#cx| #children)
|
||||
Box::new(move |#cx| #children #view_marker)
|
||||
})
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue