fix(build): dispatch slot targets to matching component render/put

req: runtime/005

req: examples/001

req: htmx_equivalents/001
This commit is contained in:
slhx agent
2026-06-23 15:06:08 +02:00
parent cfa4502748
commit 329f16aebf
5 changed files with 131 additions and 23 deletions
+76 -22
View File
@@ -539,16 +539,20 @@ impl Resources {
));
// req: build/001 req: component/003
let component_names: BTreeSet<String> = self.component_names().into_iter().collect();
if global_exports {
self.push_component_refs(&mut out, 0);
self.push_resource_modules(&mut out, None, 0);
self.push_resource_modules(&mut out, &component_names, None, 0);
}
self.push_lowering_api(&mut out, None, 0);
for component in self.component_names() {
out.push_str("\n#[allow(non_upper_case_globals)]\n");
out.push_str(&format!("pub mod {component} {{\n"));
self.push_resource_modules(&mut out, Some(&component), 1);
out.push_str(" #[derive(Clone, Copy)]\n");
out.push_str(" pub struct Component;\n\n");
self.push_resource_modules(&mut out, &component_names, Some(&component), 1);
self.push_lowering_api(&mut out, Some(&component), 1);
out.push_str("}\n");
}
@@ -572,7 +576,13 @@ impl Resources {
out.push_str(&format!("{pad}}}\n"));
}
fn push_resource_modules(&self, out: &mut String, component: Option<&str>, indent: usize) {
fn push_resource_modules(
&self,
out: &mut String,
components: &BTreeSet<String>,
component: Option<&str>,
indent: usize,
) {
let pad = " ".repeat(indent);
let inner = " ".repeat(indent + 1);
let mut handle_ids = Vec::new();
@@ -613,34 +623,53 @@ impl Resources {
out.push_str(&format!(
"{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod targets {{\n"
));
let child_prefix = if component.is_some() {
"super::super::"
} else {
"super::"
};
out.push_str(&format!("{inner}#[derive(Clone, Copy)]\n"));
out.push_str(&format!(
"{inner}pub struct SlotTarget<T> {{ slot: ::hemx::advanced::Slot<T> }}\n"
"{inner}pub struct SlotTarget<T, C = ()> {{ slot: ::hemx::advanced::Slot<T>, _marker: ::std::marker::PhantomData<C> }}\n"
));
out.push_str(&format!("{inner}#[allow(dead_code)]\n"));
out.push_str(&format!("{inner}impl<T> SlotTarget<T> {{\n"));
out.push_str(&format!("{inner} const fn new(slot: ::hemx::advanced::Slot<T>) -> Self {{ Self {{ slot }} }}\n"));
out.push_str(&format!("{inner} pub fn put(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ super::put(self.slot, view) }}\n"));
out.push_str(&format!("{inner} pub fn replace(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ super::put(self.slot, view) }}\n"));
out.push_str(&format!("{inner}impl<T, C> SlotTarget<T, C> {{\n"));
out.push_str(&format!("{inner} const fn new(slot: ::hemx::advanced::Slot<T>) -> Self {{ Self {{ slot, _marker: ::std::marker::PhantomData }} }}\n"));
out.push_str(&format!("{inner} pub fn text(self, value: impl ::std::string::ToString) -> ::hemx::advanced::Effect {{ self.slot.text(value) }}\n"));
out.push_str(&format!("{inner} pub fn set(self, value: impl ::std::string::ToString) -> ::hemx::advanced::Effect {{ self.slot.text(value) }}\n"));
out.push_str(&format!("{inner}}}\n"));
out.push_str(&format!("{inner}impl<T> SlotTarget<T, ()> {{\n"));
out.push_str(&format!("{inner} pub fn put(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ super::put(self.slot, view) }}\n"));
out.push_str(&format!("{inner} pub fn replace(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ super::put(self.slot, view) }}\n"));
out.push_str(&format!("{inner}}}\n"));
for res in self
.slots
.values()
.filter(|res| component_matches(res, component))
.filter(|res| components.contains(&res.ident) && Some(res.ident.as_str()) != component)
{
let child = &res.ident;
out.push_str(&format!("{inner}impl<T> SlotTarget<T, {child_prefix}{child}::Component> {{\n"));
out.push_str(&format!("{inner} pub fn put(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ {child_prefix}{child}::put(self.slot, view) }}\n"));
out.push_str(&format!("{inner} pub fn replace(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ {child_prefix}{child}::put(self.slot, view) }}\n"));
out.push_str(&format!("{inner}}}\n"));
}
out.push_str(&format!(
"{inner}impl<T> ::hemx::GeneratedTarget for SlotTarget<T> {{\n"
"{inner}impl<T, C> ::hemx::GeneratedTarget for SlotTarget<T, C> {{\n"
));
out.push_str(&format!("{inner} fn __hemx_resource_id(self) -> ::hemx::advanced::ResourceId {{ self.slot.id() }}\n"));
out.push_str(&format!("{inner}}}\n"));
out.push_str(&format!("{inner}#[derive(Clone, Copy)]\n"));
out.push_str(&format!("{inner}pub struct KeyedSlotTarget<K, T> {{ slot: ::hemx::advanced::KeyedSlot<K, T> }}\n"));
out.push_str(&format!("{inner}pub struct KeyedSlotTarget<K, T, C = ()> {{ slot: ::hemx::advanced::KeyedSlot<K, T>, _marker: ::std::marker::PhantomData<C> }}\n"));
out.push_str(&format!("{inner}#[allow(dead_code)]\n"));
out.push_str(&format!("{inner}impl<K, T> KeyedSlotTarget<K, T>\n"));
out.push_str(&format!("{inner}impl<K, T, C> KeyedSlotTarget<K, T, C>\n"));
out.push_str(&format!("{inner}where\n"));
out.push_str(&format!("{inner} K: ::std::string::ToString,\n"));
out.push_str(&format!("{inner}{{\n"));
out.push_str(&format!("{inner} const fn new(slot: ::hemx::advanced::KeyedSlot<K, T>) -> Self {{ Self {{ slot }} }}\n"));
out.push_str(&format!("{inner} const fn new(slot: ::hemx::advanced::KeyedSlot<K, T>) -> Self {{ Self {{ slot, _marker: ::std::marker::PhantomData }} }}\n"));
out.push_str(&format!("{inner}}}\n"));
out.push_str(&format!(
"{inner}impl<T> KeyedSlotTarget<::std::string::String, T> {{\n"
"{inner}impl<T> KeyedSlotTarget<::std::string::String, T, ()> {{\n"
));
out.push_str(&format!("{inner} pub fn append(self, view: impl ::hemplate::Hemplate + ::hemx::KeyedPartial) -> ::hemx::advanced::Effect {{ self.slot.append_html(view.hemx_key(), super::render(&view)) }}\n"));
out.push_str(&format!("{inner} pub fn prepend(self, view: impl ::hemplate::Hemplate + ::hemx::KeyedPartial) -> ::hemx::advanced::Effect {{ self.slot.prepend_html(view.hemx_key(), super::render(&view)) }}\n"));
@@ -650,8 +679,28 @@ impl Resources {
out.push_str(&format!("{inner} pub fn replace_keyed(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ self.slot.replace_html(key.to_string(), super::render(view)) }}\n"));
out.push_str(&format!("{inner} pub fn remove(self, key: impl ::std::string::ToString) -> ::hemx::advanced::Effect {{ self.slot.remove(key.to_string()) }}\n"));
out.push_str(&format!("{inner}}}\n"));
for res in self
.slots
.values()
.filter(|res| component_matches(res, component))
.filter(|res| res.keyed)
.filter(|res| components.contains(&res.ident) && Some(res.ident.as_str()) != component)
{
let child = &res.ident;
out.push_str(&format!(
"{inner}impl<T> KeyedSlotTarget<::std::string::String, T, {child_prefix}{child}::Component> {{\n"
));
out.push_str(&format!("{inner} pub fn append(self, view: impl ::hemplate::Hemplate + ::hemx::KeyedPartial) -> ::hemx::advanced::Effect {{ self.slot.append_html(view.hemx_key(), {child_prefix}{child}::render(&view)) }}\n"));
out.push_str(&format!("{inner} pub fn prepend(self, view: impl ::hemplate::Hemplate + ::hemx::KeyedPartial) -> ::hemx::advanced::Effect {{ self.slot.prepend_html(view.hemx_key(), {child_prefix}{child}::render(&view)) }}\n"));
out.push_str(&format!("{inner} pub fn replace(self, view: impl ::hemplate::Hemplate + ::hemx::KeyedPartial) -> ::hemx::advanced::Effect {{ self.slot.replace_html(view.hemx_key(), {child_prefix}{child}::render(&view)) }}\n"));
out.push_str(&format!("{inner} pub fn append_keyed(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ self.slot.append_html(key.to_string(), {child_prefix}{child}::render(view)) }}\n"));
out.push_str(&format!("{inner} pub fn prepend_keyed(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ self.slot.prepend_html(key.to_string(), {child_prefix}{child}::render(view)) }}\n"));
out.push_str(&format!("{inner} pub fn replace_keyed(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect {{ self.slot.replace_html(key.to_string(), {child_prefix}{child}::render(view)) }}\n"));
out.push_str(&format!("{inner} pub fn remove(self, key: impl ::std::string::ToString) -> ::hemx::advanced::Effect {{ self.slot.remove(key.to_string()) }}\n"));
out.push_str(&format!("{inner}}}\n"));
}
out.push_str(&format!(
"{inner}impl<K: ::std::string::ToString, T> ::hemx::GeneratedTarget for KeyedSlotTarget<K, T> {{\n"
"{inner}impl<K: ::std::string::ToString, T, C> ::hemx::GeneratedTarget for KeyedSlotTarget<K, T, C> {{\n"
));
out.push_str(&format!("{inner} fn __hemx_resource_id(self) -> ::hemx::advanced::ResourceId {{ self.slot.id() }}\n"));
out.push_str(&format!("{inner}}}\n"));
@@ -660,14 +709,19 @@ impl Resources {
.values()
.filter(|res| component_matches(res, component))
{
let marker = if components.contains(&res.ident) && Some(res.ident.as_str()) != component {
format!("{child_prefix}{}::Component", res.ident)
} else {
"()".to_string()
};
if res.keyed {
out.push_str(&format!(
"{inner}pub const {}: KeyedSlotTarget<::std::string::String, ::std::string::String> = KeyedSlotTarget::new(super::advanced::slots::{});\n",
"{inner}pub const {}: KeyedSlotTarget<::std::string::String, ::std::string::String, {marker}> = KeyedSlotTarget::new(super::advanced::slots::{});\n",
res.ident, res.ident
));
} else {
out.push_str(&format!(
"{inner}pub const {}: SlotTarget<::std::string::String> = SlotTarget::new(super::advanced::slots::{});\n",
"{inner}pub const {}: SlotTarget<::std::string::String, {marker}> = SlotTarget::new(super::advanced::slots::{});\n",
res.ident, res.ident
));
}
@@ -2127,10 +2181,10 @@ mod tests {
assert!(generated.contains("pub mod slots"));
assert!(generated.contains("pub const todos"));
assert!(generated.contains("pub mod targets"));
assert!(generated.contains("pub struct SlotTarget<T>"));
assert!(generated.contains("impl<T> ::hemx::GeneratedTarget for SlotTarget<T>"));
assert!(generated.contains("pub struct SlotTarget<T, C = ()>"));
assert!(generated.contains("impl<T, C> ::hemx::GeneratedTarget for SlotTarget<T, C>"));
assert!(generated.contains(
"impl<K: ::std::string::ToString, T> ::hemx::GeneratedTarget for KeyedSlotTarget<K, T>"
"impl<K: ::std::string::ToString, T, C> ::hemx::GeneratedTarget for KeyedSlotTarget<K, T, C>"
));
assert!(generated.contains(
"pub fn append(self, view: impl ::hemplate::Hemplate + ::hemx::KeyedPartial) -> ::hemx::advanced::Effect"
@@ -2138,7 +2192,7 @@ mod tests {
assert!(generated.contains(
"pub fn append_keyed(self, key: impl ::std::string::ToString, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect"
));
assert!(generated.contains("pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains("pub const todos: SlotTarget<::std::string::String, ()> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains("pub use self::targets::todos;"));
assert!(generated.contains(
"pub fn put(self, view: &impl ::hemplate::Hemplate) -> ::hemx::advanced::Effect"
@@ -2253,7 +2307,7 @@ mod tests {
assert!(generated.contains("pub mod todo"));
assert!(generated.contains("pub const create: ::hemx::Handle<()> = ::hemx::Handle::new("));
assert!(generated.contains("pub const todos: ::hemx::advanced::Slot<::std::string::String> = ::hemx::advanced::Slot::new("));
assert!(generated.contains("pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains("pub const todos: SlotTarget<::std::string::String, ()> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains("pub use self::targets::todos;"));
assert!(generated
.contains("pub const click: ::hemx::EventName = ::hemx::EventName::new(\"click\")"));
@@ -2301,7 +2355,7 @@ mod tests {
assert!(generated.contains(" pub mod advanced"));
assert!(generated.contains(" pub mod slots"));
assert!(generated.contains(" pub mod targets"));
assert!(generated.contains(" pub const todos: SlotTarget<::std::string::String> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains(" pub const todos: SlotTarget<::std::string::String, ()> = SlotTarget::new(super::advanced::slots::todos);"));
assert!(generated.contains(" pub use self::targets::todos;"));
assert!(generated.contains(" #[doc(hidden)]\n pub fn lower_html"));
assert!(generated.contains(