Embedding xs.wasm

Run XS in the browser by loading xs.wasm and xs.js from static.xslang.org. Same compiler the playground uses, with a virtual filesystem and captured stdout.

What it is

static.xslang.org/xs.wasm is the compiler itself, built with wasi-sdk. xs.js is a thin wrapper that instantiates the module, exposes a virtual filesystem, and routes stdout / stderr / stdin through callbacks. After loadXS() resolves, xs.run(source) evaluates an XS program and xs.exec(argv) runs the same CLI a native install would.

Minimal page

<!doctype html>
<script src="https://static.xslang.org/xs.js"></script>
<script>
  const xs = await loadXS();
  await xs.run('println("hello from the browser")');
</script>

That is the whole setup. The script tag pulls the wrapper from the CDN; the wrapper fetches and instantiates the wasm itself.

loadXS options

Capture stdout instead of writing to console.log:

const xs = await loadXS({
  stdout: (line) => document.getElementById("out").append(line + "\n"),
});
await xs.run(`
  for i in 1..=3 {
    println("tick " + str(i))
  }
`);

Preload files into the virtual filesystem so use and import resolve:

const xs = await loadXS({
  fs: { files: { "main.xs": "use util\nutil.greet(\"world\")",
                 "util.xs": "fn greet(name) { println(\"hi \" + name) }"" } },
});
await xs.exec(["xs", "main.xs"]);

Run input() against a JS prompt. worker: true spawns the wasm in a Web Worker so blocking stdin works without freezing the main thread; it requires a SharedArrayBuffer (cross- origin isolated page).

const xs = await loadXS({
  worker: true,
  stdin: async () => prompt("?") + "\n",
});
await xs.run('let n = input("number: "); println(int(n) * 2)');

vs. --emit js

xs --emit js rewrites your XS program as JavaScript that runs directly in Node or a browser, no XS runtime needed. It is the right path if you want a small, dependency-free bundle of one program.

xs.wasm is the opposite trade. The whole compiler is running on the page; you can write or evaluate any XS program at runtime, files persist in IndexedDB if you ask, and behaviour matches the native binary instead of mapping through JS semantics. It is what the playground uses. If you are building a code editor, a tutorial, or anything that needs to run user-supplied XS, embed xs.wasm; otherwise --emit js ships less.