feat(build): generate render helpers

Generate root and component render_html helpers that render hemplate views through the same lowering table, letting callers update composed views without manual render_template/lower_html/SafeHtml plumbing.

req: component/003

req: view/001

req: html_safety/002
This commit is contained in:
slhx agent
2026-06-01 22:15:58 +02:00
parent 4319dea171
commit 4d79dd4dba
2 changed files with 23 additions and 7 deletions
+4 -5
View File
@@ -502,9 +502,8 @@ fn board_view(demo: &DemoState) -> BoardLanes {
}
fn render_board(demo: &DemoState) -> SafeHtml {
// req: html_safety/002 req: view/001
let html = render_template(&board_view(demo));
SafeHtml::trusted(ui::issue_lane::lower_html(ui::issue_card::lower_html(html)))
// req: html_safety/002 req: view/001 req: component/003
ui::render_html(&board_view(demo))
}
fn issue_card(item: &WorkItem, selected: bool) -> IssueCard {
@@ -584,8 +583,8 @@ fn architecture_activity() -> SafeHtml {
}
fn render_control_center(page: ControlCenter) -> String {
// req: html_safety/002 req: view/001
ui::control_center::lower_html(render_template(&page))
// req: html_safety/002 req: view/001 req: component/003
ui::control_center::render_html(&page).into_string()
}
fn render_template(template: &impl Hemplate) -> String {
+19 -2
View File
@@ -351,8 +351,8 @@ impl Resources {
if global_exports {
self.push_component_refs(&mut out, 0);
self.push_resource_modules(&mut out, None, 0);
self.push_lowering_api(&mut out, 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");
@@ -524,6 +524,11 @@ impl Resources {
}
out.push_str(&format!("__slhx_lower_html(html.as_ref(), &{table_name})\n"));
out.push_str(&format!("{pad}}}\n\n"));
out.push_str(&format!("{pad}pub fn render_html(view: &impl ::hemplate::Hemplate) -> ::slhx::SafeHtml {{\n"));
out.push_str(&format!("{pad} let mut html = ::std::string::String::new();\n"));
out.push_str(&format!("{pad} ::hemplate::Hemplate::render_into(view, &mut html).expect(\"slhx hemplate view renders\");\n"));
out.push_str(&format!("{pad} ::slhx::SafeHtml::trusted(lower_html(html))\n"));
out.push_str(&format!("{pad}}}\n\n"));
self.push_lowering_table(out, component, indent, &table_name);
}
@@ -1293,6 +1298,7 @@ mod tests {
assert!(generated.contains("pub mod todo"));
assert!(generated.contains("pub const ALL_IDS"));
assert!(generated.contains("pub fn lower_html"));
assert!(generated.contains("pub fn render_html(view: &impl ::hemplate::Hemplate) -> ::slhx::SafeHtml"));
assert!(generated.contains("data-slhx-slot"));
assert!(generated.contains("data-slhx-form"));
assert!(generated.contains("data-sid"));
@@ -1357,7 +1363,8 @@ mod tests {
let generated = std::fs::read_to_string(out.join("slhx.generated.rs")).unwrap();
assert!(!generated.contains("\n#[allow(non_upper_case_globals)]\npub mod components"));
assert!(!generated.contains("\n#[allow(non_upper_case_globals)]\npub mod slots"));
assert!(!generated.contains("\npub fn lower_html"));
assert!(generated.contains("\npub fn lower_html"));
assert!(generated.contains("\npub fn render_html(view: &impl ::hemplate::Hemplate) -> ::slhx::SafeHtml"));
assert!(generated.contains("pub mod todo"));
assert!(generated.contains(" pub const COMPONENT: ::slhx::ComponentRef = ::slhx::ComponentRef::new(\"todo\")"));
assert!(generated.contains(" pub mod slots"));
@@ -1476,10 +1483,15 @@ mod slhx {{
impl CssClass {{ pub const fn new(name: &'static str) -> Self {{ Self(name) }} pub const fn as_str(self) -> &'static str {{ self.0 }} }}
#[derive(Clone, Copy)] pub struct ComponentRef(&'static str);
impl ComponentRef {{ pub const fn new(name: &'static str) -> Self {{ Self(name) }} pub const fn as_str(self) -> &'static str {{ self.0 }} }}
pub struct SafeHtml(String);
impl SafeHtml {{ pub fn trusted(html: impl Into<String>) -> Self {{ Self(html.into()) }} }}
pub struct FormContract {{ pub fields: &'static [FormField] }}
pub struct FormField {{ pub name: &'static str, pub kind: FormControlKind, pub required: bool }}
pub enum FormControlKind {{ Text, Number {{ min: Option<&'static str>, max: Option<&'static str>, step: Option<&'static str> }}, Checkbox, Radio, Select {{ multiple: bool }}, TextArea, File, Hidden, Submit, Other {{ tag: &'static str, input_type: Option<&'static str> }} }}
}}
mod hemplate {{
pub trait Hemplate {{ fn render_into(&self, out: &mut String) -> Result<(), ()>; }}
}}
{generated}
fn main() {{
assert_eq!(classes::drag_handle.as_str(), "drag-handle");
@@ -1743,10 +1755,15 @@ mod slhx {{
impl<T> Form<T> {{ pub const fn new(_: u32) -> Self {{ Self(::std::marker::PhantomData) }} }}
#[derive(Clone, Copy)] pub struct ComponentRef(&'static str);
impl ComponentRef {{ pub const fn new(name: &'static str) -> Self {{ Self(name) }} pub const fn as_str(self) -> &'static str {{ self.0 }} }}
pub struct SafeHtml(String);
impl SafeHtml {{ pub fn trusted(html: impl Into<String>) -> Self {{ Self(html.into()) }} }}
pub struct FormContract {{ pub fields: &'static [FormField] }}
pub struct FormField {{ pub name: &'static str, pub kind: FormControlKind, pub required: bool }}
pub enum FormControlKind {{ Text, Number {{ min: Option<&'static str>, max: Option<&'static str>, step: Option<&'static str> }}, Checkbox, Radio, Select {{ multiple: bool }}, TextArea, File, Hidden, Submit, Other {{ tag: &'static str, input_type: Option<&'static str> }} }}
}}
mod hemplate {{
pub trait Hemplate {{ fn render_into(&self, out: &mut String) -> Result<(), ()>; }}
}}
{generated}
fn main() {{
let html = todo::lower_html(r#"<form data-slhx-handle="create"><input name="title"></form><li data-slhx-slot="row" data-slhx-key="7"></li>"#);