From eb79e6164203c25a66c68fe134f5ca7a9cb7b634 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 24 Mar 2024 13:31:26 -0700 Subject: [PATCH] Add a ton of comments to rsx/hotreload, add snapshot testing, refactor a bit to simplify the crate (#2130) Merge dynamic context and dynamic mapping, clean up the rsx hotreload logic, and add location data to the Rsx objects --- Cargo.lock | 29 + Cargo.toml | 13 + packages/autofmt/src/writer.rs | 1 + packages/cli/rustfmt.toml | 1 - packages/rsx-rosetta/src/lib.rs | 1 + packages/rsx/.vscode/settings.json | 8 + packages/rsx/Cargo.toml | 6 +- packages/rsx/src/attribute.rs | 21 + packages/rsx/src/component.rs | 20 +- packages/rsx/src/context.rs | 416 ++++++++++ .../src/hot_reload/hot_reloading_file_map.rs | 18 +- packages/rsx/src/lib.rs | 771 +++--------------- packages/rsx/src/location.rs | 8 + packages/rsx/src/node.rs | 168 ++-- packages/rsx/src/renderer.rs | 126 +++ packages/rsx/src/sub_templates.rs | 45 + packages/rsx/src/util.rs | 1 + packages/rsx/tests/hotreload_pattern.rs | 90 ++ packages/rsx/tests/hotreloads.rs | 82 +- packages/rsx/tests/rsx_diff.rs | 112 +++ .../snapshots/rsx_diff__create_template.snap | 64 ++ .../snapshots/rsx_diff__diff_template-2.snap | 81 ++ .../snapshots/rsx_diff__diff_template.snap | 85 ++ packages/rsx/tests/valid/for_.new.rsx | 9 + packages/rsx/tests/valid/for_.old.rsx | 10 + packages/rsx/tests/valid/if_.new.rsx | 10 + packages/rsx/tests/valid/if_.old.rsx | 9 + .../tests/valid/{let.new.rsx => let_.new.rsx} | 0 .../tests/valid/{let.old.rsx => let_.old.rsx} | 0 packages/rsx/tests/valid/nested.new.rsx | 10 + packages/rsx/tests/valid/nested.old.rsx | 9 + 31 files changed, 1413 insertions(+), 811 deletions(-) delete mode 100644 packages/cli/rustfmt.toml create mode 100644 packages/rsx/.vscode/settings.json create mode 100644 packages/rsx/src/context.rs create mode 100644 packages/rsx/src/location.rs create mode 100644 packages/rsx/src/renderer.rs create mode 100644 packages/rsx/src/sub_templates.rs create mode 100644 packages/rsx/src/util.rs create mode 100644 packages/rsx/tests/hotreload_pattern.rs create mode 100644 packages/rsx/tests/rsx_diff.rs create mode 100644 packages/rsx/tests/snapshots/rsx_diff__create_template.snap create mode 100644 packages/rsx/tests/snapshots/rsx_diff__diff_template-2.snap create mode 100644 packages/rsx/tests/snapshots/rsx_diff__diff_template.snap create mode 100644 packages/rsx/tests/valid/for_.new.rsx create mode 100644 packages/rsx/tests/valid/for_.old.rsx create mode 100644 packages/rsx/tests/valid/if_.new.rsx create mode 100644 packages/rsx/tests/valid/if_.old.rsx rename packages/rsx/tests/valid/{let.new.rsx => let_.new.rsx} (100%) rename packages/rsx/tests/valid/{let.old.rsx => let_.old.rsx} (100%) create mode 100644 packages/rsx/tests/valid/nested.new.rsx create mode 100644 packages/rsx/tests/valid/nested.old.rsx diff --git a/Cargo.lock b/Cargo.lock index edbd58350..e32aa954c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2469,6 +2469,7 @@ name = "dioxus-rsx" version = "0.5.0-alpha.2" dependencies = [ "dioxus-core 0.5.0-alpha.2", + "insta", "internment", "krates", "proc-macro2", @@ -4728,6 +4729,19 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "insta" +version = "1.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7c22c4d34ef4788c351e971c52bfdfe7ea2766f8c5466bc175dd46e52ac22e" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "similar", + "yaml-rust", +] + [[package]] name = "instant" version = "0.1.12" @@ -7931,6 +7945,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "similar" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" + [[package]] name = "simple_logger" version = "4.3.3" @@ -10250,6 +10270,15 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index f4a365810..0e0965e32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,19 @@ axum-extra = "0.9.2" reqwest = "0.11.24" owo-colors = "4.0.0" +[workspace.dev-dependencies] +isnta = "1.36.1" + +# speed up some macros by optimizing them +[profile.dev.package.insta] +opt-level = 3 + +[profile.dev.package.similar] +opt-level = 3 + +[profile.dev.package.dioxus-core-macro] +opt-level = 3 + # Enable a small amount of optimization in debug mode [profile.cli-dev] inherits = "dev" diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 79d153779..d6466a122 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -262,6 +262,7 @@ impl<'a> Writer<'a> { then_branch, else_if_branch, else_branch, + .. } = chain; write!( diff --git a/packages/cli/rustfmt.toml b/packages/cli/rustfmt.toml deleted file mode 100644 index c3c8c3753..000000000 --- a/packages/cli/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -imports_granularity = "Crate" diff --git a/packages/rsx-rosetta/src/lib.rs b/packages/rsx-rosetta/src/lib.rs index 03844965c..1707a9a3a 100644 --- a/packages/rsx-rosetta/src/lib.rs +++ b/packages/rsx-rosetta/src/lib.rs @@ -143,6 +143,7 @@ pub fn collect_svgs(children: &mut [BodyNode], out: &mut Vec) { manual_props: None, key: None, brace: Default::default(), + location: Default::default(), }); std::mem::swap(child, &mut new_comp); diff --git a/packages/rsx/.vscode/settings.json b/packages/rsx/.vscode/settings.json new file mode 100644 index 000000000..693f5b782 --- /dev/null +++ b/packages/rsx/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "rust-analyzer.check.workspace": false, + // "rust-analyzer.check.extraArgs": [ + // "--features", + // "hot_reload" + // ], + "rust-analyzer.cargo.features": "all" +} diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index b2aba731a..5a5cbf1ac 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -23,7 +23,11 @@ krates = { version = "0.16.6", optional = true } tracing = { workspace = true } [features] -default = ["html"] +default = ["html", "hot_reload"] hot_reload = ["krates", "internment", "dioxus-core"] serde = ["dep:serde"] html = [] + + +[dev-dependencies] +insta = "1.36.1" diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index 1a403b901..4f558feac 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -8,7 +8,10 @@ use syn::{parse_quote, spanned::Spanned, Expr, ExprIf, Ident, LitStr}; #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub enum AttributeType { + /// An attribute that is known Named(ElementAttrNamed), + + /// An attribute that's being spread in via the `..` syntax Spread(Expr), } @@ -68,6 +71,24 @@ impl AttributeType { } } } + + pub fn as_static_str_literal(&self) -> Option<(&ElementAttrName, &IfmtInput)> { + match self { + AttributeType::Named(ElementAttrNamed { + attr: + ElementAttr { + value: ElementAttrValue::AttrLiteral(value), + name, + }, + .. + }) if value.is_static() => Some((name, value)), + _ => None, + } + } + + pub fn is_static_str_literal(&self) -> bool { + self.as_static_str_literal().is_some() + } } #[derive(Clone, Debug)] diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index a54bf5aa3..63fb13fc0 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -11,6 +11,8 @@ //! - [ ] Keys //! - [ ] Properties spreading with with `..` syntax +use self::{location::CallerLocation, renderer::TemplateRenderer}; + use super::*; use proc_macro2::TokenStream as TokenStream2; @@ -32,6 +34,7 @@ pub struct Component { pub children: Vec, pub manual_props: Option, pub brace: syn::token::Brace, + pub location: CallerLocation, } impl Parse for Component { @@ -88,6 +91,7 @@ impl Parse for Component { } Ok(Self { + location: CallerLocation::default(), name, prop_gen_args, fields, @@ -179,20 +183,10 @@ impl Component { toks.append_all(quote! {#field}) } if !self.children.is_empty() { - let renderer: TemplateRenderer = TemplateRenderer { - roots: &self.children, - location: None, - }; - - toks.append_all(quote! { - .children( - Some({ #renderer }) - ) - }); + let renderer = TemplateRenderer::as_tokens(&self.children, None); + toks.append_all(quote! { .children( Some({ #renderer }) ) }); } - toks.append_all(quote! { - .build() - }); + toks.append_all(quote! { .build() }); toks } diff --git a/packages/rsx/src/context.rs b/packages/rsx/src/context.rs new file mode 100644 index 000000000..d5c01a374 --- /dev/null +++ b/packages/rsx/src/context.rs @@ -0,0 +1,416 @@ +use std::collections::HashMap; + +use crate::*; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// As we create the dynamic nodes, we want to keep track of them in a linear fashion +/// We'll use the size of the vecs to determine the index of the dynamic node in the final output +#[derive(Default, Debug)] +pub struct DynamicContext<'a> { + pub dynamic_nodes: Vec<&'a BodyNode>, + pub dynamic_attributes: Vec>, + pub current_path: Vec, + pub node_paths: Vec>, + pub attr_paths: Vec>, + + /// Mapping variables used to map the old template to the new template + /// + /// This tracks whether or not we're tracking some nodes or attributes + /// If we're tracking, then we'll attempt to use the old mapping + is_tracking: bool, + + /// The mapping of node to its index in the dynamic_nodes list + /// We use the fact that BodyNode is Hash/PartialEq to track the nodes when we run into them + node_to_idx: HashMap>, + last_element_idx: usize, + + /// The mapping of attribute to its index in the dynamic_attributes list + /// We use the fact that AttributeType is Hash/PartialEq to track the attributes when we run into them + attribute_to_idx: HashMap>, + last_attribute_idx: usize, +} + +impl<'a> DynamicContext<'a> { + pub fn new_with_old(template: Option) -> Self { + let mut new = Self::default(); + + if let Some(call) = template { + for node in call.roots { + new.track_node(node); + } + new.is_tracking = true; + } + + new + } + + /// Populate the dynamic context with our own roots + /// + /// This will call update_node on each root, attempting to build us a list of TemplateNodes that + /// we can render out. + /// + /// These TemplateNodes are the same one used in Dioxus core! We just serialize them out and then + /// they'll get picked up after codegen for compilation. Cool stuff. + /// + /// If updating fails (IE the root is a dynamic node that has changed), then we return None. + pub fn populate_by_updating( + &mut self, + roots: &'a [BodyNode], + ) -> Option> { + // Create a list of new roots that we'll spit out + let mut roots_ = Vec::new(); + + // Populate the dynamic context with our own roots + for (idx, root) in roots.iter().enumerate() { + self.current_path.push(idx as u8); + roots_.push(self.update_node::(root)?); + self.current_path.pop(); + } + + Some(roots_) + } + + /// Render a portion of an rsx callbody to a TemplateNode call + /// + /// We're assembling the templatenodes + pub fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 { + match root { + BodyNode::Element(el) => self.render_static_element(el), + + BodyNode::Text(text) if text.is_static() => { + let text = text.to_static().unwrap(); + quote! { dioxus_core::TemplateNode::Text { text: #text } } + } + + BodyNode::ForLoop(for_loop) => self.render_for_loop(root, for_loop), + + BodyNode::RawExpr(_) + | BodyNode::Text(_) + | BodyNode::IfChain(_) + | BodyNode::Component(_) => self.render_dynamic_node(root), + } + } + + /// Render a for loop to a token stream + /// + /// This is basically just rendering a dynamic node, but with some extra bookkepping to track the + /// contents of the for loop in case we want to hot reload it + fn render_for_loop(&mut self, root: &'a BodyNode, _for_loop: &ForLoop) -> TokenStream2 { + self.render_dynamic_node(root) + } + + fn render_dynamic_node(&mut self, root: &'a BodyNode) -> TokenStream2 { + let ct = self.dynamic_nodes.len(); + self.dynamic_nodes.push(root); + self.node_paths.push(self.current_path.clone()); + match root { + BodyNode::Text(_) => quote! { dioxus_core::TemplateNode::DynamicText { id: #ct } }, + _ => quote! { dioxus_core::TemplateNode::Dynamic { id: #ct } }, + } + } + + fn render_static_element(&mut self, el: &'a Element) -> TokenStream2 { + let el_name = &el.name; + let ns = |name| match el_name { + ElementName::Ident(i) => quote! { dioxus_elements::#i::#name }, + ElementName::Custom(_) => quote! { None }, + }; + + let static_attrs = el + .merged_attributes + .iter() + .map(|attr| self.render_merged_attributes(attr, ns, el_name)) + .collect::>(); + + let children = el + .children + .iter() + .enumerate() + .map(|(idx, root)| self.render_children_nodes(idx, root)) + .collect::>(); + + let ns = ns(quote!(NAME_SPACE)); + let el_name = el_name.tag_name(); + + quote! { + dioxus_core::TemplateNode::Element { + tag: #el_name, + namespace: #ns, + attrs: &[ #(#static_attrs)* ], + children: &[ #(#children),* ], + } + } + } + + pub fn render_children_nodes(&mut self, idx: usize, root: &'a BodyNode) -> TokenStream2 { + self.current_path.push(idx as u8); + let out = self.render_static_node(root); + self.current_path.pop(); + out + } + + /// Render the attributes of an element + fn render_merged_attributes( + &mut self, + attr: &'a AttributeType, + ns: impl Fn(TokenStream2) -> TokenStream2, + el_name: &ElementName, + ) -> TokenStream2 { + // Rendering static attributes requires a bit more work than just a dynamic attrs + match attr.as_static_str_literal() { + // If it's static, we'll take this little optimization + Some((name, value)) => Self::render_static_attr(value, name, ns, el_name), + + // Otherwise, we'll just render it as a dynamic attribute + // This will also insert the attribute into the dynamic_attributes list to assemble the final template + _ => self.render_dynamic_attr(attr), + } + } + + fn render_static_attr( + value: &IfmtInput, + name: &ElementAttrName, + ns: impl Fn(TokenStream2) -> TokenStream2, + el_name: &ElementName, + ) -> TokenStream2 { + let value = value.to_static().unwrap(); + + let ns = match name { + ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)), + ElementAttrName::Custom(_) => quote!(None), + }; + + let name = match (el_name, name) { + (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => quote! { #el_name::#name.0 }, + _ => { + //hmmmm I think we could just totokens this, but the to_string might be inserting quotes + let as_string = name.to_string(); + quote! { #as_string } + } + }; + + quote! { + dioxus_core::TemplateAttribute::Static { + name: #name, + namespace: #ns, + value: #value, + + // todo: we don't diff these so we never apply the volatile flag + // volatile: dioxus_elements::#el_name::#name.2, + }, + } + } + + /// If the attr is dynamic, we save it to the tracked attributes list + /// This will let us use this context at a later point in time to update the template + fn render_dynamic_attr(&mut self, attr: &'a AttributeType) -> TokenStream2 { + let ct = self.dynamic_attributes.len(); + + self.dynamic_attributes.push(vec![attr]); + self.attr_paths.push(self.current_path.clone()); + + quote! { dioxus_core::TemplateAttribute::Dynamic { id: #ct }, } + } + + #[cfg(feature = "hot_reload")] + pub fn update_node( + &mut self, + root: &'a BodyNode, + ) -> Option { + match root { + // The user is moving a static node around in the template + // Check this is a bit more complex but we can likely handle it + BodyNode::Element(el) => self.update_element::(el), + + BodyNode::Text(text) if text.is_static() => { + let text = text.source.as_ref().unwrap(); + let text = intern(text.value().as_str()); + Some(TemplateNode::Text { text }) + } + + // The user is moving a dynamic node around in the template + // We *might* be able to handle it, but you never really know + BodyNode::RawExpr(_) + | BodyNode::Text(_) + | BodyNode::ForLoop(_) + | BodyNode::IfChain(_) + | BodyNode::Component(_) => self.update_dynamic_node(root), + } + } + + /// Attempt to update a dynamic node in the template + /// + /// If the change between the old and new template results in a mapping that doesn't exist, then we need to bail out. + /// Basically if we *had* a mapping of `[0, 1]` and the new template is `[1, 2]`, then we need to bail out, since + /// the new mapping doesn't exist in the original. + fn update_dynamic_node(&mut self, root: &'a BodyNode) -> Option { + let idx = match self.has_tracked_nodes() { + // Bail out if the mapping doesn't exist + // The user put it new code in the template, and that code is not hotreloadable + true => self.tracked_node_idx(root)?, + false => self.dynamic_nodes.len(), + }; + + // Put the node in the dynamic nodes list + self.dynamic_nodes.push(root); + + // Fill in as many paths as we need - might have to fill in more since the old tempate shrunk some and let some paths be empty + if self.node_paths.len() <= idx { + self.node_paths.resize_with(idx + 1, Vec::new); + } + + // And then set the path of this node to the current path (which we're hitting during traversal) + self.node_paths[idx] = self.current_path.clone(); + + Some(match root { + BodyNode::Text(_) => TemplateNode::DynamicText { id: idx }, + _ => TemplateNode::Dynamic { id: idx }, + }) + } + + fn update_element( + &mut self, + el: &'a Element, + ) -> Option { + let rust_name = el.name.to_string(); + + let mut static_attr_array = Vec::new(); + + for attr in &el.merged_attributes { + let template_attr = match attr.as_static_str_literal() { + // For static attributes, we don't need to pull in any mapping or anything + // We can just build them directly + Some((name, value)) => Self::make_static_attribute::(value, name, &rust_name), + + // For dynamic attributes, we need to check the mapping to see if that mapping exists + // todo: one day we could generate new dynamic attributes on the fly if they're a literal, + // or something sufficiently serializable + // (ie `checked`` being a bool and bools being interpretable) + // + // For now, just give up if that attribute doesn't exist in the mapping + None => { + let id = self.update_dynamic_attribute(attr)?; + TemplateAttribute::Dynamic { id } + } + }; + + static_attr_array.push(template_attr); + } + + let children = self.populate_by_updating::(el.children.as_slice())?; + + let (tag, namespace) = + Ctx::map_element(&rust_name).unwrap_or((intern(rust_name.as_str()), None)); + + Some(TemplateNode::Element { + tag, + namespace, + attrs: intern(static_attr_array.into_boxed_slice()), + children: intern(children.as_slice()), + }) + } + + fn update_dynamic_attribute(&mut self, attr: &'a AttributeType) -> Option { + let idx = match self.has_tracked_nodes() { + true => self.tracked_attribute_idx(attr)?, + false => self.dynamic_attributes.len(), + }; + + self.dynamic_attributes.push(vec![attr]); + if self.attr_paths.len() <= idx { + self.attr_paths.resize_with(idx + 1, Vec::new); + } + + self.attr_paths[idx] = self.current_path.clone(); + + Some(idx) + } + + fn make_static_attribute( + value: &IfmtInput, + name: &ElementAttrName, + element_name_rust: &str, + ) -> TemplateAttribute { + let value = value.source.as_ref().unwrap(); + let attribute_name_rust = name.to_string(); + let (name, namespace) = Ctx::map_attribute(element_name_rust, &attribute_name_rust) + .unwrap_or((intern(attribute_name_rust.as_str()), None)); + + let static_attr = TemplateAttribute::Static { + name, + namespace, + value: intern(value.value().as_str()), + }; + + static_attr + } + + /// Check if we're tracking any nodes + /// + /// If we're tracking, then we'll attempt to use the old mapping + fn has_tracked_nodes(&self) -> bool { + self.is_tracking + } + + /// Track a BodyNode + /// + /// This will save the any dynamic nodes that we find. + /// We need to be careful around saving if/for/components since we want to hotreload their contents + /// provided that their rust portions haven't changed. + pub(crate) fn track_node(&mut self, node: BodyNode) { + match node { + // If the node is a static element, we just want to merge its attributes into the dynamic mapping + BodyNode::Element(el) => self.track_element(el), + + // We skip static nodes since they'll get written out by the template during the diffing phase + BodyNode::Text(text) if text.is_static() => {} + + BodyNode::RawExpr(_) + | BodyNode::Text(_) + | BodyNode::ForLoop(_) + | BodyNode::IfChain(_) + | BodyNode::Component(_) => { + self.track_dynamic_node(node); + } + } + } + + fn track_element(&mut self, el: Element) { + for attr in el.merged_attributes { + // If the attribute is a static string literal, we don't need to insert it since the attribute + // will be written out during the diffing phase (since it's static) + if !attr.is_static_str_literal() { + self.track_attribute(attr); + } + } + + for child in el.children { + self.track_node(child); + } + } + + pub(crate) fn track_attribute(&mut self, attr: AttributeType) -> usize { + let idx = self.last_attribute_idx; + self.last_attribute_idx += 1; + self.attribute_to_idx.entry(attr).or_default().push(idx); + idx + } + + pub(crate) fn track_dynamic_node(&mut self, node: BodyNode) -> usize { + let idx = self.last_element_idx; + self.last_element_idx += 1; + self.node_to_idx.entry(node).or_default().push(idx); + idx + } + + pub(crate) fn tracked_attribute_idx(&mut self, attr: &AttributeType) -> Option { + self.attribute_to_idx + .get_mut(attr) + .and_then(|idxs| idxs.pop()) + } + + pub(crate) fn tracked_node_idx(&mut self, node: &BodyNode) -> Option { + self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop()) + } +} diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index c3b1a9c7f..df2c1124d 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -1,3 +1,7 @@ +use super::{ + hot_reload_diff::{diff_rsx, DiffResult}, + ChangedRsx, +}; use crate::{CallBody, HotReloadingContext}; use dioxus_core::{ prelude::{TemplateAttribute, TemplateNode}, @@ -9,17 +13,11 @@ pub use proc_macro2::TokenStream; pub use std::collections::HashMap; pub use std::sync::Mutex; pub use std::time::SystemTime; -use std::{collections::HashSet, ffi::OsStr, path::PathBuf}; +use std::{collections::HashSet, ffi::OsStr, marker::PhantomData, path::PathBuf}; pub use std::{fs, io, path::Path}; pub use std::{fs::File, io::Read}; -pub use syn::__private::ToTokens; use syn::spanned::Spanned; -use super::{ - hot_reload_diff::{diff_rsx, DiffResult}, - ChangedRsx, -}; - pub enum UpdateResult { UpdatedRsx(Vec