Skip to content

Commit 99ae40b

Browse files
committed
add axum live-reload example (dev-only)
1 parent d99ff8f commit 99ae40b

File tree

4 files changed

+122
-0
lines changed

4 files changed

+122
-0
lines changed

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ lto = "thin"
103103
name = "axum-hello"
104104
required-features = ["axum", "tracing"]
105105

106+
[[example]]
107+
name = "axum-live-reload"
108+
required-features = ["axum", "tracing"]
109+
110+
[[example]]
111+
name = "axum-activity-feed"
112+
required-features = ["axum", "tracing"]
113+
106114
[[example]]
107115
name = "axum-test-suite"
108116
required-features = ["axum", "tracing"]

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ update-deps:
6262
hello-axum:
6363
cargo run --example axum-hello --features axum,tracing
6464

65+
live-reload-axum:
66+
cargo run --example axum-live-reload --features axum,tracing
67+
68+
watch-live-reload-axum:
69+
RUST_LOG=debug cargo watch -x 'run --example axum-live-reload --features axum,tracing'
70+
6571
activity-feed-axum:
6672
cargo run --example axum-activity-feed --features axum,tracing
6773

examples/axum-live-reload.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use {
2+
async_stream::stream,
3+
axum::{
4+
Router,
5+
response::{Html, IntoResponse, Sse},
6+
routing::get,
7+
},
8+
core::{convert::Infallible, error::Error, time::Duration},
9+
datastar::{axum::ReadSignals, prelude::PatchElements},
10+
serde::Deserialize,
11+
tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt},
12+
};
13+
14+
#[tokio::main]
15+
async fn main() -> Result<(), Box<dyn Error>> {
16+
tracing_subscriber::registry()
17+
.with(
18+
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
19+
format!("{}=debug,tower_http=debug", env!("CARGO_CRATE_NAME")).into()
20+
}),
21+
)
22+
.with(tracing_subscriber::fmt::layer())
23+
.init();
24+
25+
let app = Router::new()
26+
.route("/", get(index))
27+
.route("/hello-world", get(hello_world));
28+
29+
#[cfg(debug_assertions)]
30+
let app = app.route("/hotreload", get(hotreload));
31+
32+
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
33+
.await
34+
.unwrap();
35+
36+
tracing::debug!("listening on {}", listener.local_addr().unwrap());
37+
38+
axum::serve(listener, app).await.unwrap();
39+
40+
Ok(())
41+
}
42+
43+
const INDEX_HTML: &str = include_str!("hello-world.html");
44+
45+
#[cfg(not(debug_assertions))]
46+
async fn index() -> Html<&'static str> {
47+
Html(INDEX_HTML)
48+
}
49+
50+
#[cfg(debug_assertions)]
51+
async fn index() -> Html<&'static str> {
52+
static MOD_INDEX_HTML: std::sync::LazyLock<&'static str> = std::sync::LazyLock::new(|| {
53+
Box::new(INDEX_HTML.replace(
54+
r##"<!-- hot reload -->"##,
55+
r##"
56+
<div id="hotreload" data-on-load="@get('/hotreload', {retryMaxCount: 1000,retryInterval:20, retryMaxWaitMs:200})" class="text-yellow-500">
57+
<p>a minimal implementation of dev-only live reload added into axum hello example</p>
58+
</div>"##
59+
)).leak()
60+
});
61+
Html(&MOD_INDEX_HTML)
62+
}
63+
64+
#[cfg(debug_assertions)]
65+
async fn hotreload() -> impl IntoResponse {
66+
use std::sync::atomic;
67+
68+
// NOTE
69+
// This only works if you develop with a single tab open only,
70+
// in case you are testing with multiple UA's / Tabs at once
71+
// you will need to expand this implementation by for example
72+
// tracking against a date or version stored in a cookie
73+
// or by some other means.
74+
75+
use datastar::prelude::ExecuteScript;
76+
static ONCE: atomic::AtomicBool = atomic::AtomicBool::new(false);
77+
78+
Sse::new(stream! {
79+
if !ONCE.swap(true, atomic::Ordering::SeqCst) {
80+
let script = ExecuteScript::new("window.location.reload()");
81+
let sse_event = script.write_as_axum_sse_event();
82+
yield Ok::<_, Infallible>(sse_event);
83+
}
84+
std::future::pending().await
85+
})
86+
}
87+
88+
const MESSAGE: &str = "Hello, world!";
89+
90+
#[derive(Deserialize)]
91+
pub struct Signals {
92+
pub delay: u64,
93+
}
94+
95+
async fn hello_world(ReadSignals(signals): ReadSignals<Signals>) -> impl IntoResponse {
96+
Sse::new(stream! {
97+
for i in 0..MESSAGE.len() {
98+
let elements = format!("<div id='message'>{}</div>", &MESSAGE[0..i + 1]);
99+
let patch = PatchElements::new(elements);
100+
let sse_event = patch.write_as_axum_sse_event();
101+
102+
yield Ok::<_, Infallible>(sse_event);
103+
104+
tokio::time::sleep(Duration::from_millis(signals.delay)).await;
105+
}
106+
})
107+
}

examples/hello-world.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"></script>
99
</head>
1010
<body class="bg-white dark:bg-gray-900 text-lg max-w-xl mx-auto my-16">
11+
<!-- hot reload -->
1112
<div data-signals-delay="400" class="bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5 space-y-2">
1213
<div class="flex justify-between items-center">
1314
<h1 class="text-gray-900 dark:text-white text-3xl font-semibold">

0 commit comments

Comments
 (0)