Add wasm32 support and browser-based client example#19
Add wasm32 support and browser-based client example#19iainmcgin merged 4 commits intoanthropics:mainfrom
Conversation
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
iainmcgin
left a comment
There was a problem hiding this comment.
Thanks for this — really appreciate the careful work, and the analysis of the workspace tokio feature usage. Tightening the published crate's tokio feature footprint is a nice benefit for everyone.
I'm happy to merge this as-is. Before I do, a couple of notes and one ask for the wasm README, plus some thoughts on directions I'd love to see this example grow in (no obligation — pointers for whoever picks it up next).
Small README ask. Could you note in examples/wasm-client/README.md (or in lib.rs's module doc) that the example currently demonstrates unary calls without deadlines, and that timeouts and streaming would each require additional setup? Just so anyone copy-pasting the transport doesn't get a surprise panic the first time they pass CallOptions::with_timeout(...). A two-line note is plenty.
Future work I'd love to see (not blocking).
-
Request timeouts.
CallOptions::with_timeout(...)currently goes throughtokio::time::timeout_at, which needs a tokio runtime context. Onwasm32-unknown-unknown, that means building a current-thread runtime withenable_time()and entering its context before each call (tokio's wasm timer driver usessetTimeoutunderneath, so this works without ablock_on). Demonstrating that pattern in the example would save the next person from rediscovering it. Longer term, threading deadlines down to the transport so it can useAbortController.signalinstead would make wasm transports timer-runtime-free, but that's a connectrpc-side change and out of scope here. -
Server-streaming. Eliza's
IntroduceRPC is server-streaming, and fetch's response body is aReadableStreamin every browser. The current transport buffers viaarray_buffer().awaitintoFull<Bytes>; swapping that for a customhttp_body::Bodyimpl that pulls chunks from the JS reader (wrapped inSendWrapper) would get progressive delivery, and connectrpc's stream reader will produce one message per envelope as bytes arrive. This feels like the most valuable next step — it's universally browser-supported and matches what gRPC-Web and Connect-Web expose. -
Client-streaming and bidi — known limitations. Worth being explicit about why these aren't here:
- Client-streaming via fetch's
ReadableStreamrequest body withduplex: 'half'is Chromium-only as of early 2026 (Firefox bug 1561841 still open, no Safari support). The Firefox-based CI test you've added wouldn't exercise it.- True bidirectional streaming isn't achievable via fetch; I'm considering submitting an RFC to standardize WebTransport as an option for ConnectRPC, which would allow for bidi in connect-es and a wasm build of connect-rust. That's likely going to be a lot of work to nail down the details, though, so don't hold your breath 😆
- Client-streaming via fetch's
I'll approve and merge this now, anything said above can be considered optional feedback for future PRs. Thanks again!
Background
I realized connectrpc effectively already supported WASM but it couldn't compile because of the unnecessary tokio features specified at the workspace level. All other usages of tokio in this workspace set their features to
fullanyway so it seemed like it could be an oversight. From there I added some tests to ensure the crate will retain WASM support in future iterations.Summary
tokio/tokio-utilto version-only soconnectrpccan declare minimal features and compile forwasm32-unknown-unknownwasm-clientexample demonstrating aClientTransportbacked byweb-sys::fetch, usable in browsers and web workersDetails
The workspace tokio dependency previously declared all features (
net,rt-multi-thread,signal, etc.), which pulled platform-specific code into every member crate. Now the workspace specifies version-only and connectrpc declares just the features it needs (rt, io-util, sync, time).The
wasm-clientexample reuses the existing eliza server and proto definitions. The eliza server gains a permissive CORS layer so browser clients can reach it. Thetest.shscript starts the eliza server, runswasm-pack test --headless --firefox, and verifies the RPC round-trips successfully.