use crate::innerlude::*;
use crate::HotReloadingContext;
use dioxus_core::internal::{
FmtedSegments, HotReloadAttributeValue, HotReloadDynamicAttribute, HotReloadDynamicNode,
HotReloadLiteral, HotReloadedTemplate, NamedAttribute,
};
use std::collections::HashMap;
use std::hash::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use super::last_build_state::LastBuildState;
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone)]
pub struct HotReloadResult {
full_rebuild_state: LastBuildState,
pub templates: HashMap<usize, HotReloadedTemplate>,
dynamic_nodes: Vec<HotReloadDynamicNode>,
dynamic_attributes: Vec<HotReloadDynamicAttribute>,
literal_component_properties: Vec<HotReloadLiteral>,
}
impl HotReloadResult {
pub fn new<Ctx: HotReloadingContext>(
full_rebuild_state: &TemplateBody,
new: &TemplateBody,
name: String,
) -> Option<Self> {
let full_rebuild_state = LastBuildState::new(full_rebuild_state, name);
let mut s = Self {
full_rebuild_state,
templates: Default::default(),
dynamic_nodes: Default::default(),
dynamic_attributes: Default::default(),
literal_component_properties: Default::default(),
};
s.hotreload_body::<Ctx>(new)?;
Some(s)
}
fn extend(&mut self, other: Self) {
self.templates.extend(other.templates);
}
fn hotreload_body<Ctx: HotReloadingContext>(&mut self, new: &TemplateBody) -> Option<()> {
self.hotreload_attributes::<Ctx>(new)?;
let new_dynamic_attributes = std::mem::take(&mut self.dynamic_attributes);
self.hotreload_dynamic_nodes::<Ctx>(new)?;
let new_dynamic_nodes = std::mem::take(&mut self.dynamic_nodes);
let literal_component_properties = std::mem::take(&mut self.literal_component_properties);
let key = self.hot_reload_key(new)?;
let roots: Vec<_> = new
.roots
.iter()
.map(|node| node.to_template_node::<Ctx>())
.collect();
let roots: &[dioxus_core::TemplateNode] = intern(&*roots);
let name = {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
new_dynamic_attributes.hash(&mut hasher);
new_dynamic_nodes.hash(&mut hasher);
literal_component_properties.hash(&mut hasher);
roots.hash(&mut hasher);
let hash = hasher.finish();
let name = &self.full_rebuild_state.name;
format!("{}:{}-{}", name, hash, new.template_idx.get())
};
let name = Box::leak(name.into_boxed_str());
let template = HotReloadedTemplate::new(
name,
key,
new_dynamic_nodes,
new_dynamic_attributes,
literal_component_properties,
roots,
);
self.templates
.insert(self.full_rebuild_state.root_index.get(), template);
Some(())
}
fn hot_reload_key(&mut self, new: &TemplateBody) -> Option<Option<FmtedSegments>> {
match new.implicit_key() {
Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(value))) => Some(Some(
self.full_rebuild_state
.hot_reload_formatted_segments(value)?,
)),
None => Some(None),
_ => None,
}
}
fn hotreload_dynamic_nodes<Ctx: HotReloadingContext>(
&mut self,
new: &TemplateBody,
) -> Option<()> {
for new_node in new.dynamic_nodes() {
self.hot_reload_node::<Ctx>(new_node)?
}
Some(())
}
fn hot_reload_node<Ctx: HotReloadingContext>(&mut self, node: &BodyNode) -> Option<()> {
match node {
BodyNode::Text(text) => self.hotreload_text_node(text),
BodyNode::Component(component) => self.hotreload_component::<Ctx>(component),
BodyNode::ForLoop(forloop) => self.hotreload_for_loop::<Ctx>(forloop),
BodyNode::IfChain(ifchain) => self.hotreload_if_chain::<Ctx>(ifchain),
BodyNode::RawExpr(expr) => self.hotreload_raw_expr(expr),
BodyNode::Element(_) => Some(()),
}
}
fn hotreload_raw_expr(&mut self, expr: &ExprNode) -> Option<()> {
let expr_index = self
.full_rebuild_state
.dynamic_nodes
.position(|node| match &node {
BodyNode::RawExpr(raw_expr) => raw_expr.expr == expr.expr,
_ => false,
})?;
self.dynamic_nodes
.push(HotReloadDynamicNode::Dynamic(expr_index));
Some(())
}
fn hotreload_for_loop<Ctx>(&mut self, forloop: &ForLoop) -> Option<()>
where
Ctx: HotReloadingContext,
{
let candidate_for_loops = self
.full_rebuild_state
.dynamic_nodes
.inner
.iter()
.enumerate()
.filter_map(|(index, node)| {
if let BodyNode::ForLoop(for_loop) = &node.inner {
if for_loop.pat == forloop.pat && for_loop.expr == forloop.expr {
return Some((index, for_loop));
}
}
None
})
.collect::<Vec<_>>();
let (index, best_call_body) = self.diff_best_call_body::<Ctx>(
candidate_for_loops
.iter()
.map(|(_, for_loop)| &for_loop.body),
&forloop.body,
)?;
self.dynamic_nodes
.push(HotReloadDynamicNode::Dynamic(candidate_for_loops[index].0));
self.extend(best_call_body);
Some(())
}
fn hotreload_text_node(&mut self, text_node: &TextNode) -> Option<()> {
if text_node.input.is_static() {
return Some(());
}
let formatted_segments = self
.full_rebuild_state
.hot_reload_formatted_segments(&text_node.input)?;
self.dynamic_nodes
.push(HotReloadDynamicNode::Formatted(formatted_segments));
Some(())
}
fn diff_best_call_body<'a, Ctx>(
&self,
bodies: impl Iterator<Item = &'a TemplateBody>,
new_call_body: &TemplateBody,
) -> Option<(usize, Self)>
where
Ctx: HotReloadingContext,
{
let mut best_score = usize::MAX;
let mut best_output = None;
for (index, body) in bodies.enumerate() {
if self.templates.contains_key(&body.template_idx.get()) {
continue;
}
if let Some(state) =
Self::new::<Ctx>(body, new_call_body, self.full_rebuild_state.name.clone())
{
let score = state.full_rebuild_state.unused_dynamic_items();
if score < best_score {
best_score = score;
best_output = Some((index, state));
}
}
}
best_output
}
fn hotreload_component<Ctx>(&mut self, component: &Component) -> Option<()>
where
Ctx: HotReloadingContext,
{
let components_with_matching_attributes: Vec<_> = self
.full_rebuild_state
.dynamic_nodes
.inner
.iter()
.enumerate()
.filter_map(|(index, node)| {
if let BodyNode::Component(comp) = &node.inner {
return Some((
index,
comp,
self.hotreload_component_fields(comp, component)?,
));
}
None
})
.collect();
let possible_bodies = components_with_matching_attributes
.iter()
.map(|(_, comp, _)| &comp.children);
let (index, new_body) =
self.diff_best_call_body::<Ctx>(possible_bodies, &component.children)?;
let (index, _, literal_component_properties) = &components_with_matching_attributes[index];
let index = *index;
self.full_rebuild_state.dynamic_nodes.inner[index]
.used
.set(true);
self.literal_component_properties
.extend(literal_component_properties.iter().cloned());
self.extend(new_body);
self.dynamic_nodes
.push(HotReloadDynamicNode::Dynamic(index));
Some(())
}
fn hotreload_component_fields(
&self,
old_component: &Component,
new_component: &Component,
) -> Option<Vec<HotReloadLiteral>> {
if new_component.name != old_component.name {
return None;
}
if new_component.fields.len() != old_component.fields.len() {
return None;
}
let mut new_fields = new_component.fields.clone();
new_fields.sort_by(|a, b| a.name.to_string().cmp(&b.name.to_string()));
let mut old_fields = old_component.fields.clone();
old_fields.sort_by(|a, b| a.name.to_string().cmp(&b.name.to_string()));
let mut literal_component_properties = Vec::new();
for (new_field, old_field) in new_fields.iter().zip(old_fields.iter()) {
if new_field.name != old_field.name {
return None;
}
match (&new_field.value, &old_field.value) {
(
AttributeValue::AttrLiteral(new_value),
AttributeValue::AttrLiteral(old_value),
) => {
if std::mem::discriminant(new_value) != std::mem::discriminant(old_value) {
return None;
}
let literal = self.full_rebuild_state.hotreload_hot_literal(new_value)?;
literal_component_properties.push(literal);
}
_ => {
if new_field.value != old_field.value {
return None;
}
}
}
}
Some(literal_component_properties)
}
fn hotreload_if_chain<Ctx: HotReloadingContext>(
&mut self,
new_if_chain: &IfChain,
) -> Option<()> {
let mut best_if_chain = None;
let mut best_score = usize::MAX;
let if_chains = self
.full_rebuild_state
.dynamic_nodes
.inner
.iter()
.enumerate()
.filter_map(|(index, node)| {
if let BodyNode::IfChain(if_chain) = &node.inner {
return Some((index, if_chain));
}
None
});
for (index, old_if_chain) in if_chains {
let Some(chain_templates) = Self::diff_if_chains::<Ctx>(
old_if_chain,
new_if_chain,
self.full_rebuild_state.name.clone(),
) else {
continue;
};
let score = chain_templates
.iter()
.map(|t| t.full_rebuild_state.unused_dynamic_items())
.sum();
if score < best_score {
best_score = score;
best_if_chain = Some((index, chain_templates));
}
}
let (index, chain_templates) = best_if_chain?;
self.full_rebuild_state.dynamic_nodes.inner[index]
.used
.set(true);
for template in chain_templates {
self.extend(template);
}
self.dynamic_nodes
.push(HotReloadDynamicNode::Dynamic(index));
Some(())
}
fn diff_if_chains<Ctx: HotReloadingContext>(
old_if_chain: &IfChain,
new_if_chain: &IfChain,
name: String,
) -> Option<Vec<Self>> {
let mut old_chain = old_if_chain;
let mut new_chain = new_if_chain;
let mut chain_templates = Vec::new();
loop {
if old_chain.cond != new_chain.cond {
return None;
}
let hot_reload =
Self::new::<Ctx>(&old_chain.then_branch, &new_chain.then_branch, name.clone())?;
chain_templates.push(hot_reload);
match (
old_chain.else_if_branch.as_ref(),
new_chain.else_if_branch.as_ref(),
) {
(Some(old), Some(new)) => {
old_chain = old;
new_chain = new;
}
(None, None) => {
break;
}
_ => return None,
}
}
match (&old_chain.else_branch, &new_chain.else_branch) {
(Some(old), Some(new)) => {
let template = Self::new::<Ctx>(old, new, name.clone())?;
chain_templates.push(template);
}
(None, None) => {}
_ => return None,
}
Some(chain_templates)
}
fn hotreload_attributes<Ctx: HotReloadingContext>(&mut self, new: &TemplateBody) -> Option<()> {
for new_attr in new.dynamic_attributes() {
self.hotreload_attribute::<Ctx>(new_attr)?;
}
Some(())
}
fn hotreload_attribute<Ctx: HotReloadingContext>(
&mut self,
attribute: &Attribute,
) -> Option<()> {
let (tag, namespace) = attribute.html_tag_and_namespace::<Ctx>();
if let AttributeName::Spread(_) = &attribute.name {
let hot_reload_attribute = self
.full_rebuild_state
.dynamic_attributes
.position(|a| a.name == attribute.name && a.value == attribute.value)?;
self.dynamic_attributes
.push(HotReloadDynamicAttribute::Dynamic(hot_reload_attribute));
return Some(());
}
let value = match &attribute.value {
AttributeValue::AttrLiteral(literal) => {
if literal.is_static() {
return Some(());
}
let hot_reload_literal = self.full_rebuild_state.hotreload_hot_literal(literal)?;
HotReloadAttributeValue::Literal(hot_reload_literal)
}
_ => {
let value_index = self.full_rebuild_state.dynamic_attributes.position(|a| {
!matches!(a.name, AttributeName::Spread(_)) && a.value == attribute.value
})?;
HotReloadAttributeValue::Dynamic(value_index)
}
};
self.dynamic_attributes
.push(HotReloadDynamicAttribute::Named(NamedAttribute::new(
tag, namespace, value,
)));
Some(())
}
}