Ruby in WebAssembly
Ruby is one of the most popular scripting languages. Famous for the Rails framework, it has been a stalwart for web developers.
Ruby now has multiple WebAssembly-based projects, including an official release of CRuby.
Available Implementations
There is an official Wasm build of Ruby. It supports WASI and a wide array of features. VMware provides a release of the official Ruby runtime as Wasm Shopify has a Wizer-optimized version of Ruby, which they call Ruvy, that speeds up startup time. This, too, uses the official CRuby.
In addition to the official Ruby distribution, Artichoke is a Rust implementation of Ruby that can compile to WebAssembly (wasm32-unknown
).
rlang
(a subset of Ruby) can run Wasm32 code in a wasm32-wasi
runtime like wasmtime
.
In this guide, we focus on the official release of Ruby (ruby.wasm
).
Usage
To use the official Ruby Wasm, download a prebuilt binary and decompress the downloaded archive.
Inside of the package, you will find a full distribution of Ruby:
$ tree -L 3
.
├── usr
│ └── local
│ ├── bin
│ ├── include
│ ├── lib
│ └── share
└── var
└── lib
└── gems
The Wasm binaries are in /usr/local/bin
. For example, to run the Ruby interpreter, you can use wasmtime ./usr/local/bin/ruby
. The example section below illustrates usage.
Pros and Cons
Things we like:
- The toolchain has been very thoughtfully developed
- It is possible to use gems that do not have C code. (We suspect there might be a way to use C extensions, but we haven’t figured it out)
We’re neutral about:
- The size of the interpreter is large, and can take a moment to start
- Spin (and Wagi) need extra configuration to simulate a command line for Ruby
Things we’re not big fans of:
- At this stage, users of the Wasm version will need to understand how Ruby loads its dependencies
Example
All of our examples follow a documented pattern using common tools.
Ruby can run in either Spin or Wagi. Here, we show how to use Spin.
Start out with a new directory:
$ mkdir hello-ruby
$ cd hello-ruby
Now fetch a copy of the Ruby source from the official releases.
For this example, we are downloading the ruby-head-wasm32-unknown-wasi-full.tar.gz
version,
but one of the smaller versions works just as well.
Now create a local lib
dir where we will put our own source.
It is also a good idea to create .gem/
, though we won’t use it in this example.
$ mkdir lib
$ mkdir .gem
At this point, our directory should look like this:
$ tree -L 2 -a -d
.
├── .gem
├── head-wasm32-unknown-wasi-full
│ ├── usr
│ └── var
└── lib
Inside of lib
, we can create hello.rb
:
puts "content-type: text/plain"
puts ""
puts "Hello, World"
You can verify this works by using ruby lib/hello.rb
.
$ ruby lib/hello.rb
content-type: text/plain
Hello, World
Ruby is a scripting language, which means it will need to load a number of scripts (ours plus all of the built-in libraries) off of its filesystem. The spin.toml
file for Ruby is more complex than most:
spin_version = "1"
name = "example-ruby-app"
version = "0.1.0"
trigger = { type = "http", base = "/" }
[[component]]
files = [
{ source = "lib", destination = "/lib" },
{ source = ".gem", destination = "/.gem" },
{ source = "head-wasm32-unknown-wasi-full/usr", destination = "/usr" },
]
id = "ruby"
source = "head-wasm32-unknown-wasi-full/usr/local/bin/ruby"
[component.trigger]
executor = { type = "wagi", argv = "${SCRIPT_NAME} -v /lib/hello.rb ${SCRIPT_NAME} ${ARGS}" }
route = "/"
[component.environment]
HOME = "/"
GEM_HOME = "/.gem"
Note that we need to mount several sets of files: lib
, .gem
, and usr
. This exposes all of Ruby’s supporting files.
(Remember: Ruby is a scripting language, and only the interpreter is compiled to Wasm. The rest is Ruby source.)
While lib
and .gem
should point to your local dev environment, you need to load usr
from the Ruby project.
A few environment variables also need to be set for the interpreter: HOME
and GEM_HOME
.
These should usually be set exactly as above.
Next, running spin up
will start the server.
$ spin up
Preparing Wasm modules is taking a few seconds...
Serving HTTP on address http://127.0.0.1:3000
Note that the first line occurs because when Spin starts up, it does take Ruby a few moments to load all of its supporting files. Using one of the smaller instances will improve startup time and overall performance.
From here, we can run our usual curl
command or point a web browser to Spin:
$ curl localhost:3000
Hello, World
And that’s all there is to it.
If you need help with Ruby and Spin/Wagi, join our Discord and ask. We know it’s not the easiest environment to get running.
Learn More
Here are some great resources:
- The official project explains building
ruby.wasm
- Ruvy is the official Ruby, but optimized (via Wizer) for faster startup times, and to not load script files. This works similarly to Fermyon’s JS and Python SDKs.
- VMware’s Ruby interpreter compiled to Wasm
- InfoWorld (Oct. 2023) explains Shopify’s new Ruvy runtime
- An update on the state of Ruby, Wasm, and WASI in March, 2022
- Instructions for building Ruby with wasm32-wasi support
- A fun dice roller browser app written in Ruby. Source is on GitHub
- Rlang compiles a subset of the Ruby language to Wasm
- Artichoke is a Rust implementation of Ruby that can compile to WebAssembly (
wasm32-unknown
) - The mruby runtime has been compiled to WebAssembly, which means you can interpret a Ruby script inside of a Wasm module
- Prism uses
mruby
to run Ruby inside of WebAssembly - While it doesn’t seem to be active anymore, run.rb is a project for running Ruby in the browser as Wasm.
- wruby also no longer looks active, but it was an
mruby
-based in-browser Ruby interpreter.