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
This commit is contained in:
slhx agent
2026-05-26 01:24:03 +02:00
parent d3fc1a745a
commit 280464017b
+49 -8
View File
@@ -14,6 +14,7 @@ pub struct AppBuilder {
out_dir: Option<PathBuf>, out_dir: Option<PathBuf>,
template_dir: PathBuf, template_dir: PathBuf,
global_exports: bool, global_exports: bool,
surfaces: Vec<(PathBuf, SurfaceDocument)>,
} }
impl AppBuilder { impl AppBuilder {
@@ -32,6 +33,11 @@ impl AppBuilder {
self self
} }
pub fn surface(mut self, path: impl Into<PathBuf>, surface: SurfaceDocument) -> Self {
self.surfaces.push((path.into(), surface));
self
}
pub fn run(self) -> io::Result<()> { pub fn run(self) -> io::Result<()> {
println!("cargo:rerun-if-changed={}", self.template_dir.display()); println!("cargo:rerun-if-changed={}", self.template_dir.display());
@@ -46,14 +52,21 @@ impl AppBuilder {
std::fs::create_dir_all(&out_dir)?; std::fs::create_dir_all(&out_dir)?;
let mut resources = Resources::default(); let mut resources = Resources::default();
for path in collect_heml(&self.template_dir)? { if self.surfaces.is_empty() {
let source = std::fs::read_to_string(&path)?; for path in collect_heml(&self.template_dir)? {
let doc = build_ast(Arc::new(source)).map_err(|err| parse_error(&path, err))?; let source = std::fs::read_to_string(&path)?;
let Some(doc) = doc else { let doc = build_ast(Arc::new(source)).map_err(|err| parse_error(&path, err))?;
continue; let Some(doc) = doc else {
}; continue;
let surface = extract_surface(&doc); };
resources.add_surface(&self.template_dir, &path, &surface)?; 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)? { for path in collect_stylesheets(&self.template_dir)? {
let source = std::fs::read_to_string(&path)?; let source = std::fs::read_to_string(&path)?;
@@ -74,6 +87,7 @@ pub fn app() -> AppBuilder {
out_dir: None, out_dir: None,
template_dir: PathBuf::from("templates"), template_dir: PathBuf::from("templates"),
global_exports: false, global_exports: false,
surfaces: Vec::new(),
} }
} }
@@ -1090,6 +1104,33 @@ mod tests {
let _ = std::fs::remove_dir_all(&base); 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#"<button data-slhx-handle="create" data-slhx-on="click">Create</button><div data-slhx-slot="todos"></div>"#
.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] #[test]
fn global_resource_exports_are_explicit_opt_in() { fn global_resource_exports_are_explicit_opt_in() {
// req: component/003 // req: component/003