This is a manual implementation of "server functions." I'm going to use this to build out the RPC protocol that will allow us to do proper, fine-grained data loading and mutations.

This commit is contained in:
Greg Johnston
2022-10-24 20:09:18 -04:00
parent 88464fefe9
commit fd847633a9
10 changed files with 296 additions and 1 deletions

View File

@@ -13,6 +13,9 @@ members = [
# examples
"examples/counter",
"examples/counter-isomorphic/client",
"examples/counter-isomorphic/server",
"examples/counter-isomorphic/counter",
"examples/counters",
"examples/counters-stable",
"examples/fetch",
@@ -23,7 +26,7 @@ members = [
"examples/router",
"examples/todomvc",
"examples/todomvc-ssr/todomvc-ssr-client",
"examples/todomvc-ssr/todomvc-ssr-server"
"examples/todomvc-ssr/todomvc-ssr-server",
]
exclude = [
"benchmarks"

View File

@@ -0,0 +1,16 @@
[package]
name = "counter-client"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_log = "0.2"
leptos = { path = "../../../leptos", default-features = false, features = ["hydrate", "serde"] }
counter-isomorphic = { path = "../counter", default-features = false, features = ["hydrate"] }
log = "0.4"
wasm-bindgen = "0.2"
console_error_panic_hook = "0.1.7"

View File

@@ -0,0 +1 @@
wasm-pack build --target=web --release

View File

@@ -0,0 +1,14 @@
use counter_isomorphic::*;
use leptos::*;
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen]
pub fn main() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), |cx| {
view! { cx, <Counter/> }
});
}

View File

@@ -0,0 +1,23 @@
[package]
name = "counter-isomorphic"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../leptos", default-features = false, features = ["serde"] }
leptos_router = { path = "../../../router", default-features = false }
console_log = "0.2"
gloo-net = "0.2"
log = "0.4"
console_error_panic_hook = "0.1.7"
thiserror = "1"
serde = { version = "1", features = ["derive"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.0"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_router/hydrate"]
ssr = ["leptos/ssr", "leptos_router/ssr"]

View File

@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z"/>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,137 @@
use std::sync::atomic::{AtomicI32, Ordering};
use leptos::*;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug, Clone, Serialize, Deserialize)]
pub enum ServerFnError {
#[error("error reaching server to call server function: {0}")]
Request(String),
#[error("error running server function: {0}")]
ServerError(String),
#[error("error deserializing server function results {0}")]
Deserialization(String),
}
pub async fn call_server_fn<T>(url: &str) -> Result<T, ServerFnError>
where
T: Serializable + Sized,
{
let window = window();
let resp = gloo_net::http::Request::get(url)
.send()
.await
.map_err(|e| ServerFnError::Request(e.to_string()))?;
// check for error status
let status = resp.status();
if status >= 500 && status <= 599 {
return Err(ServerFnError::ServerError(resp.status_text()));
}
let text = resp
.text()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
T::from_json(&text).map_err(|e| ServerFnError::Deserialization(e.to_string()))
}
#[cfg(feature = "ssr")]
static COUNT: AtomicI32 = AtomicI32::new(0);
#[cfg(feature = "ssr")]
pub async fn get_server_count() -> Result<i32, ServerFnError> {
Ok(COUNT.load(Ordering::Relaxed))
}
#[cfg(not(feature = "ssr"))]
pub async fn get_server_count() -> Result<i32, ServerFnError> {
call_server_fn("/api/get_server_count").await
}
#[cfg(feature = "ssr")]
pub async fn increment_server_count() -> Result<i32, ServerFnError> {
let new = COUNT.load(Ordering::Relaxed) + 1;
COUNT.store(new, Ordering::Relaxed);
Ok(new)
}
#[cfg(not(feature = "ssr"))]
pub async fn increment_server_count() -> Result<i32, ServerFnError> {
call_server_fn("/api/increment_server_count").await
}
#[cfg(feature = "ssr")]
pub async fn decrement_server_count() -> Result<i32, ServerFnError> {
let new = COUNT.load(Ordering::Relaxed) - 1;
COUNT.store(new, Ordering::Relaxed);
Ok(new)
}
#[cfg(not(feature = "ssr"))]
pub async fn decrement_server_count() -> Result<i32, ServerFnError> {
call_server_fn("/api/decrement_server_count").await
}
#[cfg(feature = "ssr")]
pub async fn clear_server_count() -> Result<i32, ServerFnError> {
COUNT.store(0, Ordering::Relaxed);
Ok(0)
}
#[cfg(not(feature = "ssr"))]
pub async fn clear_server_count() -> Result<i32, ServerFnError> {
call_server_fn("/api/clear_server_count").await
}
#[component]
pub fn Counter(cx: Scope) -> Element {
let (update, set_update) = create_signal(cx, 0);
let counter = create_resource(cx, move || update(), |_| get_server_count());
let dec = move |_| {
spawn_local(async move {
let new_count = decrement_server_count().await;
if let Ok(new_count) = new_count {
set_update.update(|n| *n += 1);
}
})
};
let inc = move |_| {
spawn_local(async move {
let new_count = increment_server_count().await;
if let Ok(new_count) = new_count {
set_update.update(|n| *n += 1);
}
})
};
let clear = move |_| {
spawn_local(async move {
let new_count = clear_server_count().await;
if let Ok(new_count) = new_count {
set_update.update(|n| *n += 1);
}
})
};
let value = move || counter.read().map(|count| count.unwrap_or(0)).unwrap_or(0);
let error_msg = move || {
counter
.read()
.map(|res| match res {
Ok(_) => None,
Err(e) => Some(e),
})
.flatten()
};
view! {
cx,
<div>
<button on:click=clear>"Clear"</button>
<button on:click=dec>"-1"</button>
<span>"Value: " {move || value().to_string()} "!"</span>
<button on:click=inc>"+1"</button>
</div>
}
}

View File

@@ -0,0 +1,8 @@
pub use counter::*;
use leptos::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| view! { cx, <Counter/> });
}

View File

@@ -0,0 +1,10 @@
[package]
name = "counter-server"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-files = "0.6"
actix-web = "4"
leptos = { path = "../../../leptos", default-features = false, features = ["ssr", "serde"] }
counter-isomorphic = { path = "../counter", default-features = false, features = ["ssr"] }

View File

@@ -0,0 +1,76 @@
use actix_files::Files;
use actix_web::*;
use counter_isomorphic::*;
use leptos::*;
#[get("/")]
async fn render_todomvc() -> impl Responder {
HttpResponse::Ok().content_type("text/html").body(format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Isomorphic Counter</title>
</head>
<body>
{}
</body>
<script type="module">import init, {{ main }} from './pkg/counter_client.js'; init().then(main);</script>
</html>"#,
run_scope({
|cx| {
view! { cx, <Counter/>}
}
})
))
}
#[get("/api/get_server_count")]
async fn get_server_count() -> impl Responder {
counter_isomorphic::get_server_count()
.await
.unwrap()
.to_string()
}
#[get("/api/clear_server_count")]
async fn clear_server_count() -> impl Responder {
counter_isomorphic::clear_server_count()
.await
.unwrap()
.to_string()
}
#[get("/api/increment_server_count")]
async fn increment_server_count() -> impl Responder {
counter_isomorphic::increment_server_count()
.await
.unwrap()
.to_string()
}
#[get("/api/decrement_server_count")]
async fn decrement_server_count() -> impl Responder {
counter_isomorphic::decrement_server_count()
.await
.unwrap()
.to_string()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(render_todomvc)
.service(Files::new("/pkg", "../client/pkg"))
.service(get_server_count)
.service(clear_server_count)
.service(increment_server_count)
.service(decrement_server_count)
.wrap(middleware::Compress::default())
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}