Move Introduction
Open Introduction
Intro to Rails on Wasm
Ruby on Rails is a famous “batteries included” framework for the rapid development of web applications. Its full-stack promise comes in a server-oriented, or HTML-over-the-Wire flavor: a server oversees everything from database interactions to your application UI/UX. And whenever there is a server involved, the network and its unpredictability come into play.
No matter how much you enjoy developing with Rails, it would be hard to achieve the same level of user experience as with client-side and especially local-first frameworks. And here comes Wasm. With the help of WebAssembly, we can do a radical shift—bring the Rails application right into your browser, and make it local-first or at least offline-ready!
This handbook contains recipies, ideas, and learning materials on how to leverage Wasm in Rails applications.
Introduction
132 words
Move What is Wasm
Open What is Wasm
What is Wasm
Wasm, or WebAssembly is a binary instruction format for a stack-based virtual machine. In other words, it's a custom, language- and platform-agnostic byte-code representation of your code.
An example Wasm module bytes and instructions (via Wasm Code Explorer)
Whether you write in C, C++, Rust, Go, Ruby—almost any programming language—you can compile your code into a .wasm
binary module and execute the program within a Wasm runtime.
The major benefits of using Wasm are:
Speed. WebAssembly instructions are closer to machine code and could be optimized efficiently.
Portability. You can compiler your code into a Wasm module once and use everywhere.
Safety. Wasm runtimes are sandbox execution environments.
Let's talk about runtimes for a bit.
Wasm runtimes
The original and the most popular Wasm
What is Wasm
240 words
Move What is WASI
Open What is WASI
What is WASI
Another important component of wasmification is WASI. WASI, or WebAssembly System Interface, is a group of standard API specifications for Wasm programs and runtimes. In others words, WASI specifies how your Wasm-compiled program can communicate with the outer world (i.e., which system calls are availables) if this outer world complies with particular WASI standards.
WASI makes Wasm modules truly portable: your code shouldn't no about whether it's about to be executed in the browser or in some other Wasm runtime. It only requries for specific API to be implemented.
For example, WASI defines how to work with the underlying filesystem, random number generators, etc.
WASI is still a young initiative. There were just two releases: so-called Preview 1 and Preview 2 (not they're called WASI 0.1 and WASI 0.2 respectively).
Preview 1 provided only basic syscall APIs while Preview 2 consists of multiple modules (clocks, http, cli, etc.) and, more importa
What is WASI
229 words
Move Meet ruby.wasm
Open Meet ruby.wasm
Meet ruby.wasm
Finally, let's talk about Ruby and its wasm-ability.
One of the highlights of the Ruby 3.2 release was the WASI based WebAssembly support. What that meant is that from then on, the CRuby VM could be compiled into a ruby.wasm
module and run in the browser!
You can check this yourself using the following HTML/JS code:
<html>
<script src="https://cdn.jsdelivr.net/npm/@ruby/3.3-wasm-wasi@2.6.2/dist/browser.script.iife.js"></script>
<script type="text/ruby">
require "js"
JS.global[:document].write "Hello, world, from #{RUBY_VERSION}!"
</script>
</html>
Of course, not every Ruby code can be executed within a browser (or another Wasm runtime). Remember WASI? The current stable version of ruby.wasm supports only Preview 1, so no sockets, HTTP, and other cool stuff. Threads are also yet to come to Wasm (they only reached [Phase 1](https://was
Meet ruby.wasm
268 words
Move The whys and nots of Rails Wasm-ification
Open The whys and nots of Rails Wasm-ification
The whys and nots of Rails Wasm-ification
Before we start digging deeper into technical details of running Rails on Wasm, let's talk for a moment why, beyond the joy of problem solving, you might want to do that.
The cases of client-side Rails
There are many popular Rails applications out there in the wild (check, for example, Using Rails). They all embrace the client-server paradigm with the server responsible for processing requests from different clients (sometimes, tons of clients) and communicating with other systems and services. You cannot do all of that if you manage to launch a Rails server within your browser.
However, there are some scenarios when it could be beneficial.
Let's start with the obvious one—this application.
You can turn your network connection off and continue reading the book, even though it's backed by the Rails application. Try right now!*
* Might now work in some browsers, sorry :( Try visiting the <a href=
The whys and nots of Rails Wasm-ification
658 words
Move The hows of Rails Wasm-ification, or wasmify-rails
Open The hows of Rails Wasm-ification, or wasmify-rails
The hows of Rails Wasm-ification
Compiling a full-featured Rails application into a Wasm module can be challenging. Why? Because a typical Rails application depends on much more than just a Ruby VM. Just take a quick look:
What do we have on our plate?
- We heavily depend on native extensions, some of which require specific system libraries (hello, Nokogiri!)
- A Rails app is hardly imaginable without a database
- Oh, we need an actual web server to serve HTTP requests!
- ...Not only plain text requests, but file uploads (and don't forget about image transformations)
- What about queues for our background jobs?
- Should mention cables (real-time features) or it's enough?
You may think that solving all these problems doesn't worth the effort. Let's just run Rails on servers. But don't give up too quickly! We have something that could help you get started with Rails on Wasm with as little burden as possible.
The hows of Rails Wasm-ification, or wasmify-rails
159 words
Move bin/rails wasmify
Open bin/rails wasmify
Introducing wasmify-rails
Most of the steps required to make a Rails application Wasm-compatible are the same for all Rails applications (at least those we worked with). That means, there should be a way to automate or at least simplify the process. And here comes wasmify-rails.
Wasmify Rails is a collection of tools and extensions to speed up the process of compiling a Rails application into a Wasm module as well as the starter project to run Rails in the browser.
Read the project's Readme for the full instructions set. Let me recall the basics here:
Install the wasmify-rails
gem
Run the bin/rails wasmify:install
command to preconfigure your applicaiton add the wasm
environment
Run the bin/rails wasmify:pack
command to compile the app into a Wasm module (it's likely gonna fail from the first time, you need to tweak your configuration and iterate)
(Optionally) Genera
bin/rails wasmify
220 words
Move Bundler on Wasm
Open Bundler on Wasm
Bundler on Wasm
Building Wasm modules is quite to creating Docker images: you start with the foundation, Ruby itself, then you add your dependencies via the bundle install
command.
In ruby.wasm, we have the rbwasm build
command that performs a similar task: generates a Wasm module with Ruby and all the specified dependencies from the Gemfile.
The command pulls the Ruby (MRI) source code, all the dependencies, and tries to compile and link them together into a single module. Why "tries"? Many dependencies rely on native (usually, C) extensions. Good news is that ruby.wasm can compile C extensions into WebAssembly. Not-so-good news is that not every extension is Wasm-compatible yet (e.g, nokogiri
).
Sometimes non-compilable extensions are optional transitive dependencies of the libraries you need (e.g., actioncable
depends on nio4r
, and rails
depends on actioncable
). To deal with this situation, wasmify-rails
provides a custom task, wasmify:build
, which wraps rbwasm build
an
Bundler on Wasm
364 words
Move Shims and stubs
Open Shims and stubs
Shims and stubs
When porting a Ruby application to Wasm we deal with the following limitations with regards to libraries:
- Not every Ruby VM feature is supported by Wasm (yet); for example, Threads, sockets.
- Not every C (or Rust) extension could be compiled into Wasm.
These limitations shouldn't stop us from wasmifying our applications. Luckly, the Ruby openness makes it possible to overcome them.
Ignoring gems with uncompilable native extensions
Wasmify Rails allows you to configure the list of gems to exclude from the final Wasm build:
# config/wasmify.yml
# ...
exclude_gems:
- nio4r
- io-console
- psych
When your wasmify:build
command fails, it usually means that there is incompatible extension present. Just add it to the list and try again.
Providing shims and stubs
Wasmify Rails includes some common shims and stubs by default. For example, Thread.new { ... }
, Socket, and other APIs are available.
To provide custom shims and stubs, yo
Shims and stubs
291 words
Move Active Record on Wasm
Active Record on Wasm
We can use Active Record to connect to a database running in the browser. For that, we implement a custom database adapter at the Rails side and an external interface object in the JavaScript runtime.
Wasmify Rails provides two adapters for in-Wasm database: sqlite3_wasm
and pglite
.
Using with sqlite3-wasm
Sqlite3 ships with an official Wasm port that allows you to work with a Sqlite3 database right in your browser. To connect it to your Rails on Wasm application, you must to the following:
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
import {
registerSQLiteWasmInterface,
initRailsVM
} from "wasmify-rails";
// Create a database
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB("/railsdb.sqlite3", "ct");
// Make the database accessible from th
Active Record on Wasm
Coming soon
Move Rack on Wasm
Rack on Wasm
TBD (data URIs, http "servers")
Rack on Wasm
Coming soon
Move Assets on Wasm
Assets on Wasm
TBD (precompilation, caches, OPFS)
Assets on Wasm
Coming soon
Move Untitled
Active Storage on Wasm
TDB (null processing, database backend, OPFS backend)
Untitled
Coming soon
Move HTTP APIs on Wasm
HTTP APIs on Wasm
TDB (faraday js, sniffer)
HTTP APIs on Wasm
Coming soon
Move Active Job on Wasm
Active Job on Wasm
TBD (workers, broadcast channel)
Active Job on Wasm
Coming soon
Move Untitled
Open Untitled
Action Cable on Wasm
Let's think about how we can implement a real-time communication functionality for the app running locally, without any real-time server involved. What the use of live updates in such an isolated environment? Apart from the case of running multiple copies of the application (e.g., browser tabs), there is a more significant scenario of performing some work in the background. In other words, signaling the client (JavaScript) about the operations completed outside of the request-response loop is still important.
Luckly, we can leverage Action Cable for that even under such unusual circumstances.
NOTE: All approaches described in this chapter have two requirements: 1) next-gen Action Cable architecture (see actioncable-next) to support non-WebSocket clients; 2) usage of AnyCable JS SDK instead of the Rails one because of the ability to implement cus
Untitled
723 words
Move Code examples
Code examples
Most of these code examples are extracted from this application, Writebook on Wasm, which is open-sourced, so we cannot just publish it as is.
Rails PWA integration
TBD
Database persistence
TDB
Storing Wasm modules in the browser
Code examples
Coming soon