Ruby on the Frontend? Choose Your Weapon

We all know that Ruby is a great language to use for the backend of your web application, but did you know you can write Ruby code for the frontend as well?

Jared White by Jared White on December 3, 2020

We all know that Ruby is a great language to use for the backend of your web application, but did you know you can write Ruby code for the frontend as well?

Not only that, but there are two available options to choose from when looking to “transpile” from Ruby to Javascript. These are:

Let’s take a quick peek at each one and see what might be right for your project.

Ruby2JS

My personal favorite, Ruby2JS was created by Sam Ruby (yep, that’s his name), and it is intended to convert Ruby-like syntax to Javascript as cleanly and “natively” as possible. This means that (most of the time) you’ll get a line-by-line, 1:1 correlation between your source code and the JS output. For example:

class MyClass
  def my_method(str)
    ret = "Nice #{str} you got there!"
    ret.upcase()
  end
end

will get converted to:

class MyClass {
  myMethod(str) {
    let ret = `Nice ${str} you got there!`;
    return ret.toUpperCase()
  }
}

There’s actually a lot going on here so let me unpack it for you:

  • Depending on how you configure Ruby2JS, you can convert classes to old-school JS functions/constructors, or you can use modern ES6+ classes like in the example here (which I recommend).
  • Ruby2JS provides “filters” which you can apply selectively to your code to enable new functionality. In this example, the camelCase filter automatically converts typical Ruby snake_case to camelCase as is common in Javascript. The functions filter automatically converts many popular Ruby methods into JS counterparts (so upcase becomes toUpperCase). And the return filter automatically add a return to the end of a method just like how Ruby works.
  • String interpolation in Ruby magically become valid ES6+ string interpolation, and it even works with squiggly heredocs!

How do you get started using Ruby2JS? It’s pretty simple: if you’re using a framework with Webpack support (Rails, Bridgetown), you can add the rb2js-loader plugin along with the ruby2js gem, write some frontend files with a .js.rb extension, and import those right into your JS bundle. It even supports source maps right out of the box so if you have any errors, you can see the original Ruby source code right in your browser’s dev inspector!

Full disclosure: I recently joined the Ruby2JS team and built the Webpack loader, so let me know if you run into any issues and I’ll be glad to help!

Opal

The Opal project was founded by Adam Beynon in 2012 with the ambitious goal of implementing a nearly-full-featured Ruby runtime in Javascript, and since then it has grown to support an amazing number of projects, frameworks, and use cases.

There are plenty of scenarios where you can take pretty sophisticated Ruby code, port it over to Opal as-is, and it just compiles and runs either via Node or in the browser which is pretty impressive.

Because Opal implements a Ruby runtime in Javascript, it adds many additional methods to native JS objects (strings, integers, etc.) using a $ prefix for use within Opal code. Classes are also implemented via primitives defined within Opal’s runtime layer. All this means that the final JS output can sometimes look a little closer to bytecode than traditional JS scripts.

For instance, the above example compiled via Opal would result in:

/* Generated by Opal 1.0.3 */
(function(Opal) {
  var self = Opal.top, $nesting = [], nil = Opal.nil, $$$ = Opal.const_get_qualified, $$ = Opal.const_get_relative, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass;

  Opal.add_stubs(['$upcase']);
  return (function($base, $super, $parent_nesting) {
    var self = $klass($base, $super, 'MyClass');

    var $nesting = [self].concat($parent_nesting), $MyClass_my_method$1;

    return (Opal.def(self, '$my_method', $MyClass_my_method$1 = function $$my_method(str) {
      var self = this, ret = nil;

      
      ret = "" + "Nice " + (str) + " you got there!";
      return ret.$upcase();
    }, $MyClass_my_method$1.$$arity = 1), nil) && 'my_method'
  })($nesting[0], null, $nesting)
})(Opal);

Thankfully, Opal too has support for source maps so you rarely need to look at anything like the above in day-to-day development—instead, your errors and debug output will reference clean Ruby source code in the dev inspector.

One of the more well-known frameworks using Opal is Hyperstack. Built on top of both Opal and React, Hyperstack lets you write “isomorphic” code that can run on both the server and the client, and you can reason about your web app using a well-defined component architecture and Ruby DSL.

Conclusion

As you look at the requirements for your project, you can decide whether Ruby2JS or Opal might suit your needs.

  • If you use Webpack and already have a lot of JS code or libraries you need to interoperate with, Ruby2JS is a capable and lightweight solution which integrates easily into your build pipeline.
  • If you’re starting from scratch and want all the power of a full Ruby runtime as well as opportunities to write isomorphic Ruby code, Opal might be just what the doctor ordered.

Regardless of which you choose, it’s exciting to know that we can apply our Ruby knowledge to the frontend as well as the backend for web applications large and small. It’s a great day to be a Rubyist.

“Ruby is simple in appearance, but is very complex inside, just like our human body.”

matz

Subscribe to receive a timely tip you can apply directly to your Ruby site or application each week:

Banner image by Meritt Thomas on Unsplash


Other Recent Articles

Teaching Ruby to Beginners? Trying New Gems or Techniques? Use Bridgetown!

The next big release of Bridgetown provides an intriguing new environment for teaching and learning Ruby and trying out new tools in the Ruby ecosystem.

Continue Reading

Better OOP Through Lazily-Instantiated Memoized Dependencies

There are various schools of thought around how best to define dependencies in your object graph. Let’s learn about the one I prefer to use the majority of the time. It takes advantage of three techniques Ruby provides for us: variable-like method calls, lazy instantiation, and memoization.

Continue Reading

More This Way