backgroundradial

Introducing the new Wasmer JS SDK

Today we are incredibly excited to present `@wasmer/sdk`, a new library that allows running WASI(X) applications easily on the browser

michael-f-bryan avatar
michael-f-bryan
Michael Bryan

Software Engineer

browser

December 13, 2023

arrowBack to articles
Post cover image

Dive into a world where running any WASI and WASIX package in your browser is a breeze. Whether it's Python, Bash, FFmpeg, or any package published in the registry, Wasmer Javascript SDK makes it all seamlessly possible.

We think this is incredibly exciting, because the @wasmer/sdk allows running any UNIX programs using threads, signals, subprocesses and other features in the browser (via WASIX).

With the new SDK, you just need to point to the desired Wasmer package and the SDK will download all the files (.wasm and the filesystem required) for you.

Let's see some cool examples running the Wasmer JS SDK, shall we?

  • FFMpeg
  • CPython (with threads and signals!)
  • Bash (wasmer.sh demo!)

Note: @wasmer/sdk supersedes the @wasmer/wasi package

Any WASIX package published to Wasmer will be instantly usable with the Wasmer Javascript SDK: in this article we will showcase how to use FFMpeg and Python, but you can use your own package just as easily.

A love story: FFMpeg

FFMpeg is one of the most useful and popular packages in the world. It’s used by Youtube, Facebook, Netflix and many other industry leaders. It allows them to transform and edit videos very easily.

FFMpeg has been ported a few times to Javascript already, thanks to Emscripten and WebAssembly. However, these ports required a lot of manual changes from the maintainers to plug in all the features and make the package easily usable for developers.

Thanks to the new Wasmer JS SDK, the ffmpeg WASIX version published in Wasmer can now be easily in the browser.

Let's see an example where we retrieve a video (wordpress.mp4) and extract its audio:

import { init, Wasmer } from "@wasmer/sdk";

await init();

let ffmpeg = await Wasmer.fromRegistry("wasmer/ffmpeg");
let resp = await fetch("https://cdn.wasmer.io/media/wordpress.mp4");
let video = await resp.arrayBuffer();

// We take stdin ("-") as input and write the output to stdout ("-") as a
// WAV audio stream.
const instance = await ffmpeg.entrypoint.run({
  args: ["-i", "-", "-f", "wav", "-"],
  stdin: new Uint8Array(video),
});

const { stdoutBytes } = await instance.wait();
console.log(`The audio stream: ${output.stdoutBytes}`);

Et voilá, you have the audio now created: wordpress-ffmpeg-out.wav

You can get the audio file by running ffmpeg locally with wasmer: cat wordpress.mp4 | wasmer run wasmer/ffmpeg -- -i - -f wav - > audio.wav

Running CPython in the browser

There was another project that allowed using CPython in the browser: Pyodide. However, Pyodide is based on Emscripten and as such only runs on the browser, but not only that... Pyodide doesn't support threading.

Wasmer python package can run on both the browser and the server seamlessly, and requires no manual intervention to run in the browser (no extra packages needed other than the SDK!).

Here's how you can handle CPython with the Filesystem API of the Wasmer JS SDK:

import { init, Directory, Wasmer } from "@wasmer/sdk";
 
// Initialize the Wasmer SDK
await init();
 
// Download the Python package
const python = await Wasmer.fromRegistry("python/python");
 
// The script to be executed
const script = `
import sys
 
with open("/out/version.txt", "w") as f:
  f.write(sys.version)
`;
// A shared directory where the output will be written
const out = new Directory();
 
// Running the Python script
const instance = await python.entrypoint.run({
  args: ["/src/main.py"],
  mount: {
    "/out": out,
    "/src": {
      "main.py": script,
    }
  },
});
const output = await instance.wait();
 
if (!output.ok) {
  throw new Error(`Python failed ${output.code}: ${output.stderr}`);
}
 
// Read the version string back
const pythonVersion = await out.readTextFile("/version.txt");
console.log(pythonVersion) // 3.11.6 (main, ...)

Check out the FileSystem guide to learn more.

Wasmer.sh

As an example, our own wasmer.sh website has now an incredibly simple implementation that just fetches the bash package, and pipes it over xterm.js using the Wasmer Javascript SDK.

Wasmer.sh using new Wasmer JS SDK

Something that before required 1,000 lines of code in Rust + JS glue code manually written, now can be written in less than 50 lines of clear JavaScript code

You can now have your own terminal emulator running in your website very easily using XTerm.js and the Wasmer JS SDK:

// This is a simplified version of:
// https://github.com/wasmerio/wasmer-js/blob/main/examples/wasmer.sh/index.ts
import { Terminal } from "xterm";

async function main() {
  const { Wasmer, init } = await import("@wasmer/sdk");
  await init();

  const term = new Terminal();
  term.open(document.getElementById("terminal")!);

  const pkg = await Wasmer.fromRegistry("sharrattj/bash");
  const instance = await pkg.entrypoint!.run();
	
  connectStreams(instance, term);
}

function connectStreams(instance, term) {
  const stdin = instance.stdin?.getWriter();
  const encoder = new TextEncoder();
  term.onData(data => stdin?.write(encoder.encode(data)));
  instance.stdout.pipeTo(new WritableStream({ write: chunk => term.write(chunk) }));
  instance.stderr.pipeTo(new WritableStream({ write: chunk => term.write(chunk) }));
}

main();

Technical feat

Making the Wasmer JS SDK to work was not an easy task. We had to to achieve incredibly hard challenges, so you don't need to worry about solving them yourself:

  • Using asyncify to be able to fork processes
  • Using a universal format to containerize the Wasm and filesystem together
  • A Filesystem abstraction to ease the container use

Lets get into this a bit more!

Supporting multithreading

Javascript runs in a single thread. However, it is possible to use JS Workers to enable multithreading in the browser. The Workers API differs from the typical threading API (that you would use in Python, Go or Rust), in the sense that there's no join (to wait for a thread to finish). The JS Worker communicates with the main process via a message channel/bus.

At the end, we were able to allow UNIX-like threading to run via JS Workers by reusing the shared memory from the main WebAssembly process and notifying the memory upon changes (using Wasm notify/wait syntax).

Running fork in the browser

Running fork in the browser is almost an impossible task, as it requires dumping the stack and being able to resume it from a different process.

We used asyncify to freeze the program with the current stack, and then resume it later in a newly created Worker.

An universal container format

The Wasmer API now ships a special container format (that we will cover in future blogposts) that allows serving both the Wasm assets along with the filesystem ones. That way, you can download one package with all it's filesystem dependencies at once (such as Python!).

This container format is the format also used in both the native runtime and Wasmer Edge. More to come on this later!

A Filesystem API

We realized that using the Filesystem was critical to allow any developer achieving their needs.

Read more about the Wasmer JS SDK Filesystem API here: https://docs.wasmer.io/javascript-sdk/how-to/use-filesystem


In summary

We can’t wait to see how you use the Wasmer JS SDK to use all packages published in the Wasmer ecosystem.

We have many examples on how to use Wasmer-JS:

Using Wasmer SDK in the browser requires you setting the Cross-origin isolation headers (COOP and COEP) in the pages that use the SDK. If you can’t do this (because you are publishing to Github Pages, for example) you can use coi-serviceworker to automatically patch the headers client-side (see example).

For more details, check out SharedArrayBuffer and Cross-Origin Isolation in the JavaScript SDK docs.

Bash, Python, FFMpeg, QuickJS, OpenSSL, and many more packages are waiting for you!

Get started with the Wasmer JS SDK docs : https://docs.wasmer.io/javascript-sdk

Let us know what you build next on Wasmer's discord

About the Author

Michael is a Rust sorcerer working in WASIX and Wasmer SDK

Michael Bryan avatar
Michael Bryan
Michael Bryan

Software Engineer

Read more

runtimeengineeringwasmer runtime

Wasmer 2.1

Syrus AkbaryDecember 1, 2021

runtimeengineeringwasmer runtime

Wasmer 2.3

Syrus AkbaryJune 7, 2022

runtimeengineeringwasmer runtime

Wasmer 3.2

Syrus AkbaryApril 18, 2023

runtimeengineeringwasmer runtime

Wasmer 2.0, It's a big deal!

Syrus AkbaryJune 2, 2021