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:
Evan Almloff authored and GitHub committed 2024-07-30 17:16:27 +00:00
1 parent f3ca1484a1
commit 34bdcd15cf
40 files changed
+2288 -1897

No files matched your search

+18 -52
View File
@@ -1,5 +1,5 @@
use crate::innerlude::MountId;
use crate::{Attribute, AttributeValue, DynamicNode::*, Template};
use crate::{Attribute, AttributeValue, DynamicNode::*};
use crate::{VNode, VirtualDom, WriteMutations};
use core::iter::Peekable;
@@ -21,21 +21,6 @@ impl VNode {
// The node we are diffing from should always be mounted
debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
// If hot reloading is enabled, we need to make sure we're using the latest template
#[cfg(debug_assertions)]
{
let name = new.template.get().name;
if let Some(template) = dom.templates.get(name).cloned() {
new.template.set(template);
if template != self.template.get() {
let mount_id = self.mount.get();
let parent = dom.mounts[mount_id.0].parent;
self.replace(std::slice::from_ref(new), parent, dom, to);
return;
}
}
}
// If the templates are different by name, we need to replace the entire template
if self.templates_are_different(new) {
return self.light_diff_templates(new, dom, to);
@@ -120,7 +105,7 @@ impl VNode {
&self,
root_idx: usize,
) -> Option<(usize, &DynamicNode)> {
self.template.get().roots[root_idx]
self.template.roots[root_idx]
.dynamic_id()
.map(|id| (id, &self.dynamic_nodes[id]))
}
@@ -148,7 +133,7 @@ impl VNode {
pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId {
let mount = &dom.mounts[self.mount.get().0];
let last_root_index = self.template.get().roots.len() - 1;
let last_root_index = self.template.roots.len() - 1;
match self.get_dynamic_root_node_and_id(last_root_index) {
// This node is static, just get the root id
None | Some((_, Placeholder(_) | Text(_))) => mount.root_ids[last_root_index],
@@ -272,7 +257,7 @@ impl VNode {
destroy_component_state: bool,
replace_with: Option<usize>,
) {
let roots = self.template.get().roots;
let roots = self.template.roots;
for (idx, node) in roots.iter().enumerate() {
let last_node = idx == roots.len() - 1;
if let Some(id) = node.dynamic_id() {
@@ -305,7 +290,7 @@ impl VNode {
dom: &mut VirtualDom,
destroy_component_state: bool,
) {
let template = self.template.get();
let template = self.template;
for (idx, dyn_node) in self.dynamic_nodes.iter().enumerate() {
let path_len = template.node_paths.get(idx).map(|path| path.len());
// Roots are cleaned up automatically above and nodes with a empty path are placeholders
@@ -361,14 +346,14 @@ impl VNode {
}
fn templates_are_different(&self, other: &VNode) -> bool {
let self_node_name = self.template.get().id();
let other_node_name = other.template.get().id();
let self_node_name = self.template.id();
let other_node_name = other.template.id();
self_node_name != other_node_name
}
pub(super) fn reclaim_attributes(&self, mount: MountId, dom: &mut VirtualDom) {
let mut next_id = None;
for (idx, path) in self.template.get().attr_paths.iter().enumerate() {
for (idx, path) in self.template.attr_paths.iter().enumerate() {
// We clean up the roots in the next step, so don't worry about them here
if path.len() <= 1 {
continue;
@@ -399,7 +384,7 @@ impl VNode {
let mut old_attributes_iter = old_attrs.iter().peekable();
let mut new_attributes_iter = new_attrs.iter().peekable();
let attribute_id = dom.mounts[mount_id.0].mounted_attributes[idx];
let path = self.template.get().attr_paths[idx];
let path = self.template.attr_paths[idx];
loop {
match (old_attributes_iter.peek(), new_attributes_iter.peek()) {
@@ -566,21 +551,6 @@ impl VNode {
}
}
/// Get the most up to date template for this rsx block
#[allow(unused)]
pub(crate) fn template(&self, dom: &VirtualDom) -> Template {
// check for a overridden template
#[cfg(debug_assertions)]
{
let template = self.template.get();
if let Some(new_template) = dom.templates.get(template.name) {
self.template.set(*new_template);
}
};
self.template.get()
}
/// Create this rsx block. This will create scopes from components that this rsx block contains, but it will not write anything to the DOM.
pub(crate) fn create(
&self,
@@ -589,7 +559,7 @@ impl VNode {
mut to: Option<&mut impl WriteMutations>,
) -> usize {
// Get the most up to date template
let template = self.template(dom);
let template = self.template;
// Initialize the mount information for this vnode if it isn't already mounted
if !self.mount.get().mounted() {
@@ -616,13 +586,9 @@ impl VNode {
}
// Walk the roots, creating nodes and assigning IDs
// nodes in an iterator of (dynamic_node_index, path)
let nodes_sorted = template.breadth_first_node_paths();
let attrs_sorted = template.breadth_first_attribute_paths();
let mut nodes = nodes_sorted.peekable();
let mut attrs = attrs_sorted.peekable();
// nodes in an iterator of (dynamic_node_index, path) and attrs in an iterator of (attr_index, path)
let mut nodes = template.node_paths.iter().copied().enumerate().peekable();
let mut attrs = template.attr_paths.iter().copied().enumerate().peekable();
// Get the mounted id of this block
// At this point, we should have already mounted the block
@@ -690,7 +656,7 @@ impl VNode {
fn reference_to_dynamic_node(&self, mount: MountId, dynamic_node_id: usize) -> ElementRef {
ElementRef {
path: ElementPath {
path: self.template.get().node_paths[dynamic_node_id],
path: self.template.node_paths[dynamic_node_id],
},
mount,
}
@@ -834,7 +800,7 @@ impl VNode {
let this_id = dom.next_element();
dom.mounts[mount.0].root_ids[root_idx] = this_id;
to.load_template(self.template.get().name, root_idx, this_id);
to.load_template(self.template.name, root_idx, this_id);
this_id
}
@@ -874,7 +840,7 @@ impl VNode {
dom: &mut VirtualDom,
) -> (ElementId, &'static [u8]) {
// Add the mutation to the list
let path = self.template.get().node_paths[idx];
let path = self.template.node_paths[idx];
// Allocate a dynamic element reference for this text node
let new_id = mount.mount_node(idx, dom);
@@ -940,8 +906,8 @@ fn matching_components<'a>(
left: &'a VNode,
right: &'a VNode,
) -> Option<Vec<(&'a VComponent, &'a VComponent)>> {
let left_node = left.template.get();
let right_node = right.template.get();
let left_node = left.template;
let right_node = right.template;
if left_node.roots.len() != right_node.roots.len() {
return None;
}