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 and a much reduced boilerplate for hybrid JS/WebAssembly
applications. At the moment only a minimal core API is provided (i.e. for debug
output, string, pointer, typed array accessors [8/16/32/64 bit (u)ints, 32/64
bit floats]), 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 {
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 (Please also see /test/index.ts` & the example further below in this
readme):
// Import JS core API
const js = @import("wasmapi");
/// JS external to fill vec2 w/ random values
/// Note: Each API module uses a separate import object to avoid naming clashes
/// Here we declare an external binding belonging to the "custom" import group
extern "custom" fn 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
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.61 KB
Dependencies
API
Generated API docs
import { WasmBridge, WasmExports } from "@thi.ng/wasm-api";
import { readFileSync } from "fs";
interface App extends WasmExports {
start: () => void;
}
(async () => {
const bridge = new WasmBridge<App>();
await bridge.instantiate(readFileSync("hello.wasm"));
bridge.exports.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 using the following command (or 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 "core" "_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