Replacing Ruby on Rails

Replacing Ruby on Rails

This last week I've done very little productive work. I had some issues around our database (too background tasks running at once, flooding the database with queries, then locking the tables so users couldn't get the data they needed). But the worst bit was, instead of writing useful code, I've spent hours fighting against yarn and npm in an attempt to get all the Javascript and CSS working correctly.

The main Collabor8Online app is Rails 6.1 and uses Webpacker - this uses webpack and yarn under the hood to manage npm packages and provide a build pipeline for my javascript controllers, CSS and other assets. It seemed to be working OK, but something along the way upset things and now I get weirdness in the CSS (I think stuff is getting loaded in a different order or a new package is overwriting my customisations) and rails assets:precompile might work in Docker on my machine, but fail in Docker on the staging server.

I really don't like the Rails asset pipeline and Rails itself is huge and unwieldy - as well as becoming generally unsuitable for the type of apps we build nowadays.

So I've been searching for an alternative.

Unfortunately, most of the ruby web-server frameworks do not impress me. They seem dry, technical and spark no joy as I'm playing around with them (Camping and Sinatra being notable exceptions - but they are both tiny).

However, I may have found the solution.

On the back-end, I've rediscovered Grape. I used this many years ago and remember really enjoying it1. Grape is a tiny framework focused purely on building HTTP APIs - it does a load of parameter validation up-front, and even better, it generates documentation, in Swagger format, for the consumers of that API.

And on the front-end, I was playing with Bridgetown. Bridgetown grew out of Jekyll - a static site generator. You define a layout and write your blog posts in Markdown, then tell Jekyll to build your site. It then generates all the HTML and you can rsync the lot up to your server. Fast and secure.

But Bridgetown has become a lot more than that. Firstly, it uses esbuild (which I know nothing about) and yarn internally to handle your assets. It uses Roda internally so your generated site can also include dynamically generated pages2. But, it also comes with a number of predefined configurations that you can install, which add to the build process.

One of these configurations is Ruby2JS, which I hadn't heard of before.

I have played with Opal.rb and really like it. Opal is a ruby interpreter that recreates the standard ruby environment but makes it runnable from Javascript. You can take your .rb files and choose to run them in normal MRI ruby, or in JRuby (on the JVM) or TruffleRuby3 and in theory you get the same behaviour but different performance, threading and other capabilities. Opal lets you run those same .rb files in a Javascript environment - either dynamically compiling them (via a script type="ruby" tag) or precompiling them to a .js file. But Opal is complicated and I struggled to understand how it's native browser integration works. But Ruby2JS is much smaller in scope - it basically translates ruby into javascript, with a one-to-one mapping between your source code and the output javascript4. And even better, Ruby2JS includes a "preset" for Lit components.

Lit is a javascript library that makes writing "custom elements" (web components) really easy, with support for the Shadow DOM (so your CSS is self-contained and doesn't leak across components), reactive properties (so changes automatically trigger updates). Plus those updates are queued, so a raft of changes only triggers a single redraw without a ton of flicker on-screen.

I've already been using Lit indirectly, via Corey LaViska's excellent Shoelace library, and while looking into how it was built, I was impressed with how clean and lightweight Lit is. The only trouble being I really don't like Javascript, which Ruby2JS now cures.

So it looks like I've got all the pieces I need to replace a Rails stack. Bridgetown to generate my front-end, Ruby2JS and Lit to define what gets shown and handle user interactions, and Grape to actually protect and serve the data that my application needs.

The next step is to try it out - which I'll be writing about next.


1 Although, at the time, I dropped it because I was building a large Rails app and having the two different styles in the same system felt odd.

2 Of course, this means you're no longer just generating HTML, so you need to run the Roda process in ruby on your server

3 A high performance ruby interpreter that I know nothing about, and as it's Oracle, I've kind of avoided

4 This has some disadvantages. You're not writing ruby, you're actually writing a weird ruby/javascript hybrid which causes problems if you forget to add () to a method call or include super in an inherited method.

Rahoul Baruah

Rahoul Baruah

Rubyist since 1.8.6. Freelancer since 2007, dedicated to building incredible, low-cost, bespoke software for tiny businesses. Also CTO at Collabor8Online.
Leeds, England