feat(runtime): add portable runtime support

req: push/009 req: push/010 req: push/011
This commit is contained in:
tmk241
2026-08-17 07:59:04 +02:00
parent e4bd6db13a
commit 353174604e
19 changed files with 1018 additions and 347 deletions
+36 -3
View File
@@ -1601,6 +1601,7 @@ fn known_hemx_attr(name: &str) -> bool {
name,
"data-hemx-root"
| "data-hemx-sse"
| "data-hemx-ws"
| "data-hemx-st"
| "data-hemx-handle"
| "data-hemx-slot"
@@ -1728,6 +1729,14 @@ fn reject_invalid_hemx_attr_values(path: &Path, attrs: &[SurfaceAttribute]) -> i
"expected a non-empty same-origin SSE URL",
));
}
"data-hemx-ws" if value.trim().is_empty() => {
return Err(invalid_hemx_value(
path,
&attr.name,
value,
"expected a non-empty same-origin WebSocket URL",
));
}
"data-hemx-revealed-ahead"
if !value
.trim()
@@ -1796,6 +1805,13 @@ fn reject_invalid_hemx_attr_placement(
"expected placement on the same element as `data-hemx-root`",
));
}
if has_attr(attrs, "data-hemx-ws") && !has_attr(attrs, "data-hemx-root") {
return Err(invalid_hemx_placement(
path,
"data-hemx-ws",
"expected placement on the same element as `data-hemx-root`",
));
}
Ok(())
}
@@ -2789,12 +2805,12 @@ mod tests {
let canonical_syms = resources.syms();
assert_eq!(
stable_id("generated-rs", &canonical_generated),
2_620_423_950,
4_047_122_428,
"canonical generated Rust changed"
);
assert_eq!(
stable_id("generated-rs-global", &canonical_globals),
2_559_847_213,
1_196_973_467,
"canonical global-export Rust changed"
);
assert_eq!(
@@ -4232,6 +4248,12 @@ fn main() {{
"data-hemx-sse",
"non-empty",
),
(
"ws-empty",
r#"<main data-hemx-root="app" data-hemx-ws=" "></main>"#,
"data-hemx-ws",
"non-empty",
),
(
"delay-empty",
r#"<button data-hemx-handle="save" data-hemx-delay="">Save</button>"#,
@@ -4358,6 +4380,7 @@ fn main() {{
"data-hemx-on" => "expected runtime-supported events: `click`, `submit`, `input`, `change`, `keydown`, `dragstart`, `dragover`, or `drop`",
"data-hemx-confirm" => "expected a non-empty confirmation message",
"data-hemx-sse" => "expected a non-empty same-origin SSE URL",
"data-hemx-ws" => "expected a non-empty same-origin WebSocket URL",
"data-hemx-debounce" | "data-hemx-delay" | "data-hemx-throttle"
| "data-hemx-every" | "data-hemx-interval" => {
"expected milliseconds like `250`/`250ms` or seconds like `1s`"
@@ -4437,7 +4460,7 @@ fn main() {{
}
let valid_source = r#"
<main data-hemx-root="app" data-hemx-client-module="/app.js" data-hemx-sse="/events">
<main data-hemx-root="app" data-hemx-client-module="/app.js" data-hemx-sse="/events" data-hemx-ws="/room/socket">
<button data-hemx-handle="save" data-hemx-policy="latest" data-hemx-on="click change" data-hemx-confirm="Save?" data-hemx-delay="250ms" data-hemx-throttle="1s">Save</button>
<button data-hemx-client="save" data-hemx-client-event="click" data-hemx-client-policy="drop" data-hemx-client-state-version="1">Client</button>
<form method="get" action="/search" data-hemx-history="replace"><input name="q"></form>
@@ -4500,6 +4523,12 @@ fn main() {{
"data-hemx-sse",
"expected placement on the same element as `data-hemx-root`",
),
(
"ws-child",
r#"<section data-hemx-root="room"><div data-hemx-ws="/room/socket"></div></section>"#,
"data-hemx-ws",
"expected placement on the same element as `data-hemx-root`",
),
] {
let base = test_dir(&format!("hemx-build-invalid-placement-{case}-test"));
let templates = base.join("templates");
@@ -4540,6 +4569,10 @@ fn main() {{
"sse-root",
r#"<main data-hemx-root="feed" data-hemx-sse="/events"></main>"#,
),
(
"ws-root",
r#"<main data-hemx-root="room" data-hemx-ws="/room/socket"></main>"#,
),
] {
let base = test_dir(&format!("hemx-build-valid-placement-{case}-test"));
let templates = base.join("templates");