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)');

Iframe the playground

If you just want a runnable snippet on a page (a docs site, a blog post, a tutorial) and don't care about owning the chrome, drop an iframe pointing at xslang.org/embed. The frame renders the same editor + run button + output panel the main playground uses, with no nav, no sidebar, no file sheet.

<iframe
  src="https://xslang.org/embed?code=<encoded>"
  width="100%"
  height="420"
  frameborder="0"
  allow="cross-origin-isolated"
  loading="lazy"
></iframe>

The ?code=parameter carries the whole workspace (multiple files, with a chosen active tab) in a URL-safe gzip payload. Don't hand-roll it. Open the workspace in the playground, click share, switch to the embed tab, and copy the iframe snippet it produces. Optional ?file=<name> overrides which tab opens, and ?theme=light|dark pins the colour scheme.

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.