perf(build): preserve no-op codegen artifacts
This commit is contained in:
@@ -62,7 +62,7 @@ Keep it stable. Prefer pointers to canonical sources over copied structure, file
|
||||
- `examples/html_examples` is the copy-paste HTML pattern gallery for htmx-style examples; keep exact htmx URL slugs visible while translating behavior to boring `.heml`, generated resources, and server-owned Rust state, not HTMX syntax, selector targeting, or user-authored browser JavaScript. Shared runtime loading and declarative `data-hemx-*` are allowed. Boost containers enhance same-origin descendants only and preserve native external/download/new-tab behavior. req: htmx_equivalents/001 req: htmx_equivalents/003 req: htmx_equivalents/005 req: examples/005 req: examples/007 req: examples/012 req: page_swap/007 req: page_swap/008
|
||||
- Use `cargo run -p hemx-xtask -- app new PATH` for the generic page/form/keyed-row/notice starter, and `cargo run -p hemx-xtask -- app new --mobile PATH` for the phone-first starter with host capabilities, recovery truth, and release-kit commands; do not treat it as a mobile framework or store-submission bot. req: ceremony/005 req: ceremony/006 req: ceremony/007
|
||||
- The public component-reuse explanation lives in `docs/recipes/reusable-partials.md`; do not grow a client component framework to explain partial composition.
|
||||
- The stable public `.heml` authoring surface lives in `docs/hemplate-syntax.md`; Hemlate examples must use that real hemplate syntax, not Vue/Handlebars sketches. hemx-build consumes hemplate Surface facts and must not grow an independent `.heml` parser or CSS-path identity model. Plain CSS/SCSS owns appearance; generated class constants are ergonomic references, not a styling framework or behavior selector system. Generated resources, form/handle metadata, atoms, and event constants come from hemx-build facts, not hand-written app plumbing. Forms remain HTML-shaped, checked against user-authored Rust domain types, parsed through `FormValue`, and manipulated through generated form/control ids rather than selectors. Proc-macros stay local/side-effect-free while build.rs owns global codegen and hard build failures. req: boundary/001 req: boundary/002 req: boundary/003 req: boundary/004 req: surface/001 req: surface/002 req: surface/003 req: surface/004 req: surface/005 req: surface/006 req: surface/007 req: surface/008 req: surface/009 req: surface/010 req: codegen/001 req: codegen/003 req: codegen/004 req: codegen/005 req: codegen/006 req: form/001 req: form/004 req: form/007 req: form/008 req: form_effects/001 req: form_effects/002 req: form_effects/003 req: build/001 req: build/002 req: build/003 req: build/004 req: build/005 req: build/006 req: build/007 req: build/008 req: style/001 req: style/002 req: style/003 req: style/004 req: style/005 req: style/006
|
||||
- The stable public `.heml` authoring surface lives in `docs/hemplate-syntax.md`; Hemlate examples must use that real hemplate syntax, not Vue/Handlebars sketches. hemx-build consumes hemplate Surface facts and must not grow an independent `.heml` parser or CSS-path identity model. Plain CSS/SCSS owns appearance; generated class constants are ergonomic references, not a styling framework or behavior selector system. Generated resources, form/handle metadata, atoms, and event constants come from hemx-build facts, not hand-written app plumbing. Forms remain HTML-shaped, checked against user-authored Rust domain types, parsed through `FormValue`, and manipulated through generated form/control ids rather than selectors. Proc-macros stay local/side-effect-free while build.rs owns global codegen and hard build failures. No-op global codegen must preserve generated artifact timestamps so downstream Rust compilation remains fresh only when canonical output changes. req: boundary/001 req: boundary/002 req: boundary/003 req: boundary/004 req: surface/001 req: surface/002 req: surface/003 req: surface/004 req: surface/005 req: surface/006 req: surface/007 req: surface/008 req: surface/009 req: surface/010 req: codegen/001 req: codegen/003 req: codegen/004 req: codegen/005 req: codegen/006 req: form/001 req: form/004 req: form/007 req: form/008 req: form_effects/001 req: form_effects/002 req: form_effects/003 req: build/001 req: build/002 req: build/003 req: build/004 req: build/005 req: build/006 req: build/007 req: build/008 req: build/009 req: style/001 req: style/002 req: style/003 req: style/004 req: style/005 req: style/006
|
||||
- Optional `.heml` editor overlays must share authority with `hemx-build` diagnostics and `docs/hemplate-syntax.md`; `hemx-lsp` owns editor protocol glue for diagnostics/completion/hover and derive-known template facts, while VS Code/Cursor/Neovim keep normal HTML/tree-sitter tooling. Do not create a second template language, selector model, formatter, Rust type system, or custom editor framework. Cross-file template/handler references visible to build validation must fail at `cargo check` with useful spans; global completeness checks stay component-scoped unless caught at mount/tests. req: diagnostics/004 req: diagnostics/005 req: diagnostics/006 req: diagnostics/007 req: diagnostics/008 req: invariant/004 req: invariant/006 req: check/001 req: check/003
|
||||
- JS runtime changes must preserve root-scoped lookup, delegated listeners, postcard EffectBatch application, fail-closed request handling, transactional/recoverable failure behavior, root-scoped error outlets, and tiny pending/failure/trigger-timing conventions without selectors, handler-name parsing, VDOM, expressions, or per-node listeners. Runtime `.d.ts` types are developer convenience only, not core tooling authority. req: invariant/002 req: runtime/001 req: runtime/002 req: runtime/003 req: runtime/005 req: runtime/006 req: failure/001 req: failure/002 req: failure/003 req: failure/004 req: failure/005 req: failure/006 req: convention/001 req: convention/002 req: convention/003 req: convention/004 req: convention/005 req: convention/006 req: convention/007 req: convention/008 req: convention/009 req: convention/010 req: convention/011 req: convention/012 req: convention/013 req: convention/014 req: convention/015 req: convention/016 req: convention/017 req: ts/001
|
||||
- Opaque island JavaScript is a leaf adapter for high-frequency local behavior only; client-local handlers keep the server-handler shape while `hemx-wasm` owns concrete opt-in syntax. Use native events/generated helpers at the boundary and do not introduce a component runtime, client state graph, VDOM, selector interop, or second UI model. req: canonical_authoring/017 req: client_local/001 req: client_local/003 req: client_local/004 req: interop/001 req: interop/002 req: interop/003 req: interop/006 req: interop/007 req: interop/008 req: interop/009 req: interop/010 req: interop/011 req: interop/012
|
||||
|
||||
@@ -1079,6 +1079,9 @@ what a valid business email is. [north_star]
|
||||
### req: build/008
|
||||
0 008 Global checks for every declared handle having an implementation are deferred to app-mount tests or enabled by an optional `#[hemx::component]` macro.
|
||||
|
||||
### req: build/009
|
||||
0 009 `hemx_build` preserves generated artifact timestamps when canonical contents are unchanged, so no-op builds do not invalidate downstream Rust compilation. [north_star]
|
||||
|
||||
---
|
||||
|
||||
## misc
|
||||
|
||||
+51
-4
@@ -223,15 +223,26 @@ impl AppBuilder {
|
||||
resources.add_stylesheet(&self.template_dir, &path, &source)?;
|
||||
}
|
||||
|
||||
std::fs::write(
|
||||
out_dir.join("hemx.generated.rs"),
|
||||
resources.generated_rs(self.global_exports),
|
||||
write_if_changed(
|
||||
&out_dir.join("hemx.generated.rs"),
|
||||
resources.generated_rs(self.global_exports).as_bytes(),
|
||||
)?;
|
||||
std::fs::write(out_dir.join("hemx.syms"), resources.syms())?;
|
||||
write_if_changed(&out_dir.join("hemx.syms"), resources.syms().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn write_if_changed(path: &Path, contents: &[u8]) -> io::Result<bool> {
|
||||
match std::fs::read(path) {
|
||||
Ok(existing) if existing == contents => return Ok(false),
|
||||
Ok(_) => {}
|
||||
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
std::fs::write(path, contents)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn app() -> AppBuilder {
|
||||
AppBuilder {
|
||||
out_dir: None,
|
||||
@@ -2162,6 +2173,42 @@ fn parse_error(path: &Path, err: impl std::fmt::Display) -> io::Error {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_op_build_preserves_generated_artifact_timestamps() {
|
||||
// req: build/009
|
||||
let base = test_dir("hemx-build-no-op");
|
||||
let templates = base.join("templates");
|
||||
let out = base.join("out");
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
std::fs::create_dir_all(&templates).unwrap();
|
||||
std::fs::write(
|
||||
templates.join("panel.heml"),
|
||||
r#"<button data-hemx-handle="save">Save</button>"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
app().template_dir(&templates).out_dir(&out).run().unwrap();
|
||||
let generated = out.join("hemx.generated.rs");
|
||||
let symbols = out.join("hemx.syms");
|
||||
let generated_modified = std::fs::metadata(&generated).unwrap().modified().unwrap();
|
||||
let symbols_modified = std::fs::metadata(&symbols).unwrap().modified().unwrap();
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
app().template_dir(&templates).out_dir(&out).run().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
std::fs::metadata(generated).unwrap().modified().unwrap(),
|
||||
generated_modified,
|
||||
"no-op codegen must not invalidate downstream Rust compilation"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::metadata(symbols).unwrap().modified().unwrap(),
|
||||
symbols_modified,
|
||||
"no-op symbol generation must preserve its artifact timestamp"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_generated_resources_from_heml() {
|
||||
// req: codegen/003
|
||||
|
||||
Reference in New Issue
Block a user