Switch to a pool of dynamic values for hot reloading (#2705)
* create the dynamic value pool * assign ids to dynamic formatted segments * separate the rendering and literal pools * rsx output compiles again * more examples compiling with new rsx expansion * update template body explanation * all workspace examples compile * fix formatted segments in keys * start hot reload diffing * fix component literal hot reloading * start integrate new hot reloading with the CLI * simple hot reloads working * Fix hot reloading blocks with components * implement hot reloading for if chains * Fix hot reloading after a template requires a full rebuild * Fix hot reloading any attribute values * remove unsafe from hot reload utils * Fix hot reloading empty rsx * add more hot reloading tests * reorganize hot reload module * fix web hydration * fix empty rsx nodes in autoformatting * fix tests * remove path sorting logic from core * make template names more consistent in debug mode * fix quote_as_hot_reload_literal for explicitly typed literals * fix can_be_shorthand for string literals * fix formatted single dynamic expression * Fix usize component properties and playwright tests * remove default implementation for TemplateBody * add a bunch more comments for diffing, scoring and why this scoring system is optimal
This commit is contained in:
1 parent
f3ca1484a1
commit
34bdcd15cf
40 files changed
+2288
-1897
No files matched your search
@@ -0,0 +1,138 @@
|
||||
use crate::attribute::Attribute;
|
||||
use crate::{
|
||||
AttributeValue, BodyNode, HotLiteral, HotReloadFormattedSegment, Segment, TemplateBody,
|
||||
};
|
||||
|
||||
/// A visitor that assigns dynamic ids to nodes and attributes and accumulates paths to dynamic nodes and attributes
|
||||
struct DynIdVisitor<'a> {
|
||||
body: &'a mut TemplateBody,
|
||||
current_path: Vec<u8>,
|
||||
dynamic_text_index: usize,
|
||||
component_literal_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> DynIdVisitor<'a> {
|
||||
fn new(body: &'a mut TemplateBody) -> Self {
|
||||
Self {
|
||||
body,
|
||||
current_path: Vec::new(),
|
||||
dynamic_text_index: 0,
|
||||
component_literal_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_children(&mut self, children: &[BodyNode]) {
|
||||
for (idx, node) in children.iter().enumerate() {
|
||||
self.current_path.push(idx as u8);
|
||||
self.visit(node);
|
||||
self.current_path.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&mut self, node: &BodyNode) {
|
||||
match node {
|
||||
// Just descend into elements - they're not dynamic
|
||||
BodyNode::Element(el) => {
|
||||
for (idx, attr) in el.merged_attributes.iter().enumerate() {
|
||||
if !attr.is_static_str_literal() {
|
||||
self.assign_path_to_attribute(attr, idx);
|
||||
if let AttributeValue::AttrLiteral(HotLiteral::Fmted(lit)) = &attr.value {
|
||||
self.assign_formatted_segment(lit);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Assign formatted segments to the key which is not included in the merged_attributes
|
||||
if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(fmted))) = el.key() {
|
||||
self.assign_formatted_segment(fmted);
|
||||
}
|
||||
|
||||
self.visit_children(&el.children);
|
||||
}
|
||||
|
||||
// Text nodes are dynamic if they contain dynamic segments
|
||||
BodyNode::Text(txt) => {
|
||||
if !txt.is_static() {
|
||||
self.assign_path_to_node(node);
|
||||
self.assign_formatted_segment(&txt.input);
|
||||
}
|
||||
}
|
||||
|
||||
// Raw exprs are always dynamic
|
||||
BodyNode::RawExpr(_) | BodyNode::ForLoop(_) | BodyNode::IfChain(_) => {
|
||||
self.assign_path_to_node(node)
|
||||
}
|
||||
BodyNode::Component(component) => {
|
||||
self.assign_path_to_node(node);
|
||||
let mut index = 0;
|
||||
for property in &component.fields {
|
||||
if let AttributeValue::AttrLiteral(literal) = &property.value {
|
||||
if let HotLiteral::Fmted(segments) = literal {
|
||||
self.assign_formatted_segment(segments);
|
||||
}
|
||||
component.component_literal_dyn_idx[index]
|
||||
.set(self.component_literal_index);
|
||||
self.component_literal_index += 1;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Assign ids to a formatted segment
|
||||
fn assign_formatted_segment(&mut self, segments: &HotReloadFormattedSegment) {
|
||||
let mut dynamic_node_indexes = segments.dynamic_node_indexes.iter();
|
||||
for segment in &segments.segments {
|
||||
if let Segment::Formatted(segment) = segment {
|
||||
dynamic_node_indexes
|
||||
.next()
|
||||
.unwrap()
|
||||
.set(self.dynamic_text_index);
|
||||
self.dynamic_text_index += 1;
|
||||
self.body.dynamic_text_segments.push(segment.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign a path to a node and give it its dynamic index
|
||||
/// This simplifies the ToTokens implementation for the macro to be a little less centralized
|
||||
fn assign_path_to_node(&mut self, node: &BodyNode) {
|
||||
// Assign the TemplateNode::Dynamic index to the node
|
||||
node.set_dyn_idx(self.body.node_paths.len());
|
||||
|
||||
// And then save the current path as the corresponding path
|
||||
self.body.node_paths.push(self.current_path.clone());
|
||||
}
|
||||
|
||||
/// Assign a path to a attribute and give it its dynamic index
|
||||
/// This simplifies the ToTokens implementation for the macro to be a little less centralized
|
||||
pub(crate) fn assign_path_to_attribute(
|
||||
&mut self,
|
||||
attribute: &Attribute,
|
||||
attribute_index: usize,
|
||||
) {
|
||||
// Assign the dynamic index to the attribute
|
||||
attribute.set_dyn_idx(self.body.attr_paths.len());
|
||||
|
||||
// And then save the current path as the corresponding path
|
||||
self.body
|
||||
.attr_paths
|
||||
.push((self.current_path.clone(), attribute_index));
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateBody {
|
||||
/// Cascade down path information into the children of this template
|
||||
///
|
||||
/// This provides the necessary path and index information for the children of this template
|
||||
/// so that they can render out their dynamic nodes correctly. Also does plumbing for things like
|
||||
/// hotreloaded literals which need to be tracked on a per-template basis.
|
||||
///
|
||||
/// This can only operate with knowledge of this template, not the surrounding callbody. Things like
|
||||
/// wiring of ifmt literals need to be done at the callbody level since those final IDs need to
|
||||
/// be unique to the entire app.
|
||||
pub(crate) fn assign_paths_inner(&mut self, nodes: &[BodyNode]) {
|
||||
let mut visitor = DynIdVisitor::new(self);
|
||||
visitor.visit_children(nodes);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user