This project is part of the
@thi.ng/umbrella monorepo.
About
Modular, extensible API bridge and generic glue code between JS & WebAssembly.
This package provides a small, generic and modular
WasmBridge
class as interop basis for hybrid JS/WebAssembly applications. At the moment
only a basic core API is provided (i.e. for debug output, string & pointer
handling), but in the future we aim to also supply support modules for DOM
manipulation, WebGL, WebGPU, WebAudio etc.
In general, all languages with a WebAssembly target are supported, however
currently only bindings for Zig are included.
Custom API modules
On the JS side, custom API modules can be easily integrated via the IWasmAPI
interface. The
following example provides a brief overview:
import { IWasmAPI, WasmBridge } from "@thi.ng/wasm-api";
export class CustomAPI implements IWasmAPI {
parent!: WasmBridge;
async init(parent: WasmBridge) {
this.parent = parent;
this.parent.logger.debug("initializing custom API");
return true;
}
getImports(): WebAssembly.Imports {
return {
custom_randomVec2: (addr: number) => {
this.parent.f32.set(
[Math.random(), Math.random()],
addr >> 2
);
}
};
}
}
Now we can supply this custom API when creating the main WASM bridge:
export const bridge = new WasmBridge({ custom: new CustomAPI() });
In Zig (or any other language of your choice) we can then utilize this custom
API like so (also see example further below in this readme):
const js = @import("wasmapi");
/// JS external to fill vec2 w/ random values
extern fn custom_randomVec2(addr: usize) void;
export fn test_randomVec2() void {
var foo = [2]f32{ 0, 0 };
// print original
js.printF32Array(foo[0..]);
// populate foo with random numbers
custom_randomVec2(@ptrToInt(&foo));
// print result
js.printF32Array(foo[0..]);
}
Object indices & handles
Since only numeric values can be exchanged between the WASM module and the JS
host, any JS native objects the WASM side might want to be working with must be
managed in JS. For this purpose the ObjectIndex
class can be
used by API modules to handle ID generation (incl. recycling, using
@thi.ng/idgen)
& indexing of different types of JS objects/values. Only the numeric IDs will
then need to be exchanged with the WASM module...
import { ObjectIndex } from "@thi.ng/wasm-api";
const canvases = new ObjectIndex<HTMLCanvasElement>({ name: "canvas" });
canvases.add(document.createElement("canvas"));
canvases.get(0);
canvases.get(0).id = "foo";
canvases.has(1)
canvases.get(1)
canvases.get(1, false)
canvases.find((x) => x.id == "bar")
canvases.delete(0);
Status
ALPHA - bleeding edge / work-in-progress
Search or submit any issues for this package
Installation
yarn add @thi.ng/wasm-api
ES module import:
<script type="module" src="https://cdn.skypack.dev/@thi.ng/wasm-api"></script>
Skypack documentation
For Node.js REPL:
# with flag only for < v16
node --experimental-repl-await
> const wasmApi = await import("@thi.ng/wasm-api");
Package sizes (gzipped, pre-treeshake): ESM: 1.21 KB
Dependencies
API
Generated API docs
import { WasmBridge } from "@thi.ng/wasm-api";
import { readFileSync } from "fs";
interface App {
memory: WebAssembly.Memory;
start: () => void;
}
(async () => {
const bridge = new WasmBridge();
const wasm = await WebAssembly.instantiate(
readFileSync("hello.wasm"),
bridge.getImports()
);
const app: App = <any>wasm.instance.exports;
await bridge.init(app.memory);
app.start();
})();
//! Example Zig application (hello.zig)
/// import externals
/// see build command for configuration
const js = @import("wasmapi");
export fn start() void {
js.printStr("hello world!");
}
The WASM binary can be built via (for more complex scenarios add the supplied
.zig file(s) to your build.zig
and/or source folder):
zig build-lib \
--pkg-begin wasmapi node_modules/@thi.ng/wasm-api/zig/core.zig --pkg-end \
-target wasm32-freestanding \
-O ReleaseSmall -dynamic --strip \
hello.zig
wasm-dis -o hello.wast hello.wasm
The resulting WASM:
(module
(type $i32_i32_=>_none (func (param i32 i32)))
(type $none_=>_none (func))
(import "env" "_printStr" (func $fimport$0 (param i32 i32)))
(global $global$0 (mut i32) (i32.const 65536))
(memory $0 2)
(data (i32.const 65536) "hello world!\00")
(export "memory" (memory $0))
(export "start" (func $0))
(func $0
(call $fimport$0
(i32.const 65536)
(i32.const 12)
)
)
)
Authors
Karsten Schmidt
If this project contributes to an academic publication, please cite it as:
@misc{thing-wasm-api,
title = "@thi.ng/wasm-api",
author = "Karsten Schmidt",
note = "https://thi.ng/wasm-api",
year = 2022
}
License
© 2022 Karsten Schmidt // Apache Software License 2.0