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

+43 -32
View File
@@ -20,7 +20,7 @@ use crate::innerlude::*;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2_diagnostics::SpanDiagnosticExt;
use quote::{quote, ToTokens, TokenStreamExt};
use std::collections::HashSet;
use std::{collections::HashSet, vec};
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
@@ -32,6 +32,7 @@ pub struct Component {
pub name: syn::Path,
pub generics: Option<AngleBracketedGenericArguments>,
pub fields: Vec<Attribute>,
pub component_literal_dyn_idx: Vec<DynIdx>,
pub spreads: Vec<Spread>,
pub brace: token::Brace,
pub children: TemplateBody,
@@ -56,12 +57,19 @@ impl Parse for Component {
diagnostics,
} = input.parse::<RsxBlock>()?;
let literal_properties_count = fields
.iter()
.filter(|attr| matches!(attr.value, AttributeValue::AttrLiteral(_)))
.count();
let component_literal_dyn_idx = vec![DynIdx::default(); literal_properties_count];
let mut component = Self {
dyn_idx: DynIdx::default(),
children: TemplateBody::new(children),
name,
generics,
fields,
component_literal_dyn_idx,
brace,
spreads,
diagnostics,
@@ -71,7 +79,6 @@ impl Parse for Component {
// validating it will dump diagnostics into the output
component.validate_component_path();
component.validate_fields();
component.validate_key();
component.validate_component_spread();
Ok(component)
@@ -167,38 +174,17 @@ impl Component {
}
}
/// Ensure only one key and that the key is not a static str
///
/// todo: we want to allow arbitrary exprs for keys provided they impl hash / eq
fn validate_key(&mut self) {
let key = self.get_key();
if let Some(attr) = key {
let diagnostic = match &attr.value {
AttributeValue::AttrLiteral(ifmt) if ifmt.is_static() => {
ifmt.span().error("Key must not be a static string. Make sure to use a formatted string like `key: \"{value}\"")
}
AttributeValue::AttrLiteral(_) => return,
_ => attr
.value
.span()
.error("Key must be in the form of a formatted string like `key: \"{value}\""),
};
self.diagnostics.push(diagnostic);
}
}
pub fn get_key(&self) -> Option<&Attribute> {
self.fields
.iter()
.find(|attr| matches!(&attr.name, AttributeName::BuiltIn(key) if key == "key"))
pub fn get_key(&self) -> Option<&AttributeValue> {
self.fields.iter().find_map(|attr| match &attr.name {
AttributeName::BuiltIn(key) if key == "key" => Some(&attr.value),
_ => None,
})
}
/// Ensure there's no duplicate props - this will be a compile error but we can move it to a
/// diagnostic, thankfully
///
/// Also ensure there's no stringly typed propsa
/// Also ensure there's no stringly typed props
fn validate_fields(&mut self) {
let mut seen = HashSet::new();
@@ -271,9 +257,10 @@ impl Component {
}
fn make_field_idents(&self) -> Vec<(TokenStream2, TokenStream2)> {
let mut dynamic_literal_index = 0;
self.fields
.iter()
.filter_map(|attr| {
.filter_map(move |attr| {
let Attribute { name, value, .. } = attr;
let attr = match name {
@@ -287,7 +274,30 @@ impl Component {
AttributeName::Spread(_) => return None,
};
Some((attr, value.to_token_stream()))
let release_value = value.to_token_stream();
// In debug mode, we try to grab the value from the dynamic literal pool if possible
let value = if let AttributeValue::AttrLiteral(literal) = &value {
let idx = self.component_literal_dyn_idx[dynamic_literal_index].get();
dynamic_literal_index += 1;
let debug_value = quote! { __dynamic_literal_pool.component_property(#idx, &*__template_read, #literal) };
quote! {
{
#[cfg(debug_assertions)]
{
#debug_value
}
#[cfg(not(debug_assertions))]
{
#release_value
}
}
}
} else {
release_value
};
Some((attr, value))
})
.collect()
}
@@ -310,6 +320,7 @@ impl Component {
fields: vec![],
spreads: vec![],
children: TemplateBody::new(vec![]),
component_literal_dyn_idx: vec![],
dyn_idx: DynIdx::default(),
diagnostics,
}
@@ -429,7 +440,7 @@ fn generics_params() {
let input_without_children = quote! {
Outlet::<R> {}
};
let component: CallBody = syn::parse2(input_without_children).unwrap();
let component: crate::CallBody = syn::parse2(input_without_children).unwrap();
println!("{}", component.to_token_stream().pretty_unparse());
}