See the shape of your wasm
Read, edit and build WebAssembly modules from JavaScript. Every function, global, memory, import and export is a live handle — read through to the module, write straight back, then emitWasm().
Inspect. Edit. Build.
One module handle, three things to do with it. The same graph you read is the graph you mutate and the graph you emit.
Walk every collection through live handles — exports, imports, globals, memory, types. Iterate, or look items up by name.
import { WasmModule } from '@napi-rs/wasm-tools'
const mod = WasmModule.fromBuffer(bytes)
// Every collection is a live handle: .length, .items(), lookups.
for (const exp of mod.exports.items()) {
if (exp.kind === 'Global') {
console.log(exp.name, exp.global()!.ty.type)
}
}
mod.exports.byName('run') // WasmExport | null
mod.imports.find('env', 'log') // WasmImport | nullAssign to a handle and the write lands in the owning module. Rename an export, flip a global, grow memory — then emit.
// Mutations write straight back to the owning module.
mod.name = 'patched-module'
mod.exports.byName('run')!.name = 'main' // rename an export
mod.exports.byName('counter')!.global()!.mutable = true
mod.mainMemory!.initial = 4n // grow memory (bigint)
const bytes = mod.emitWasm(false)Synthesize functions from an instruction-descriptor tree, add locals, wire exports, and emit a module that really runs.
import { I32, WasmModule } from '@napi-rs/wasm-tools'
// Locals are module-wide; make them, then bind as params.
const a = mod.locals.add(I32)
const b = mod.locals.add(I32)
const idx = mod.buildFunction(
[I32, I32], [I32], [a.index, b.index],
[
{ type: 'LocalGet', local: a.index },
{ type: 'LocalGet', local: b.index },
{ type: 'Binop', op: 'I32Add' },
],
)Edit an export, re-emit, it persists
There is no separate mutation API and no rebuild step. A handle is a window onto the module — reads read through, writes write back. Round-trip through the bytes to prove it.
parse — fromBuffer reads the module into a live graph.
edit — Assign to a handle — the write lands in the module.
re-emit — emitWasm serializes the mutated graph to bytes.
prove — Re-parse the bytes: every edit is baked in.
import { readFile } from 'node:fs/promises'
import { WasmModule } from '@napi-rs/wasm-tools'
const mod = WasmModule.fromBuffer(await readFile('./module.wasm'))
mod.exports.byName('run')!.name = 'main' // rename an export
mod.mainMemory!.initial = 4n // grow initial memory
// Re-parse the emitted bytes to prove every edit baked in:
const out = WasmModule.fromBuffer(mod.emitWasm(false))
out.exports.byName('run') // null
out.exports.byName('main')!.kind // 'Function'
out.mainMemory!.initial // 4nimport { I32, WasmModule } from '@napi-rs/wasm-tools'
// Start from the 8-byte empty module (`\0asm` + version 1).
const mod = WasmModule.fromBuffer(new Uint8Array([0x00, 0x61, 0x73, 0x6d, 1, 0, 0, 0]))
// Locals are module-wide; make them, then bind as params.
const a = mod.locals.add(I32)
const b = mod.locals.add(I32)
const idx = mod.buildFunction(
[I32, I32], [I32], [a.index, b.index],
[
{ type: 'LocalGet', local: a.index },
{ type: 'LocalGet', local: b.index },
{ type: 'Binop', op: 'I32Add' },
],
)
mod.exports.addFunction('add', mod.functions.getByIndex(idx)!)
const bytes = mod.emitWasm(false)
// new Uint8Array(bytes) narrows the buffer type so instantiate picks its BufferSource
// overload (emitWasm's Uint8Array<ArrayBufferLike> otherwise resolves to the Module one).
const { instance } = await WebAssembly.instantiate(new Uint8Array(bytes))
const add = instance.exports.add as (a: number, b: number) => number
console.log(add(2, 3)) // 5
// The body round-trips back to descriptors:
const fn = WasmModule.fromBuffer(bytes).exports.byName('add')!.func()!
fn.instructions()
// [ { type: 'LocalGet', local: 0 }, … { type: 'Binop', op: 'I32Add' } ]Describe add(a, b), run it, get 5
buildFunction takes params, results, the locals that are params, and an instruction-descriptor tree. Export it, emit, and WebAssembly.instantiate runs the real thing. The body round-trips back to the same descriptors.
27 classes, 17 value types
The full walrus module graph, exposed. Collections hand out handles; handles read and write through to the module; value types come ready-made so you import I32 instead of spelling it out.
Core
3Entry points — configure a parse, hold a module, build const exprs.
Collections
11Live arena sets on a module: .length, .items(), lookups and add* factories.
Handles
11One item each — read a property through to the module, write it back.
Section façades
2Single-section wrappers with their own API — not .length / .items() collections.
Value types
17Numeric types and the nullable reference types, exported as constants — import { I32 } instead of { type: 'I32' }.
PLAYGROUND
Walk your module in the browser
Paste WAT or drop a .wasm and watch the graph build itself — parse, inspect, edit and re-emit, all client-side.