Opal: Running Ruby in the JavaScript Runtime (Without Losing Your Mind)
Opal: Running Ruby in the JavaScript Runtime (Without Losing Your Mind)

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:

  • Compiler (lib/) → Parses Ruby and generates JavaScript
  • Runtime (opal/) → Implements Ruby semantics in JS
  • Stdlib (stdlib/) → Partial Ruby standard library

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:

  • method dispatch (obj.$method)
  • class system emulation
  • lack of native Ruby threading
  • async execution constraints

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:

  • validations
  • serializers
  • business rules

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:

  • browser environments
  • Node.js
  • edge runtimes (e.g., workers)

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:

  • blocking I/O
  • request/response lifecycle
  • server environment

None of these exist in a browser or edge runtime.

So running Sinatra on Opal requires:

  • patching
  • shimming
  • or rethinking the entire request model


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:

  • how async flows are exposed to Ruby
  • whether to simulate sync behavior or embrace async semantics


Stdlib and Gem Compatibility

Opal ships a partial stdlib.

Anything relying on:

  • file system access
  • native extensions
  • threads

will not work.

This means:

  • not all gems are compatible
  • many need adaptation or replacement


Real-World Pattern: Sinatra + Opal

A growing pattern in advanced setups is:

  • vendoring Sinatra
  • patching it for Opal compatibility
  • compiling it into JavaScript
  • running it on a custom runtime

This effectively creates:

A Sinatra-like DSL executing inside a JavaScript environment.

However, this approach comes with trade-offs:

  • tight coupling to Sinatra internals
  • fragile upgrades
  • need for a custom runtime (event loop, request handling, etc.)

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:

  • Ruby on the frontend
  • shared Ruby logic across environments
  • experimental runtimes (edge, workers, client-side apps)
  • full control over a Ruby-in-JS execution model

Avoid it if you need:

  • full gem ecosystem compatibility
  • traditional Rack-based applications
  • predictable performance across all environments


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.

Article content


To view or add a comment, sign in

More articles by Germán Alberto Giménez Silva

Explore content categories