A simple WASM app
This website is served as static files. JavaScript and Zig compiled to WebAssembly handle the interactive parts entirely in your browser, with no server-side application logic. This demo shows how the two languages work together!
For this demo, I chose to execute the simplest Zig function: an function that adds two numbers.
Try it!
Loading WebAssembly...
How it works
- Zig defines an
addfunction. - The Zig compiler turns it into a
.wasmmodule. - JavaScript loads that module, validates the form inputs, and calls the exported function.
Of course, adding two numbers does not need WebAssembly. I'm doing this to show the basic principle behind interfacing Zig and javascript.
The Zig source
This is the complete add.zig file. Simple right?
export fn add(a: i32, b: i32) i32 {
return a + b;
}
The export
keyword exposes the function to javascript. Both arguments and the result
are signed 32-bit integers (i32).
Building the WebAssembly module
The module is built with a simple script calling the zig compiler. Here is the complete script:
mkdir -p public
zig build-exe add.zig \
-target wasm32-freestanding \
-fno-entry \
--export=add \
-Osafe \
-femit-bin=public/add.wasm
wasm32-freestandingtargets WebAssembly without an operating system.-fno-entrybuilds a module without a program entry point.--export=addkeeps the function available to JavaScript.-Osafeenables optimization while retaining runtime safety checks.-femit-binsets the output file.
You can download the compiled module. It's simply a binary file
The browser code
The page loads app.js as a
JavaScript module.
WebAssembly.instantiate compiles and instantiates the
WebAssembly module. First, we retrieve its exported Zig function:
const add = instance.exports.add;
Then, inside the form's submit handler, this line calls it with the validated inputs and displays the result:
result.textContent = `${a} + ${b} = ${add(a, b)}`;
The add(a, b) call looks like an ordinary JavaScript
function call, but the addition runs inside the WebAssembly module
compiled from our Zig code.
Here is the complete browser code, including loading the module, validating the inputs, and updating the form's result:
const result = document.querySelector("#result");
const button = document.querySelector("#add");
try {
const response = await fetch(new URL("./add.wasm", import.meta.url));
if (!response.ok) throw new Error("WASM download failed");
const { instance } = await WebAssembly.instantiate(
await response.arrayBuffer(),
{}
);
const add = instance.exports.add;
document.querySelector("#calculator").onsubmit = event => {
event.preventDefault();
const a = document.querySelector("#a").valueAsNumber;
const b = document.querySelector("#b").valueAsNumber;
if (![a, b].every(n =>
Number.isInteger(n) && n >= 0 && n <= 1000
)) {
result.textContent = "Enter integers from 0 to 1000.";
return;
}
result.textContent = `${a} + ${b} = ${add(a, b)}`;
};
button.disabled = false;
result.textContent = "Ready.";
} catch (error) {
result.textContent = "Failed to load WebAssembly.";
console.error(error);
}
The form supplies the calculator, a,
b, and add element IDs. And when the user
submits it, the JavaScript code calls the add function we wrote in Zig
and compiled to WebAssembly, before displaying the result. How neat is that?
Final words
This could seem like a lot of work to simply add two numbers. But with this setup, you can call any Zig function from JavaScript. Think about it, any Zig function!
For example, you could write a Zig function to:
- Generate a fractal or procedural terrain, then draw it on a canvas.
- Apply an image filter to a buffer of pixels.
- Simulate particles and collisions for a little physics playground.
- Parse a binary file selected by the user and inspect its headers.
- Compress and decompress data directly in the browser.
(Okay, not any function. I lied. Of course, the code must compile for WebAssembly and expose an interface JavaScript can call. For instance, strings and pixel buffers will require some extra glue through the module's memory, and code that relies on operating-system APIs needs adaptation for the browser. But hey, if that's what it takes to write more Zig and less JavaScript, I take it anyday!)
And that's it!