Opal: Running Ruby in the JavaScript Runtime (Without Losing Your Mind)
Ruby has always been a server-first language. But what if you could take Ruby beyond MRI and run it directly in the browser, or even on the edge?
That’s exactly what Opal enables.
Opal is not just a transpiler. It’s a full Ruby execution environment implemented in JavaScript, capable of compiling Ruby code into JS and running it with a custom runtime that emulates Ruby’s object model, core classes, and method dispatch.
This opens the door to a very different way of thinking about Ruby applications.
How Opal Actually Works
At a glance, Opal looks simple:
opal --compile app.rb > app.js
But under the hood, it’s doing something much more ambitious.
Opal consists of three key layers:
When you compile Ruby with Opal, you’re not just translating syntax you’re shipping a Ruby runtime embedded in JavaScript.
That’s why code like this:
puts "Hello world"
works in a browser. The puts call is resolved by Opal’s runtime, not native JavaScript.
The Mental Model Shift
If you approach Opal like “Ruby → JS transpilation,” you’ll hit a wall quickly.
The correct model is:
Ruby code runs inside a JavaScript-hosted Ruby VM.
That distinction matters, especially when dealing with:
You’re not escaping JavaScript you’re embedding Ruby inside it.
Where Opal Shines
1. Ruby in the Browser
You can write client-side applications in Ruby:
<script type="text/ruby">
puts "Hello from Ruby in the browser"
</script>
While not recommended for production in that form, it demonstrates the core capability: Ruby as a frontend language.
2. Shared Logic Between Frontend and Backend
Opal allows you to reuse:
across environments.
This is particularly useful when trying to maintain consistency between a Rails backend and a JS frontend.
3. Experimental Runtimes (Edge, Workers, etc.)
This is where things get interesting.
With the right abstractions, Opal can target:
This enables patterns like:
Writing request handlers in Ruby that execute in a JavaScript runtime.
The Hard Parts (You Should Know These)
Opal is powerful but it’s not a drop-in replacement for MRI.
No Native Rack
Frameworks like Sinatra depend on Rack, which assumes:
None of these exist in a browser or edge runtime.
So running Sinatra on Opal requires:
Async vs Sync Mismatch
Ruby code is typically written as synchronous:
get '/' do
data = fetch_data
data.to_json
end
But JavaScript environments are async:
const data = await fetch(...)
Opal does not automatically reconcile this difference.
Any serious system built on Opal must define:
Stdlib and Gem Compatibility
Opal ships a partial stdlib.
Anything relying on:
will not work.
This means:
Real-World Pattern: Sinatra + Opal
A growing pattern in advanced setups is:
This effectively creates:
A Sinatra-like DSL executing inside a JavaScript environment.
However, this approach comes with trade-offs:
In practice, most successful implementations evolve into:
A Sinatra-inspired DSL, not full compatibility.
So, Should You Use Opal?
Opal is not for everyone. But it’s extremely valuable in specific scenarios:
Use it if you want:
Avoid it if you need:
Final Thoughts
Opal challenges one of Ruby’s core assumptions: that it belongs on the server.
By bringing Ruby into the JavaScript runtime, it enables a new class of architectures ones where the language stays the same, but the execution environment changes completely.
It’s not just about running Ruby in the browser.
It’s about redefining where Ruby can live.