Did a quick experiment to build an isomorphic wasm component that could be used locally or run remotely on a cluster. As I understand wrpc uses the same interface description but has a completely different binary serialization, and because of that the API's between wit-bindgen and wrpc-bindgen are completely different. However if the below example could be made less boilerplatte heavy (by creating a macro that implements a wrpc server based on the wit bindings) it would be cool. I can also imagine extending it to frontend components which export their html as a string. This way the component could be run server side for indexing or client side with a webview or wrapped into a browser module with jco.
use anyhow::Result;
use clap::Parser;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use wasi_hw_native::WasmCtx;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Mode {
Local,
Client,
Server,
}
impl std::str::FromStr for Mode {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"local" => Mode::Local,
"client" => Mode::Client,
"server" => Mode::Server,
_ => anyhow::bail!("invalid mode {s}"),
})
}
}
#[derive(Parser)]
struct Opts {
#[arg(short, long, default_value = "local")]
mode: Mode,
#[arg(
short,
long,
default_value = "../target/wasm32-wasip2/release/wasi_hw.wasm"
)]
file: PathBuf,
#[arg(short, long, default_value = "127.0.0.1:8080")]
address: String,
}
mod client_bindings {
wit_bindgen_wrpc::generate!({
world: "hello-world-consumer",
path: "../wit",
});
}
mod server_bindings {
wit_bindgen_wrpc::generate!({
world: "hello-world-provider",
path: "../wit",
});
}
mod host_bindings {
wasmtime::component::bindgen!({
world: "hello-world-provider",
path: "../wit",
});
}
pub struct CustomWasmCtx {
ctx: WasmCtx,
}
impl CustomWasmCtx {
pub fn new(path: &Path) -> Result<Self> {
Ok(Self {
ctx: WasmCtx::new(path)?,
})
}
pub async fn local_hello_hello_world_call_hello_world(&self) -> Result<String> {
let (mut store, instance) = self.ctx.instantiate().await?;
let bindings = host_bindings::HelloWorldProvider::new(&mut store, &instance)?;
let result = store
.run_concurrent(async |accessor| {
bindings
.local_hello_hello_world()
.call_hello_world(accessor)
.await
})
.await??;
Ok(result)
}
}
#[derive(Clone)]
struct Server {
ctx: Arc<CustomWasmCtx>,
}
impl Server {
pub fn new(ctx: CustomWasmCtx) -> Self {
Self { ctx: Arc::new(ctx) }
}
}
impl server_bindings::exports::local::hello::hello_world::Handler<SocketAddr> for Server {
async fn hello_world(&self, _ctx: SocketAddr) -> anyhow::Result<String> {
self.ctx.local_hello_hello_world_call_hello_world().await
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
let opts = Opts::parse();
if opts.mode == Mode::Client {
let transport = wrpc_transport::tcp::Client::from(opts.address);
let result =
client_bindings::local::hello::hello_world::hello_world(&transport, ()).await?;
println!("result: {}", result);
return Ok(());
}
let ctx = CustomWasmCtx::new(&opts.file)?;
if opts.mode == Mode::Local {
let result = ctx.local_hello_hello_world_call_hello_world().await?;
println!("result: {}", result);
return Ok(());
}
let server = Server::new(ctx);
let transport = Arc::new(wrpc_transport::Server::default());
let accept = wasi_hw_native::tcp_server(&opts.address, transport.clone()).await?;
let invocation = server_bindings::serve(transport.as_ref(), server).await?;
wasi_hw_native::wrpc_server(accept, invocation).await?;
Ok(())
}
Did a quick experiment to build an isomorphic wasm component that could be used locally or run remotely on a cluster. As I understand wrpc uses the same interface description but has a completely different binary serialization, and because of that the API's between wit-bindgen and wrpc-bindgen are completely different. However if the below example could be made less boilerplatte heavy (by creating a macro that implements a wrpc server based on the wit bindings) it would be cool. I can also imagine extending it to frontend components which export their html as a string. This way the component could be run server side for indexing or client side with a webview or wrapped into a browser module with jco.