mola-sitegen-wren logo
mola-sitegen-wren

Wren, Zig, HTML IR, WASM

Architecture

How the pieces fit together.

mola-sitegen1

Owns HTML IR, rendering, and filesystem emission into dist/.

mola-wren

Owns the embedded Wren VM and foreign-function bindings.

Wren authoring

Owns page composition, copy, loops, and static site declarations.

Browser runtime

Owns live demo state, fetching strategy, and client-side rendering.

Build-time flow

Static build

  • 1. Zig boots Wren

    src/main.zig configures the embedded runtime.

  • 2. Wren declares pages

    wren/main.wren calls Site.page(...) and Site.asset(...).

  • 3. Host bindings translate IR

    src/sitegen_binding.zig maps Wren calls into native html.Node values.

  • 4. dist/ is emitted

    Pages, CSS, JS, and app.wasm become normal static files.

Browser flow

Browser runtime

  • 1. app.js boots the host

    It discovers DOM roots and instantiates app.wasm.

  • 2. Zig/WASM owns the state machine

    Mode changes, fetch sequencing, and rendering stay in Zig.

  • 3. JS performs browser syscalls

    fetch, DOM writes, and event attachment stay in the host.

  • 4. HTML comes back from Zig

    The runtime still renders browser fragments through mola-sitegen1 HTML IR.

Code example

Wren to Zig HTML IR

Wren stays expressive and small while Zig keeps ownership of the actual HTML node model.

class Html {
  static h2(className, text) {
    return Html.el("h2", [Html.attr("class", className)], [Html.text(text)])
  }
}

Site.page("/architecture/index.html", "Architecture", "How the project fits together.", Pages.architecture(posts))

Code example

Zig foreign binding

The host boundary is explicit. Wren does not bypass Zig to emit files.

if (std.mem.eql(u8, class_name, "Node")) {
    if (std.mem.eql(u8, sig, "text(_)")) return nodeText;
    if (std.mem.eql(u8, sig, "el(_,_,_)")) return nodeEl;
}

if (std.mem.eql(u8, class_name, "Site")) {
    if (std.mem.eql(u8, sig, "page(_,_,_,_)")) return sitePage;
}

Code example

WASM runtime boundary

The browser host is deliberately small. Zig owns the app logic and asks JS for browser capabilities.

extern "env" fn jsFetch(panel_index: u32, request_id: u32, phase: u32, url_ptr: u32, url_len: u32) void;

pub export fn selectMode(panel_index: u32, mode_ptr: u32, mode_len: u32) void {
    const mode = ptrToConstSlice(mode_ptr, mode_len);
    startFeedRequest(panel_index, mode) catch logError("failed to switch live panel mode");
}

Project map

Key files

  • wren/main.wren

    Registers the pages and assets for the static build.

  • wren/pages.wren

    Contains the project-site page composition.

  • src/sitegen_binding.zig

    Foreign binding layer from Wren into Zig HTML IR.

  • src/live_panel.zig

    Shared renderer for browser-side HTML fragments.

  • src/live_panel_wasm.zig

    Stateful WASM runtime for the demo page.

  • assets/app.js

    Browser host bridge only.