Skip to content

Instantly share code, notes, and snippets.

@postspectacular
Last active August 29, 2023 22:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save postspectacular/08098359f75fa703a2cda64b1258a459 to your computer and use it in GitHub Desktop.
Save postspectacular/08098359f75fa703a2cda64b1258a459 to your computer and use it in GitHub Desktop.
#HowToThing #010 — Creating a basic web app with Zig/WebAssembly and the extensible https://thi.ng/wasm-api and its https://thi.ng/wasm-api-dom add-on module
const std = @import("std");
const wasm = @import("wasm-api");
const dom = @import("wasm-api-dom");
// expose thi.ng/wasm-api core API (incl. panic handler & allocation fns)
pub usingnamespace wasm;
// main entry point
export fn start() void {
init() catch |e| @panic(@errorName(e));
wasm.printStr("app started");
}
// allocator, also exposed & used by JS-side WasmBridge & DOM module
// see further comments in:
// https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig/lib.zig
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
pub const WASM_ALLOCATOR = gpa.allocator();
// since various initialization functions can return errors
// we're bundling them all in a single function, which is called by start()
// and so only needs one code site for error handling
fn init() !void {
// ...other initializations...
// create DOM tree/elements using thi.ng/wasm-api-dom (see readme)
_ = dom.createElement(&.{
.tag = "div",
.id = "app",
// CSS classes from tachyons.io for simplicity
.class = "bg-light-blue ma3 pa3",
// attach to document.body as parent element
.parent = dom.body,
// list of child elements
.children = dom.children(&.{
.{ .tag = "h1", .class = "ma0", .text = "Hello Zig! 👋" },
.{
.tag = "button",
.id = "counter",
// initial button label (will be modified by event handler)
.text = "Click me!",
.attribs = dom.attribs(&.{
// specify click event handler
dom.Attrib.event("click", incCounter, null),
}),
},
}),
});
}
// counter state
var counter: usize = 0;
// button event listener to update counter & label text
fn incCounter(e: *const dom.Event, _: ?*anyopaque) void {
counter += 1;
// create new label using temp buffer
var buf: [16]u8 = undefined;
var label = std.fmt.bufPrintZ(&buf, "Clicks: {d:0>4}", .{counter}) catch return;
// apply label to button element
dom.setInnerText(e.target, label);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
// define library build step
// use helper build script from hybrid thi.ng/wasm-api package
const lib = @import("node_modules/@thi.ng/wasm-api/zig/build.zig").wasmLib(b, .{
.modules = &.{
// declare other hybrid TS/Zig modules and their entry points
.{ .name = "wasm-api-dom", .path = "@thi.ng/wasm-api-dom/zig/lib.zig" },
},
// optionally define release mode
.optimize = .ReleaseSmall,
});
// create lib install step and add it to the top-level install step
b.installArtifact(lib);
}
import type { Fn0 } from "@thi.ng/api";
import { WasmBridge, type WasmExports } from "@thi.ng/wasm-api";
import { WasmDom, type WasmDomExports } from "@thi.ng/wasm-api-dom";
// (this import assumes we're using ViteJS, adjust as necessary...)
import WASM_URL from "./main.wasm?url";
// interface for combining our custom WASM export(s) with those
// provided by the thi.ng/wasm-api libraries
interface WasmApp extends WasmExports, WasmDomExports {
// declare exposed function(s) in WASM module (see main.zig)
start: Fn0<void>;
}
// main app initialization
(async () => {
// create WASM bridge with extra API module(s)
// here we're only adding the DOM module as additional requirement
const bridge = new WasmBridge<WasmApp>([new WasmDom()]);
// load and instantiate our WASM module
await bridge.instantiate(fetch(WASM_URL));
// call WASM start function to kick off
bridge.exports.start();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment