Wrenlift

WebAssembly

The same runtime that compiles native ELF and Mach-O binaries also compiles to a self-contained .wasm module. wlift.js is the JavaScript harness you import to run Wren code in a browser — used by the playground and ready for any embedder.

Add wlift.js to your page

Wrenlift hosts the harness at wrenlift.com/dist/:

<script type="module">
  import { createWlift } from "https://wrenlift.com/dist/wlift.js";
  const wlift = await createWlift();
</script>

The harness lazily loads the matching wlift_wasm_bg.wasm module from the same origin. It auto-detects support for the WebAssembly SIMD proposal and loads the faster simd128 variant when available; older browsers get the scalar fallback.

Hosting it yourself? Copy the four files into your /dist/:

  • wlift.js
  • wlift_wasm.js
  • wlift_wasm_bg.wasm
  • wlift_wasm_bg.simd128.wasm

Hello world

<script type="module">
  import { createWlift } from "/dist/wlift.js";

  const wlift = await createWlift();
  const result = await wlift.run('System.print("hello, web")');
  console.log(result.output);  // "hello, web\n"
</script>

result is a { ok, output, errorKind, elapsedMs } shape. output captures everything the script wrote to System.print; errorKind is non-null when the program threw.

runProject — source + hatchfile

For anything beyond a one-liner, you pass the program source and its hatchfile so dependencies install through the registry:

import { createWlift, runProject } from "/dist/wlift.js";

const wlift = await createWlift();
const source = `
  import "@hatch:json" for JSON
  System.print(JSON.encode({"hello": "world"}))
`;
const hatchfile = `
  name = "demo"
  version = "0.1.0"
  entry = "main"

  [dependencies]
  "@hatch:json" = "0.1.4"
`;
const result = await runProject(wlift, source, hatchfile);
console.log(result.output);

runProject parses the manifest, fetches every declared dep from the Hatch registry, installs them, then runs the user source. It returns the same result shape as wlift.run.

Bundle caching

Fetched .hatch bundles are cached via the browser's Cache API by default. The next page load resolves cached deps instantly, no network round-trip. Override the cache name or opt out for tests:

// Default cache name:
await runProject(wlift, source, hatchfile);

// Per-tenant cache:
await runProject(wlift, source, hatchfile, { cache: "tenant-abc" });

// Disable persistence:
await runProject(wlift, source, hatchfile, { cache: false });

Wipe the stored bundles when a release goes wrong:

import { clearHatchCache } from "/dist/wlift.js";
await clearHatchCache();

Prefetching deps

Warm the cache while the user is still typing — running the code itself stays snappy because the bundle bytes are already on disk by the time runProject needs them:

import { prefetchHatchDeps } from "/dist/wlift.js";

editor.on("change", debounce(() => {
  prefetchHatchDeps(getHatchfileText())
    .catch(err => console.warn("prefetch", err));
}, 600));

Worker vs main thread

Wrenlift runs on whatever thread createWlift is called from. The runtime is cooperative — a long-running script blocks the thread until it suspends on a fiber yield or finishes. For UI-heavy pages, run the VM in a Web Worker and post messages from the main thread:

// worker.js
import { createWlift, runProject } from "/dist/wlift.js";
const wlift = await createWlift();
self.onmessage = async (e) => {
  const result = await runProject(wlift, e.data.source, e.data.hatchfile);
  postMessage(result);
};

WebGPU canvas rendering needs an OffscreenCanvas transferred to the worker via postMessage. The playground does this through the canvas key on the worker bridge; see wasm/web/worker.js for a complete example.

SIMD detection

Skip the SIMD-fallback variant entirely on browsers that support the proposal:

import { simd128Supported, createWlift } from "/dist/wlift.js";

if (simd128Supported()) {
  console.log("loading SIMD wasm");
}
const wlift = await createWlift();   // picks the right binary

createWlift already does the right thing; simd128Supported() is exposed for embedders who want to surface the choice (analytics, opt-in flags, etc.).