Ruby in browser via WebAssembly

In a significant move for the Ruby programming language, the latest version, 3.2, now includes support for WebAssembly (Wasm). This update allows Ruby developers to move beyond the backend and port their code to Wasm, enabling them to run their applications on multiple platforms, including the frontend, embedded devices, serverless functions, containers, and on the edge. The addition of Wasm support could make Ruby a universal language, comparable to other languages that can already target Wasm, such as C, Go, and Rust.

Wasm is a binary, low-level instruction format designed to run applications on any browser at near-native speeds. The technology is considered an alternative to JavaScript, and has been a W3C standard since 2019. Portability and security are its key pillars: wasm binaries can run on any modern browser or mobile device, while wasm programs run in a sandboxed, memory-safe virtual machine, preventing access to system resources.

With the new Wasm port of the interpreter, Ruby code can now run directly in the browser without the need for a backend. The example provided by the Ruby developers shows how easy it is to get started with Wasm support. However, there are still some limitations to be aware of, such as the lack of threading and networking support, potential memory leaks from the garbage collector, and the unavailability of gems and modules without a custom wasm image.
Let's see an example:

<html>
<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    puts "Hello world from chrome console!"
  </script>
</html>

That will print hello world in your browser console - but what if we want to mix HTML with JS? Here is the sample:

<html>
<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/browser.umd.js"></script>
<script>
  const { DefaultRubyVM } = window["ruby-wasm-wasi"];
  const main = async () => {
    const response = await fetch("https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@latest/dist/ruby.wasm");
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.compile(buffer);
    const { vm } = await DefaultRubyVM(module);

    vm.printVersion();
    vm.eval(`
require "js"
puts "Hello from chrome console"

class User
  attr_accessor :name, :email

  def initialize(name, email)
    @name = name
    @email = email
  end

  def print_me
    "Name: #{@name}, Email: #{@email}"
  end
end
user = User.new('John', '[email protected]')
output = user.print_me

JS::eval("document.querySelector('body').appendChild(document.createElement('div')).innerText = '#{output}'")
      `);
  };

  main();
</script>
<body style="font-size: 3rem">
<p>Hello from HTML</p>
</body>
</html>

Despite these limitations, the addition of Wasm support to Ruby opens up exciting possibilities for the language. As the tools and support around Wasm improve, Ruby developers can use this technology to explore new frontiers, such as running Ruby on the edge and serverless applications. While it may be some time before we see complex Ruby applications running with Wasm, this is a significant step in that direction.

Overall, the inclusion of Wasm support in Ruby's latest release represents a significant step forward for the language and its ecosystem. Developers can begin experimenting with the technology, and with further development, Ruby could become a more versatile language that runs on a wide range of platforms.