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

+22 -109
View File
@@ -6,7 +6,6 @@ use crate::{
properties::ComponentFunction,
};
use crate::{Properties, ScopeId, VirtualDom};
use core::panic;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::vec;
@@ -122,7 +121,7 @@ pub struct VNodeInner {
pub key: Option<String>,
/// The static nodes and static descriptor of the template
pub template: Cell<Template>,
pub template: Template,
/// The dynamic nodes in the template
pub dynamic_nodes: Box<[DynamicNode]>,
@@ -252,12 +251,12 @@ impl VNode {
key: None,
dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
dynamic_attrs: Box::new([]),
template: Cell::new(Template {
template: Template {
name: "packages/core/nodes.rs:198:0:0",
roots: &[TemplateNode::Dynamic { id: 0 }],
node_paths: &[&[0]],
attr_paths: &[],
}),
},
})
})
.clone()
@@ -278,7 +277,7 @@ impl VNode {
Self {
vnode: Rc::new(VNodeInner {
key,
template: Cell::new(template),
template,
dynamic_nodes,
dynamic_attrs,
}),
@@ -290,7 +289,7 @@ impl VNode {
///
/// Returns [`None`] if the root is actually a static node (Element/Text)
pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> {
self.template.get().roots[idx]
self.template.roots[idx]
.dynamic_id()
.map(|id| &self.dynamic_nodes[id])
}
@@ -411,7 +410,7 @@ where
}
#[cfg(feature = "serialize")]
fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'a [T], D::Error>
pub(crate) fn deserialize_leaky<'a, 'de, T, D>(deserializer: D) -> Result<&'a [T], D::Error>
where
T: serde::Deserialize<'de>,
D: serde::Deserializer<'de>,
@@ -423,7 +422,9 @@ where
}
#[cfg(feature = "serialize")]
fn deserialize_option_leaky<'a, 'de, D>(deserializer: D) -> Result<Option<&'static str>, D::Error>
pub(crate) fn deserialize_option_leaky<'a, 'de, D>(
deserializer: D,
) -> Result<Option<&'static str>, D::Error>
where
D: serde::Deserializer<'de>,
{
@@ -448,36 +449,6 @@ impl Template {
let ptr: *const str = self.name;
ptr as *const () as usize
}
/// Iterate over the attribute paths in order along with the original indexes for each path
pub(crate) fn breadth_first_attribute_paths(
&self,
) -> impl Iterator<Item = (usize, &'static [u8])> {
// In release mode, hot reloading is disabled and everything is in breadth first order already
#[cfg(not(debug_assertions))]
{
self.attr_paths.iter().copied().enumerate()
}
// If we are in debug mode, hot reloading may have messed up the order of the paths. We need to sort them
#[cfg(debug_assertions)]
{
sort_bfo(self.attr_paths).into_iter()
}
}
/// Iterate over the node paths in order along with the original indexes for each path
pub(crate) fn breadth_first_node_paths(&self) -> impl Iterator<Item = (usize, &'static [u8])> {
// In release mode, hot reloading is disabled and everything is in breadth first order already
#[cfg(not(debug_assertions))]
{
self.node_paths.iter().copied().enumerate()
}
// If we are in debug mode, hot reloading may have messed up the order of the paths. We need to sort them
#[cfg(debug_assertions)]
{
sort_bfo(self.node_paths).into_iter()
}
}
}
/// A statically known node in a layout.
@@ -523,7 +494,10 @@ pub enum TemplateNode {
/// This template node is just a piece of static text
Text {
/// The actual text
#[serde(deserialize_with = "deserialize_string_leaky")]
#[cfg_attr(
feature = "serialize",
serde(deserialize_with = "deserialize_string_leaky")
)]
text: &'static str,
},
@@ -548,7 +522,7 @@ impl TemplateNode {
/// A node created at runtime
///
/// This node's index in the DynamicNode list on VNode should match its respective `Dynamic` index
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum DynamicNode {
/// A component node
///
@@ -692,8 +666,10 @@ pub struct VText {
impl VText {
/// Create a new VText
pub fn new(value: String) -> Self {
Self { value }
pub fn new(value: impl ToString) -> Self {
Self {
value: value.to_string(),
}
}
}
@@ -783,6 +759,7 @@ impl Attribute {
///
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
/// variant.
#[derive(Clone)]
pub enum AttributeValue {
/// Text attribute
Text(String),
@@ -800,7 +777,7 @@ pub enum AttributeValue {
Listener(ListenerCb),
/// An arbitrary value that implements PartialEq and is static
Any(Box<dyn AnyValue>),
Any(Rc<dyn AnyValue>),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
@@ -824,7 +801,7 @@ impl AttributeValue {
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
pub fn any_value<T: AnyValue>(value: T) -> AttributeValue {
AttributeValue::Any(Box::new(value))
AttributeValue::Any(Rc::new(value))
}
}
@@ -859,19 +836,6 @@ impl PartialEq for AttributeValue {
}
}
impl Clone for AttributeValue {
fn clone(&self) -> Self {
match self {
Self::Text(arg0) => Self::Text(arg0.clone()),
Self::Float(arg0) => Self::Float(*arg0),
Self::Int(arg0) => Self::Int(*arg0),
Self::Bool(arg0) => Self::Bool(*arg0),
Self::Listener(_) | Self::Any(_) => panic!("Cannot clone listener or any value"),
Self::None => Self::None,
}
}
}
#[doc(hidden)]
pub trait AnyValue: 'static {
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
@@ -1117,7 +1081,7 @@ impl IntoAttributeValue for Arguments<'_> {
}
}
impl IntoAttributeValue for Box<dyn AnyValue> {
impl IntoAttributeValue for Rc<dyn AnyValue> {
fn into_value(self) -> AttributeValue {
AttributeValue::Any(self)
}
@@ -1143,54 +1107,3 @@ pub trait HasAttributes {
volatile: bool,
) -> Self;
}
#[cfg(debug_assertions)]
pub(crate) fn sort_bfo(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
let mut with_indices = paths.iter().copied().enumerate().collect::<Vec<_>>();
with_indices.sort_by(|(_, a), (_, b)| {
let mut a = a.iter();
let mut b = b.iter();
loop {
match (a.next(), b.next()) {
(Some(a), Some(b)) => {
if a != b {
return a.cmp(b);
}
}
// The shorter path goes first
(None, Some(_)) => return std::cmp::Ordering::Less,
(Some(_), None) => return std::cmp::Ordering::Greater,
(None, None) => return std::cmp::Ordering::Equal,
}
}
});
with_indices
}
#[test]
#[cfg(debug_assertions)]
fn sorting() {
let r: [(usize, &[u8]); 5] = [
(0, &[0, 1]),
(1, &[0, 2]),
(2, &[1, 0]),
(3, &[1, 0, 1]),
(4, &[1, 2]),
];
assert_eq!(
sort_bfo(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]),
r
);
let r: [(usize, &[u8]); 6] = [
(0, &[0]),
(1, &[0, 1]),
(2, &[0, 1, 2]),
(3, &[1]),
(4, &[1, 2]),
(5, &[2]),
];
assert_eq!(
sort_bfo(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]),
r
);
}