jsg: a small helper for ruby.wasm projects
ruby.wasm runs CRuby in the browser, and the js gem is the bridge between your Ruby code and the JavaScript world. Getting a project off the ground still involves a few manual steps: an HTML page, the browser script, a Gemfile, a build command and a local web server.
In 2024 I wrapped those steps into a small gem called jsg. Its gemspec sums it up in one line: “JSG helps setting up ruby.wasm projects and comes with a nicer syntax”. So there are two halves: a command line tool that sets up and builds a project, and a Ruby file that you load in the browser.
A fair warning first: jsg is an early experiment. The current version is 0.1.2 from May 2024, the README on GitHub is still the Bundler placeholder, and there is no real test suite yet. This post is the closest thing to documentation it has.
Installation
jsg needs Ruby 3.0 or newer and depends on the js gem (~> 2.6) and erb:
gem install jsg
The command line tool
The gem ships a jsg executable with three commands:
jsg new myapp # create a project from the template, then build it
jsg build # bundle install, then build assets/ruby-app.wasm
jsg server # serve the current directory with WEBrick
jsg new myapp copies the template into a new myapp folder. You get:
index.html, rendered from an ERB template with your project name as title and headingsrc/main.rb, the place for your own codeassets/browser.script.iife.js, ruby.wasm’s browser script, set up to loadassets/ruby-app.wasm- a
Gemfilewithruby_wasm,jsandjsg - a
.gitignorethat ignores thebuild/andrubies/folders
Right after copying, it changes into the folder and runs the build. jsg build does exactly two things: bundle install and rbwasm build -o assets/ruby-app.wasm. The comment in the template’s src/main.rb explains the idea: your code goes there and ends up inside the .wasm file after jsg build.
jsg server (or jsg serve) starts WEBrick on port 8080 and moves on to the next port if 8080 is already taken. The examples folder uses Ruby’s built-in one-liner instead, which works just as well:
ruby -run -e httpd . -p 8080
Both rely on WEBrick, which is no longer part of Ruby’s standard library since Ruby 3.0, so you may need a gem install webrick first.
The “nicer syntax”
The second half is jsg.rb. It defines a JSG module with a few shortcuts and adds a set of methods to JS::Object. This is the Ruby that the example page in the repository runs:
require 'jsg'
$window = JSG.global
$d = $window.document
$d.getElementById("spinner").style.display = "none"
$d.createElement("h2").tap {|e|
e.innerText = "Examples"
$d.body.appendChild(e)
}
Instead of JS.global[:document] you write $window.document, and you call DOM methods like you would in JavaScript. Here is what the source adds:
- Shortcuts on
JSG:JSG.window(aliasJSG.w) for the global object,JSG.document(aliasJSG.d) andJSG.querySelectorAll(aliasJSG.q). Anything else, likeJSG.global, is forwarded to theJSmodule. - Conversion to Ruby types: a
to_rbmethod turns JavaScript numbers into Floats, strings into Strings, booleans intotrue/false, bigints into Integers and arrays into Ruby Arrays. Other objects stayJS::Object. - Property access and method calls:
method_missingreads properties and calls functions, converting the result withto_rb. A name ending in=sets a property, and a name ending in?returns a Ruby boolean. - Comparisons:
==compares against Ruby’strue,falseandnil, so you do not have to writeJS::True. There are alsonil?,undefined?andtypeof?(:string). - Arrays:
to_auses JavaScript’sArray.from, so array-like objects such as aNodeListbecome Ruby Arrays, andeachiterates over JavaScript arrays.
The template’s index.html also shows a trick for development: it redefines require_relative to load files over HTTP through JS::RequireRemote. The comment there says this is only needed if you want to use require_relative instead of jsg build.
Status
jsg is small and still rough around the edges. If you try it and something does not work, the source is short: one Ruby file for the browser side and one script in exe/jsg. Issues and pull requests are welcome on GitHub. The license is MIT.
ruby.wasm also shows up in another project of mine: the Hackety Hack port behind clogs has a browser build that runs CRuby compiled to WebAssembly.