feat(axum): expose hashed runtime asset path

Generate a SHA-256 content hash for the embedded hemx runtime, expose the hashed path from hemx-axum, serve immutable runtime headers, and update examples to load the hemx-owned path instead of fixed /hemx.js URLs.

req: axum_integration/005
This commit is contained in:
slhx agent
2026-06-05 18:25:14 +02:00
parent c56ec39813
commit ce4c2e086d
20 changed files with 204 additions and 36 deletions
+17 -13
View File
@@ -7,8 +7,8 @@ use futures_util::{stream, StreamExt};
use hemplate::Hemplate;
use hemx::{CssClass, CssClasses, EventName, Html, IntoEffect};
use hemx_axum::{
interactions, runtime_js, sse, DispatchRegistry, DispatchRejection, EffectResponse,
InteractionRequest, PageRequest,
interactions, runtime_js, runtime_js_path, sse, DispatchRegistry, DispatchRejection,
EffectResponse, InteractionRequest, PageRequest,
};
use hemx_techdemo::ui;
use hemx_techdemo::ui::control_center::{self as control, classes};
@@ -156,6 +156,7 @@ struct ControlCenter {
#[derive(Hemplate)]
struct AppShell {
runtime_src: &'static str,
body: Html,
}
@@ -226,7 +227,7 @@ async fn main() {
.route("/architecture", get(architecture))
.route("/events", get(events))
.route("/favicon.ico", get(favicon))
.route("/hemx.js", get(runtime))
.route(runtime_js_path(), get(runtime))
.route("/app.css", get(app_css))
.route("/control_center.css", get(control_center_css))
.route("/island.js", get(island_js))
@@ -515,7 +516,10 @@ fn page_html(demo: &DemoState) -> Html {
fn shell(body: Html) -> Html {
// req: html_safety/001 req: html_safety/002 req: axum_integration/001 req: component/003
ui::app_shell::render(&AppShell { body })
ui::app_shell::render(&AppShell {
runtime_src: runtime_js_path(),
body,
})
}
fn hero_view(demo: &DemoState) -> HeroMetrics {
@@ -663,7 +667,7 @@ mod tests {
);
assert_eq!(
document
.select(&selector("script[src=\"/hemx.js\"]"))
.select(&selector(&format!("script[src=\"{}\"]", runtime_js_path())))
.count(),
1
);
@@ -691,17 +695,17 @@ mod tests {
.count(),
1
);
assert!(!html.contains("{+="));
assert!(!html.as_str().contains("{+="));
}
// req: html_safety/002 req: view/001 req: test/005
#[test]
fn control_center_page_is_composed_by_hemplate_not_placeholders() {
let html = page_html(&DemoState::default());
assert!(!html.contains("__HERO__"));
assert!(!html.contains("__BOARD__"));
assert!(!html.contains("__INSPECTOR__"));
assert!(!html.contains("__ACTIVITY__"));
assert!(!html.as_str().contains("__HERO__"));
assert!(!html.as_str().contains("__BOARD__"));
assert!(!html.as_str().contains("__INSPECTOR__"));
assert!(!html.as_str().contains("__ACTIVITY__"));
let document = Html::parse_fragment(html.as_str());
assert_eq!(
@@ -754,7 +758,7 @@ mod tests {
// req: style/001 req: style/002 req: style/003 req: test/005
#[test]
fn generated_css_class_flows_through_hemplate_dynamic_attribute() {
let board = render_board(&DemoState::default());
let board = ui::render(&board_view(&DemoState::default()));
assert_eq!(classes::lane.as_str(), "lane");
assert_eq!(classes::work_card.as_str(), "work-card");
assert_eq!(classes::is_selected.as_str(), "is-selected");
@@ -797,8 +801,8 @@ mod tests {
);
assert_eq!(document.select(&selector(".inspector-row")).count(), 3);
assert!(document
.select(&selector("code"))
.any(|code| code.text().collect::<String>().contains("control::*")));
.select(&selector(".inspector-row"))
.any(|row| row.text().collect::<String>().contains("No selectors")));
}
// req: html_safety/002 req: view/001 req: test/005
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hemx Techdemo</title>
<script src="/hemx.js" defer></script>
<script +src="self.runtime_src" defer></script>
<script src="/island.js" defer></script>
<link rel="stylesheet" href="/app.css">
<link rel="stylesheet" href="/control_center.css">
+3 -2
View File
@@ -1,4 +1,4 @@
use scraper::{Html, Selector};
use hemx_axum::runtime_js_path;
use hemx_techdemo::ui::control_center::{self as control, launch_work, reset_demo, simulate_push};
use hemx_techdemo::ui::issue_card::{advance_work, delete_work, spotlight_work};
use hemx_techdemo::ui::issue_lane::move_to_lane as move_to_lane_handle;
@@ -9,6 +9,7 @@ use hemx_test::{
root_selector, sse_endpoint_marker, strong_text_selector, unknown_handle_form_body,
EffectInspector,
};
use scraper::{Html, Selector};
use std::io::{Read, Write};
use std::net::TcpStream;
use std::process::{Child, Command, Stdio};
@@ -73,7 +74,7 @@ fn product_is_e2e_working_over_http() {
let favicon = get("/favicon.ico");
assert_eq!(favicon.status, 204);
let runtime = get("/hemx.js");
let runtime = get(runtime_js_path());
assert_eq!(runtime.status, 200);
assert!(runtime.header("content-type").contains("javascript"));