Counter — Mere → DOM
A minimal interactive demo for the
Mere frontend FFI
(Phase 48). The button below is wired to the count through
contrib/dom/dom.mere: the click handler is a Mere
closure compiled to Wasm, dispatched back from JavaScript via the
exported function table.
…
Source (counter.mere)
import "contrib/dom/dom.mere"; let display = dom_get_by_id "count" in let btn = dom_get_by_id "tick" in let counter = vec_new () in let _ = vec_push counter 0 in let _ = dom_set_text display "0" in let _ = dom_on_click btn (fn (u: unit) -> let cur = vec_get counter 0 in let next = cur + 1 in let _ = vec_set counter 0 next in dom_set_text display (show next) ) in 0
What's happening
-
mere -w counter.mere > counter.watproduces WebAssembly text format containing 4 host imports (dom_get_by_id,dom_set_text,dom_on_click,dom_input_value) plus an export of__indirect_function_table. -
wat2wasm counter.wat -o counter.wasmassembles the binary (~6KB). -
The page below loads
counter.wasmand wires the env imports to real DOM operations viacontrib/dom/dom.glue.js. -
When you click, the Mere closure runs:
vec_get/+ 1/vec_set/dom_set_text. The counter state lives in a single slot ofvec_new ()in the Wasm bump arena, which persists for the page lifetime.
How JS sees the closure
dom_on_click receives the closure as an
i32 pointer to a 2-word record
{ env, fn_idx } in linear memory. The host glue
reads both words and dispatches through the exported
__indirect_function_table:
// inside dom.glue.js
dom_on_click: (handleIdx, closurePtr) => {
const view = new Int32Array(memory.buffer);
const env = view[closurePtr >> 2];
const fnIdx = view[(closurePtr + 4) >> 2];
el.addEventListener("click", () =>
table.get(fnIdx)(env, 0)
);
},