Edge
Two builds reach the edge. -e edge makes a single-threaded WebAssembly module and its loader, which a Cloudflare Worker imports and boots with initNative(). -e http makes a WASI HTTP component: a C++ request handler with no JavaScript at all, which wasmtime serve, Spin and wasmCloud run.
Cloudflare Workers
Workers, and the edge runtimes that follow their model, run JavaScript in V8 isolates: no Web Workers, no SharedArrayBuffer and no bundler plugin, so the Worker imports the build output itself. Install the CLI and name the project, which names the output files:
With no bundler to see your imports, the build binds every .h file under src/native, so a port is reached through a header of your own. Build, and the loader and its wasm land in dist:
Native is a class declared in src/native/native.h. Edge runtimes cannot compile WebAssembly from bytes, so the Worker imports the .wasm as a module and getWasmFunction (Runtime API) hands it to the loader. initNative() boots once per isolate and returns the same module on every later call.
Boot from the handler rather than with a top-level await: Workers forbid random number generation in global scope, and Rust's standard library asks for random bytes while the module starts. The build also writes dist/edge/wasm.mjs, which does both imports itself and exports initNative and every bound name; it is the entry a package publishes for edge runtimes (Publishing packages).
Wrangler bundles the .wasm import with no extra rules:
To start from a working Worker instead, npm create crossbind@beta -- my-worker Cloud "Cloudflare Worker" scaffolds one.
Limits
- Single-threaded only.
-r mtanduseWorkerneed Web Workers, which an isolate does not have. - No filesystem for JavaScript. The edge build exports no
m.FSand has no OPFS. Keep durable data in a store such as R2 or KV, read it in JavaScript and pass it to your bound functions. - No header constants.
#defineandconstvalues bind only for names an app imports, and a Worker imports no header. - Platform caps. A free Worker gets 10 ms of CPU per request, and every plan caps a Worker's size: check your
.wasmagainst yours.
WASI HTTP components
-e http links your C++ into a component that exports wasi:http/[email protected], the export of the wasi:http/service world. There is no main() and no JavaScript: src/native defines the one function <crossbind/http.h> declares, and the host calls it once per request.
Spin 4 serves the same file from a manifest, with spin up:
wasmCloud's wash dev takes it as build.component_path in .wash/config.yaml (checked by hand with wash 2.10). The build compiles like any WASI command, and ports link in from their -wasi packages.
Requestcarriesmethod,authority,pathandqueryas the client sent them,headerswith lowercase names, andbody, read whole beforehandleruns.Responseisstatus(200 unless set),headersandbody.- An exception out of
handleanswers 500 and logscrossbind: <method> <path> threw: <what>to stderr. - A host can reuse one instance for many requests, so static state outlives a request. Calls of
handlenever overlap, but one can run while an earlier response body is still being written. - Each request runs on a 1 MiB stack of its own;
-Wl,-z,stack-size=<bytes>inbinary.wasiFlagschanges it.
Not there yet: outgoing requests (wasi:http/client), streamed request and response bodies, trailers, threads, Rust, and the data files and environment of dependencies such as PROJ, GDAL and OpenSSL, which no host passes to a component. The component also imports the wasi:cli environment, exit and terminal interfaces, so a host that offers only the wasi:http/service imports rejects it.
examples/cloud-cloudflare-worker is a complete Worker, and e2e/cloud-wasi-http builds a component and tests it under wasmtime serve 47 and Spin 4.2.