Skip to content

Commit 851a43a

Browse files
committed
feat: add yew-link crate for unified SSR/CSR data fetching
1 parent f229952 commit 851a43a

File tree

16 files changed

+1560
-40
lines changed

16 files changed

+1560
-40
lines changed

Cargo.lock

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,4 @@ chrono = "0.4"
5858
thiserror = "2.0"
5959
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
6060
reqwest = "0.12"
61+
axum = "0.8"

examples/function_router/src/content.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use serde::{Deserialize, Serialize};
2+
13
use crate::generator::{Generated, Generator};
24

3-
#[derive(Clone, Debug, Eq, PartialEq)]
5+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
46
pub struct Author {
57
pub seed: u32,
68
pub name: String,
@@ -22,7 +24,7 @@ impl Generated for Author {
2224
}
2325
}
2426

25-
#[derive(Clone, Debug, Eq, PartialEq)]
27+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2628
pub struct PostMeta {
2729
pub seed: u32,
2830
pub title: String,
@@ -48,7 +50,7 @@ impl Generated for PostMeta {
4850
}
4951
}
5052

51-
#[derive(Clone, Debug, Eq, PartialEq)]
53+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
5254
pub struct Post {
5355
pub meta: PostMeta,
5456
pub content: Vec<PostPart>,
@@ -68,7 +70,7 @@ impl Generated for Post {
6870
}
6971
}
7072

71-
#[derive(Clone, Debug, Eq, PartialEq)]
73+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
7274
pub enum PostPart {
7375
Section(Section),
7476
Quote(Quote),
@@ -87,7 +89,7 @@ impl Generated for PostPart {
8789
}
8890
}
8991

90-
#[derive(Clone, Debug, Eq, PartialEq)]
92+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
9193
pub struct Section {
9294
pub title: String,
9395
pub paragraphs: Vec<String>,
@@ -112,7 +114,7 @@ impl Generated for Section {
112114
}
113115
}
114116

115-
#[derive(Clone, Debug, Eq, PartialEq)]
117+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
116118
pub struct Quote {
117119
pub author: Author,
118120
pub content: String,

examples/function_router/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
// Hence, it may not yield the same value on the client and server side.
1515

1616
mod app;
17-
mod components;
18-
mod content;
17+
pub mod components;
18+
pub mod content;
1919
mod generator;
2020
pub mod imagegen;
21-
mod pages;
21+
pub mod pages;
2222

2323
pub use app::*;
2424
pub use content::*;

examples/ssr_router/Cargo.toml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ name = "ssr_router"
33
version = "0.1.0"
44
edition = "2021"
55

6-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7-
86
[[bin]]
97
name = "ssr_router_hydrate"
108
required-features = ["hydration"]
@@ -15,8 +13,14 @@ required-features = ["ssr"]
1513

1614
[dependencies]
1715
yew = { path = "../../packages/yew" }
16+
yew-link = { path = "../../packages/yew-link" }
17+
yew-router = { path = "../../packages/yew-router" }
1818
function_router = { path = "../function_router" }
1919
log = "0.4"
20+
rand = { workspace = true }
21+
getrandom = { workspace = true }
22+
serde = { workspace = true, features = ["derive"] }
23+
serde_json = { workspace = true }
2024
futures = { workspace = true, features = ["std"] }
2125
hyper-util = "0.1.20"
2226

@@ -25,9 +29,8 @@ wasm-bindgen-futures.workspace = true
2529
wasm-logger.workspace = true
2630

2731
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
28-
yew-router = { path = "../../packages/yew-router" }
2932
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "net", "fs"] }
30-
axum = "0.8"
33+
axum = { workspace = true }
3134
tower = { version = "0.5", features = ["make"] }
3235
tower-http = { version = "0.6", features = ["fs"] }
3336
env_logger = "0.11"
@@ -38,5 +41,5 @@ hyper = { version = "1.4", features = ["server", "http1"] }
3841
jemallocator = "0.5"
3942

4043
[features]
41-
ssr = ["yew/ssr"]
42-
hydration = ["yew/hydration"]
44+
ssr = ["yew/ssr", "yew-link/ssr", "yew-link/axum"]
45+
hydration = ["yew/hydration", "yew-link/hydration"]

examples/ssr_router/src/bin/ssr_router_hydrate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use function_router::App;
1+
use ssr_router::App;
22

33
fn main() {
44
#[cfg(target_arch = "wasm32")]

examples/ssr_router/src/bin/ssr_router_server.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ use axum::extract::{Query, Request, State};
99
use axum::handler::HandlerWithoutStateExt;
1010
use axum::http::Uri;
1111
use axum::response::IntoResponse;
12-
use axum::routing::get;
12+
use axum::routing::{get, post};
1313
use axum::Router;
1414
use clap::Parser;
15-
use function_router::{route_meta, Route, ServerApp, ServerAppProps};
15+
use function_router::{route_meta, Route};
1616
use futures::stream::{self, StreamExt};
1717
use hyper::body::Incoming;
1818
use hyper_util::rt::TokioIo;
1919
use hyper_util::server;
20+
use ssr_router::{LinkedAuthor, LinkedPost, LinkedPostMeta, ServerApp, ServerAppProps};
2021
use tokio::net::TcpListener;
2122
use tower::Service;
2223
use tower_http::services::ServeDir;
2324
use yew::platform::Runtime;
25+
use yew_link::{linked_state_handler, Resolver, ResolverProp};
2426
use yew_router::prelude::Routable;
2527

2628
// We use jemalloc as it produces better performance.
@@ -45,25 +47,36 @@ fn head_tags_for(path: &str) -> String {
4547
)
4648
}
4749

50+
#[derive(Clone)]
51+
struct AppState {
52+
index_html_before: String,
53+
index_html_after: String,
54+
resolver: ResolverProp,
55+
}
56+
4857
async fn render(
4958
url: Uri,
5059
Query(queries): Query<HashMap<String, String>>,
51-
State((index_html_before, index_html_after)): State<(String, String)>,
60+
State(state): State<AppState>,
5261
) -> impl IntoResponse {
5362
let path = url.path().to_owned();
5463

5564
// Inject route-specific <head> tags before </head>, outside of Yew rendering.
56-
let before = index_html_before.replace("</head>", &format!("{}</head>", head_tags_for(&path)));
65+
let before = state
66+
.index_html_before
67+
.replace("</head>", &format!("{}</head>", head_tags_for(&path)));
68+
let resolver = state.resolver.clone();
5769

5870
let renderer = yew::ServerRenderer::<ServerApp>::with_props(move || ServerAppProps {
5971
url: path.into(),
6072
queries,
73+
resolver,
6174
});
6275

6376
Body::from_stream(
6477
stream::once(async move { before })
6578
.chain(renderer.render_stream())
66-
.chain(stream::once(async move { index_html_after }))
79+
.chain(stream::once(async move { state.index_html_after }))
6780
.map(Result::<_, Infallible>::Ok),
6881
)
6982
}
@@ -93,33 +106,43 @@ where
93106
#[tokio::main]
94107
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
95108
let exec = Executor::default();
96-
97109
env_logger::init();
98-
99110
let opts = Opt::parse();
100111

112+
let resolver_prop: ResolverProp = Resolver::new()
113+
.register_linked::<LinkedPost>(())
114+
.register_linked::<LinkedAuthor>(())
115+
.register_linked::<LinkedPostMeta>(())
116+
.into();
117+
let arc_resolver = resolver_prop.0.clone();
118+
101119
let index_html_s = tokio::fs::read_to_string(opts.dir.join("index.html"))
102120
.await
103121
.expect("failed to read index.html");
104122

105123
let (index_html_before, index_html_after) = index_html_s.split_once("<body>").unwrap();
106124
let mut index_html_before = index_html_before.to_owned();
107125
index_html_before.push_str("<body>");
108-
109126
let index_html_after = index_html_after.to_owned();
110127

111-
let app = Router::new().fallback_service(
112-
ServeDir::new(opts.dir)
113-
.append_index_html_on_directories(false)
114-
.fallback(
115-
get(render)
116-
.with_state((index_html_before.clone(), index_html_after.clone()))
117-
.into_service(),
118-
),
119-
);
128+
let app_state = AppState {
129+
index_html_before,
130+
index_html_after,
131+
resolver: resolver_prop,
132+
};
133+
134+
let app = Router::new()
135+
.route(
136+
"/api/link",
137+
post(linked_state_handler).with_state(arc_resolver),
138+
)
139+
.fallback_service(
140+
ServeDir::new(opts.dir)
141+
.append_index_html_on_directories(false)
142+
.fallback(get(render).with_state(app_state).into_service()),
143+
);
120144

121145
let addr: SocketAddr = ([0, 0, 0, 0], 8080).into();
122-
123146
println!("You can view the website at: http://localhost:8080/");
124147

125148
let listener = TcpListener::bind(addr).await?;

0 commit comments

Comments
 (0)