From 280464017b6987053d9cb4a13acc846a288335d9 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Tue, 26 May 2026 01:24:03 +0200 Subject: [PATCH] feat(build): accept precomputed surface facts Allow slhx-build callers to feed hemplate SurfaceDocument facts directly, preserving the existing .heml parse fallback while covering the build-from-surface path. req: surface/008 req: build/001 --- slhx-build/src/lib.rs | 57 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/slhx-build/src/lib.rs b/slhx-build/src/lib.rs index 1a5c459..83507ba 100644 --- a/slhx-build/src/lib.rs +++ b/slhx-build/src/lib.rs @@ -14,6 +14,7 @@ pub struct AppBuilder { out_dir: Option, template_dir: PathBuf, global_exports: bool, + surfaces: Vec<(PathBuf, SurfaceDocument)>, } impl AppBuilder { @@ -32,6 +33,11 @@ impl AppBuilder { self } + pub fn surface(mut self, path: impl Into, surface: SurfaceDocument) -> Self { + self.surfaces.push((path.into(), surface)); + self + } + pub fn run(self) -> io::Result<()> { println!("cargo:rerun-if-changed={}", self.template_dir.display()); @@ -46,14 +52,21 @@ impl AppBuilder { std::fs::create_dir_all(&out_dir)?; let mut resources = Resources::default(); - for path in collect_heml(&self.template_dir)? { - let source = std::fs::read_to_string(&path)?; - let doc = build_ast(Arc::new(source)).map_err(|err| parse_error(&path, err))?; - let Some(doc) = doc else { - continue; - }; - let surface = extract_surface(&doc); - resources.add_surface(&self.template_dir, &path, &surface)?; + if self.surfaces.is_empty() { + for path in collect_heml(&self.template_dir)? { + let source = std::fs::read_to_string(&path)?; + let doc = build_ast(Arc::new(source)).map_err(|err| parse_error(&path, err))?; + let Some(doc) = doc else { + continue; + }; + let surface = extract_surface(&doc); + resources.add_surface(&self.template_dir, &path, &surface)?; + } + } else { + // req: surface/008 + for (path, surface) in &self.surfaces { + resources.add_surface(&self.template_dir, path, surface)?; + } } for path in collect_stylesheets(&self.template_dir)? { let source = std::fs::read_to_string(&path)?; @@ -74,6 +87,7 @@ pub fn app() -> AppBuilder { out_dir: None, template_dir: PathBuf::from("templates"), global_exports: false, + surfaces: Vec::new(), } } @@ -1090,6 +1104,33 @@ mod tests { let _ = std::fs::remove_dir_all(&base); } + #[test] + fn app_can_consume_precomputed_surface_facts() { + // req: surface/008 req: build/001 + let base = test_dir("slhx-build-precomputed-surface-test"); + let templates = base.join("templates"); + let out = base.join("out"); + let _ = std::fs::remove_dir_all(&base); + std::fs::create_dir_all(&templates).unwrap(); + let path = templates.join("todo.heml"); + let source = Arc::new( + r#"
"# + .to_string(), + ); + let ast = build_ast(source).unwrap().unwrap(); + let surface = extract_surface(&ast); + + app().template_dir(&templates).out_dir(&out).surface(&path, surface).run().unwrap(); + + let generated = std::fs::read_to_string(out.join("slhx.generated.rs")).unwrap(); + assert!(generated.contains("pub mod todo")); + assert!(generated.contains("pub const create: ::slhx::Handle<()> = ::slhx::Handle::new(")); + assert!(generated.contains("pub const todos: ::slhx::Slot<::std::string::String> = ::slhx::Slot::new(")); + assert!(generated.contains("pub const click: ::slhx::EventName = ::slhx::EventName::new(\"click\")")); + + let _ = std::fs::remove_dir_all(&base); + } + #[test] fn global_resource_exports_are_explicit_opt_in() { // req: component/003