jsg : un petit assistant pour les projets ruby.wasm
ruby.wasm fait tourner CRuby dans le navigateur, et le gem js sert de pont entre votre code Ruby et le monde JavaScript. Lancer un projet demande pourtant encore quelques étapes manuelles : une page HTML, le script du navigateur, un Gemfile, une commande de build et un serveur web local.
En 2024, j’ai regroupé ces étapes dans un petit gem appelé jsg. Son gemspec le résume en une ligne : « JSG helps setting up ruby.wasm projects and comes with a nicer syntax ». Il y a donc deux moitiés : un outil en ligne de commande qui met en place et construit un projet, et un fichier Ruby que vous chargez dans le navigateur.
Un avertissement d’abord : jsg est une expérience à un stade précoce. La version actuelle est la 0.1.2 de mai 2024, le README sur GitHub est encore le texte par défaut de Bundler, et il n’existe pas encore de véritable suite de tests. Cet article est ce qui se rapproche le plus d’une documentation.
Installation
jsg nécessite Ruby 3.0 ou plus récent et dépend du gem js (~> 2.6) et de erb :
gem install jsg
L’outil en ligne de commande
Le gem fournit un exécutable jsg avec trois commandes :
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 copie le modèle dans un nouveau dossier myapp. Vous obtenez :
index.html, généré à partir d’un modèle ERB avec le nom de votre projet comme titre et en-têtesrc/main.rb, l’endroit pour votre propre codeassets/browser.script.iife.js, le script navigateur de ruby.wasm, configuré pour chargerassets/ruby-app.wasm- un
Gemfileavecruby_wasm,jsetjsg - un
.gitignorequi ignore les dossiersbuild/etrubies/
Juste après la copie, il se place dans le dossier et lance le build. jsg build fait exactement deux choses : bundle install et rbwasm build -o assets/ruby-app.wasm. Le commentaire dans le src/main.rb du modèle explique l’idée : votre code va là et se retrouve dans le fichier .wasm après jsg build.
jsg server (ou jsg serve) démarre WEBrick sur le port 8080 et passe au port suivant si 8080 est déjà pris. Le dossier d’exemples utilise plutôt le one-liner intégré à Ruby, qui fonctionne tout aussi bien :
ruby -run -e httpd . -p 8080
Les deux reposent sur WEBrick, qui ne fait plus partie de la bibliothèque standard de Ruby depuis Ruby 3.0 ; il faudra donc peut-être d’abord un gem install webrick.
La « syntaxe plus agréable »
La seconde moitié est jsg.rb. Il définit un module JSG avec quelques raccourcis et ajoute une série de méthodes à JS::Object. Voici le Ruby qu’exécute la page d’exemple du dépôt :
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)
}
Au lieu de JS.global[:document], vous écrivez $window.document, et vous appelez les méthodes DOM comme en JavaScript. Voici ce que le code source ajoute :
- Raccourcis sur
JSG:JSG.window(aliasJSG.w) pour l’objet global,JSG.document(aliasJSG.d) etJSG.querySelectorAll(aliasJSG.q). Tout le reste, commeJSG.global, est transmis au moduleJS. - Conversion en types Ruby : une méthode
to_rbtransforme les nombres JavaScript en Floats, les chaînes en Strings, les booléens entrue/false, les bigints en Integers et les tableaux en Arrays Ruby. Les autres objets restent desJS::Object. - Accès aux propriétés et appels de méthodes :
method_missinglit les propriétés et appelle les fonctions, en convertissant le résultat avecto_rb. Un nom se terminant par=définit une propriété, et un nom se terminant par?renvoie un booléen Ruby. - Comparaisons :
==compare avectrue,falseetnilde Ruby, vous n’avez donc pas besoin d’écrireJS::True. Il y a aussinil?,undefined?ettypeof?(:string). - Tableaux :
to_autiliseArray.fromde JavaScript, de sorte que les objets de type tableau comme uneNodeListdeviennent des Arrays Ruby, eteachparcourt les tableaux JavaScript.
L’index.html du modèle montre aussi une astuce pour le développement : il redéfinit require_relative pour charger les fichiers par HTTP via JS::RequireRemote. Le commentaire précise que ce n’est nécessaire que si vous voulez utiliser require_relative au lieu de jsg build.
État
jsg est petit et encore un peu brut. Si vous l’essayez et que quelque chose ne fonctionne pas, le code source est court : un fichier Ruby pour le côté navigateur et un script dans exe/jsg. Issues et pull requests sont les bienvenues sur GitHub. La licence est MIT.
ruby.wasm apparaît aussi dans un autre de mes projets : le portage de Hackety Hack derrière clogs dispose d’un build navigateur qui exécute CRuby compilé en WebAssembly.