Electron Adventures: Episode 93: Opal Ruby

I tried it a few times before, and it was a bit too complicated, so I dropped it and did something else, but the series is nearing completion, so I can't postpone any longer.

Opal Ruby is complicated primarily because it's not meant to be used as JavaScript-like language, it's meant to be used as part of a bigger system, generally with a Ruby backend, so all that wiring is behind the scenes. But we'll use it directly.

For this episode, I'll only do the frontend (renderer) in Opal Ruby, and the backend (main) in JavaScript.


The backend process will just open public/index.html:

let { app, BrowserWindow } = require("electron")

function createWindow() {
  let win = new BrowserWindow({height: 600, width: 800})

app.on("ready", createWindow)

app.on("window-all-closed", () => {


For the app we'll just show a button, and a count of how many times it was clicked:

<!DOCTYPE html>
    <meta charset="utf-8">
    <title>Ruby Opal Application</title>
    <link href="app.css" rel="stylesheet" type="text/css" />
    <button>Click the button</button>
    <div>Click count: <span id="count">0</span></div>
    <script src="./build/app.js"></script>

The compiled file will go to ./build/app.js.


We're doing basically mode: dark; display: center; except with a few more words:

body {
  margin: 0;
  background-color: #444;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: 300%;
  min-height: 100vh;

button {
  font-size: unset;


That covers the Electron part, so let's get to the Ruby part. Gemfile is like package.json - and there's Gemfile.lock corresponding to package-lock.json

gem "opal", "~> 1.3"


We need to tell Opal to build the app.js. There are likely some packages for watching the source folder and doing it automatically, but I decided to do it the hard way for now.

All this could also go into package.json scripts section.

require "pathname"

desc "Build JavaScript files"
task "build" do
  sh "opal src/app.rb -c >public/build/app.js"


And finally the app! Opal Ruby generally requires wrappers around JavaScript objects. There are a few ways to do this - native module provides $$ which corresponds to JavaScript window/global except it wraps every JavaScript object in an Opal Ruby wrapper:

require "native"

counter = 0

button = $$.document.querySelector("button")
count = $$.document.querySelector("#count")

button.addEventListener("click") do
  counter += 1
  count.innerText = counter


Here's the results:

In the next episode we'll write the terminal app in Opal Ruby.