The following article teaches about WASM
, WASI
, Web Components and how easy it is to plug a Web Component that runs pre-compiled WASM code in the browser.
https://runno.dev/articles/wasi-web-component
Web Components are a really interesting API for JavaScript libraries. Here’s a Web Component I’ve built that can run WASI binaries.
The HTML is very simple:
<runno-wasi src="https://assets.runno.dev/ffmpeg/ffmpeg.wasm" autorun>
</runno-wasi>
Background
As part of recent work to make Runno hosted on its own browser-specific WASI runtime I built my own implementation of WASI (@runno/wasi
). After building the implementation, I rewrote the way the main Runno package works to use my own WASI implementation under-the-hood. Because Runno is all about using Web Components to run short snippets of code directly from HTML, I realised it would be neat to also support running WASI binaries this way.
It needs the same setup that a normal Runno runtime needs, you need to be in a Cross-Origin Isolated Context, and you need to include the @runno/runtime
package (see the docs), but once you’ve done that it’s pretty straightforward to run WASI binaries. That opens up a bunch of ways to run CLI tools in your web browser, whether they’re built in Rust, C, Zig, C# or the variety of other languages that support compiling to WASI.
Note: If you’re lost about what the heck WASI is, it stands for WebAssembly System Interface. It’s a standard interface for WASM (WebAssembly) binaries to interact with a host (like an OS). In a binary built for an old Intel mac the target might be x86-macos
. In that string x86
is the CPU and macos
is the operating system. These binaries are wasm-wasi
so WASM is the CPU, and WASI is the operating system.
Examples
Running a CLI program with no args is very simple.
<runno-wasi src="https://assets.runno.dev/ffmpeg/ffmpeg.wasm" autorun>
</runno-wasi>
You specify the src
of the WASI binary as an attribute, just like an img
. If you want it to automatically run, then specify autorun
as an attribute. In this example we’re running ffmpeg
with no args, and no files. You’ll notice that terminal output is automatically displayed, this terminal can be styled with CSS (docs).
Running ffmpeg
without anything in the filesystem isn’t particularly useful, so lets look at how we can add files to the runno-wasi
element. The simplest way to do this is with the runno-file
element.
<runno-wasi
src="https://assets.runno.dev/examples/cat.wasi.wasm"
args="/foo.txt /bar.txt"
autorun
>
<runno-file path="/foo.txt">
Hello, World!
</runno-file>
<runno-file path="/bar.txt">
Lets concatenate these files.
</runno-file>
</runno-wasi>
Hello, World! Lets concatenate these files.
In this example I’m using a cat
program implemented in Rust (source). This program concatenates two files passed as arguments. I’ve configured the two files by using runno-file
elements. The path
attribute specifies the full path of these files in the filesystem and must start with /
.
Then to tell cat
what the two files are, I’ve passed args
as an attribute to the element. This is just like your args in a shell, a space seperated string that is passed to the program as part of execution. Putting it all together we get concatenation!
Specifying files this way is neat, but it’s going to be hard to use with ffmpeg
, we’d need to write out the video file as text in the web browser, and I’m not sure HTML escape codes are up to the task. So instead we can specify a url
attribute on those wasi-file
elements to have them pull down their content from a URL.
<runno-wasi
src="https://assets.runno.dev/ffmpeg/ffprobe.wasm"
args="/serenity.mp4"
autorun
>
<runno-file
path="/serenity.mp4"
url="https://assets.runno.dev/ffmpeg/serenity.mp4"
>
</runno-file>
</runno-wasi>
In this example we’re using ffprobe
to get information about an mp4
file. The file in question is the Serenity trailer. Before the WASI binary is run, the video file is downloaded. Then ffprobe
runs over the file and outputs metadata about it. You can see this was the DVD Trailer from 2005!
In some circumstances you might want to specify a full filesystem, not just a couple of files. In those cases you can pass an fs-url
to the runno-wasi
element.
<runno-wasi
src="/langs/python-3.11.3.wasm"
fs-url="https://assets.runno.dev/examples/python-package.tar.gz"
controls
>
</runno-wasi>
In this example I’ve used a binary of Python 3.11 compiled to WASI, along with the files required for a python package. When you run it you should get a Python REPL, it’s a little bit dodgy but mostly works.
That python-package.tar.gz
file contains a minimal filesystem that looks like:
$ tar xzvf python-package.tar.gz
x package/
x package/__init__.py
When extracted, the files will have a /
added to the front, to make them compatible with the Runno filesystem. When python runs it will have this package available in the local filesystem, you can try it yourself in the REPL above:
>>> from package import say_hello
>>> say_hello()
I’ve also specified controls
as an attribute, this gives the user a run button that they can click to run the code. Without controls
or autorun
you can call run
directly on the element reference. That’s the handle you get back from running something like querySelector('runno-wasi')
.
Headless
If you’re interested in running WASI binaries in the browser headlessly, have a look at my @runno/wasi
package. It’s the implementation that sits underneath these web components, and has a pretty neat external interface.
Post-script
Thanks for having a read and I hope you enjoyed these examples! I’m having a lot of fun working with WASI in the browser. It’s interesting seeing the cool stuff you can do when you stick to standards. If you use Runno in one of your own projects, I’d love to hear from you!
I’m heading over to Seattle from Australia to be at WASMCon2023, if you’re planning on attending and would like to chat, shoot me an email or find me on Mastodon. The links are in the footer.
#reads #ben taylor #browser #wasm #wasi #web components #html #python