WebAssembly (w/ WASI)
WebAssembly is a secure, sandboxed, portable runtime that runs inside and outside web browsers.
In practice:
- Languages (like Javascript) compile to WebAssembly (
.wasmfiles) - WebAssembly runtimes (like
wasmtimeorjco) enable running WebAssembly binaries
While core WebAssembly has no access to things like the local filesystem or sockets, the WebAssembly System Interface steps in to enable defining a platform under WebAssebly workloads.
This means that with WASI, WebAssembly can operate on files, sockets, and much more.
INFO
Want to peek at the WASI interface yourself? check out wasi:http
Support for WebAssembly w/ WASI in JS is powered by StarlingMonkey, and thanks to the focus on Web standards in both StarlingMonkey and Hono, Hono works *out of the box with WASI-enabled WebAssembly ecosystems.
1. Setup
The WebAssembly JS ecosystem provides tooling to make it easy to get started building WASI-enabled WebAssembly components:
- StarlingMonkey is a fork of SpiderMonkey that compiles to WebAssembly and enables components
componentize-jsturns Javascript ES modules into WebAssembly componentsjcois a multi-tool that builds components, generates types, and runs components in environments like NodeJS or the browser
INFO
Webassembly has an open ecosystem and is open source, with core projects stewarded primarily by the Bytecode Alliance and it's members.
New features, issues, pull requests and other types of contributions are always welcome.
While a starter for Hono on WebAssembly is not yet available, you can start a WebAssembly Hono project just like any other:
mkdir my-app
cd my-app
npm init
npm i hono
npm i -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
npm i -D rolldownmkdir my-app
cd my-app
npm init
yarn add hono
yarn add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
yarn add -D rolldown
G```
```sh [pnpm]
mkdir my-app
cd my-app
pnpm init --init-type module
pnpm add hono
pnpm add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-std
pnpm add -D rolldownmkdir my-app
cd my-app
npm init
bun add hono
bun add -D @bytecodealliance/jco @bytecodealliance/componentize-js @bytecodealliance/jco-stdINFO
To ensure your project uses ES modules, ensure type is set to "module" in package.json
After entering the my-app folder, install dependencies, and initialize Typescript:
npm i
npx tsc --inityarn
yarn tsc --initpnpm i
pnpm exec --initbun iOnce you have a basic typescript configuration file (tsconfig.json), please ensure it has the following configuration:
compilerOptions.moduleset to"nodenext"
Since componentize-js (and jco which re-uses it) supports only single JS files, bundling is necessary, so rolldown can be used to create a single file bundle.
A Rolldown configuration (rolldown.config.mjs) like the following can be used:
import { defineConfig } from 'rolldown'
export default defineConfig({
input: 'src/component.ts',
external: /wasi:.*/,
output: {
file: 'dist/component.js',
format: 'esm',
},
})INFO
Feel free to use any other bundlers that you're more comfortable with (rolldown, esbuild, rollup, etc)
2. Set up WIT interface & dependencies
WebAssembly Inteface Types (WIT) is an Interface Definition Language ("IDL") that governs what functionality a WebAssembly component uses ("imports"), and what it provides ("exports").
Amongst the standardized WIT interfaces, wasi:http is for dealing with HTTP requests (whether it's receiving them or sending them out), and since we intend to make a web server, our component must declare the use of wasi:http/incoming-handler in it's WIT world:
First, let's set up the component's WIT world in a file called wit/component.wit:
package example:hono;
world component {
export wasi:http/incoming-handler@0.2.6;
}Put simply, the WIT file above means that our component "providers" the functionality of "receiving"/"handling incoming" HTTP requests.
The wasi:http/incoming-handler interface relies on upstream standardized WIT interfaces (specifications on how requests are structured, etc).
To pull those third party (Bytecode Alliance maintained) WIT interaces, one tool we can use is wkg:
wkg wit fetchOnce wkg has finished running, you should find your wit folder populated with a new deps folder alongside component.wit:
wit
├── component.wit
└── deps
├── wasi-cli-0.2.6
│ └── package.wit
├── wasi-clocks-0.2.6
│ └── package.wit
├── wasi-http-0.2.6
│ └── package.wit
├── wasi-io-0.2.6
│ └── package.wit
└── wasi-random-0.2.6
└── package.wit3. Hello Wasm
To build a HTTP server in WebAssembly, we can make use of the [jco-std][jco-std] project, which contains helpers that make the experience very similar to the standard Hono experience.
Let's fulfill our component world with a basic Hono application as a WebAssembly component in a file called src/component.ts:
import { Hono } from 'hono'
import { fire } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server'
const app = new Hono()
app.get('/hello', (c) => {
return c.json({ message: 'Hello from WebAssembly!' })
})
fire(app)
// Although we've called `fire()` with wasi HTTP configured for use above,
// we still need to actually export the `wasi:http/incoming-handler` interface object,
// as jco and componentize-js will be looking for the ES module export that matches the WASI interface.
export { incomingHandler } from '@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server'4. Build
Since we're using Rollup (and it's configured to handle Typescript compilation), we can use it to build and bundle:
npx rolldown -cyarn rolldown -cpnpm exec rolldown -cbun build --target=bun --outfile=dist/component.js ./src/component.tsINFO
The bundling step is necessary because WebAssembly JS ecosystem tooling only currently supports a single JS file, and we'd like to include Hono along with related libraries.
For components with simpler requirements, bundlers are not necessary.
To build your WebAssembly component, use jco (and indirectly componentize-js):
npx jco componentize -w wit -o dist/component.wasm dist/component.jsyarn jco componentize -w wit -o dist/component.wasm dist/component.jspnpm exec jco componentize -w wit -o dist/component.wasm dist/component.jsbun run jco componentize -w wit -o dist/component.wasm dist/component.js3. Run
To run your Hono WebAssembly HTTP server, you can use any WASI-enabled WebAssembly runtime:
wasmtimejco(runs in NodeJS)
In this guide, we'll use jco serve since it's already installed.
WARNING
jco serve is meant for development, and is not recommended for production use.
npx jco serve dist/component.wasmyarn jco serve dist/component.wasmpnpm exec jco serve dist/component.wasmbun run jco serve dist/component.wasmYou should see output like the following:
$ npx jco serve dist/component.wasm
Server listening @ localhost:8000...Sending a request to localhost:8000/hello will produce the JSON output you've specified in your Hono application.
You should see output like the following:
{ "message": "Hello from WebAssembly!" }INFO
jco serve works by converting the WebAssembly component into a basic WebAssembly coremodule, so that it can be run in runtimes like NodeJS and the browser.
This process is normally run via jco transpile, and is the way we can use JS engines like NodeJS and the browser (which may use V8 or other Javascript engines) as WebAssembly Component runtimes.
How jco transpile is outside the scope of this guide, you can read more about it in the Jco book
More information
To learn moreabout WASI, WebAssembly components and more, see the following resources: