Rails and JavaScript Part 2
UJS, CoffeeScript and Sprockets, oh my.
Previously on Locally Sourced: I wrote about the early years of Rails and JavaScript. Which made it to Ruby Weekly. Also, my Rails and JavaScript book is still on sale.
A quick program note: If you’ve liked the Entropy Essays, I’m doing a virtual Chicago Ruby Meetup on July 7th at 6:00 Central Daylight Time. It’s being streamed via zoom, and you can sign up here. I’ll be tying all those essays together and talking about how they relate. Please do come, it’ll be nice to have you.
And now, on with the show. Please use the inline buttons to share, comment, or subscribe.
When I was in grad school, I worked with a professor who studied what was then called “ubiquitous computing” – what we could do when everybody had instant access to a computing device all the time. We all carried around Apple Newtons, and his seemed amazingly futuristic in 1997. Now we just call it “life”.
Anyway, around Rails 3.0 time, “unobtrusive JavaScript” became a buzzword. The term that had been around for a while, but it only started to affect how Rails programmers worked when tools like jQuery then later Angular and Ember became powerful. “Unobtrusive” meant a couple of things, but for our purposes the most important was that it called for separating JavaScript from the markup. In practice, this meant not including any JavaScript directly in the HTML files and the onclick
handlers of elements, but instead putting JavaScript in external files and using jQuery or something similar to identify elements and attach event listeners to them. This all seemed amazingly futuristic in 2010, but now we just call it “life”.
Rails 3.0 was a big release that changed the internals of Rails significantly. The release also featured the Rails adaptation to the unobtrusive JavaScript style by introducing the rails_ujs
library. Rails UJS replaced the link_to_remote
and remote_form_for
helpers with a remote: true attribute that the helper rendered as a data-remote
HTML attribute and the Rails UJS code picked up on. It also provided for Rails confirmation dialogs, and Rails-specific Ajax events that you could hook into. Rails UJS allowed for the use of the existing Rails JavaScript interaction patterns – still largely meant to be receiving HTML – within an unobtrusive setup.
Although Rails RJS didn’t provide much of a functionality difference from the earlier handler, the move to UJS was a big deal. Rails, kind of for the first time, was adapting to changes in the JavaScript ecosystem. The change added support for popular libraries like jQuery, and allowed Rails developers to more easily adopt what was quickly becoming best JavaScript practices.
To some extent that becomes the long-term pattern. While the Rails team continues to evolve their particular way of doing client-side interaction, Rails itself continues to adapt to allow other patterns to be usable.
Rails 3.1: CoffeeScript and Sprockets
UJS quickly evolved into single page apps, with Angular and Ember becoming the first JavaScript SPA frameworks to get significant mindshare in the Rails Community, and we started to see the Rails community having debates between single page apps and what DHH would eventually call “sprinkles of JavaScript”. The Rails way for dealing with sprinkles of JavaScript in contrast to SPAs came out in two steps.
The first step came in Rails 3.1, which added CoffeeScript and the Asset Pipeline. Which was a lot for a point release.
CoffeeScript is a language that compiles to JavaScript and has a syntax that is kind of a greatest-hits combination of Ruby and Python – “everything is an expression” from Ruby, significant whitespace and list comprehensions from Python. If I’m remembering correctly, the pull request that added CoffeeScript as a default to the Rails gemfile was among the most commented-on in Rails history. At the time, CoffeeScript was both aesthetically pleasing to the Rails team because the syntax was more Ruby-like, and functionally pleasing, because CoffeeScript had features that JavaScript didn’t yet have, most notably actual classes.
It probably doesn’t surprise you that I was a huge, unabashed CoffeeScript fan. I’m looking at the CoffeeScript docs page as I write this, and I still think the syntax looks better than JavaScript. But eventually JavaScript ES2015 added classes and arrow functions and enough of CoffeeScript’s features to make the compilation step less appealing. (But I’m looking at this now, and I bet Stimulus controllers would look great in CoffeeScript).
A side effect of the new JavaScript SPA framework patterns was that you now had many more JavaScript and CSS files to manage, and packaging them into something that a browser could handle became its own separate task. The asset pipeline, or Sprockets gem, was meant to be the Rails way of packaging files and serving them to the browser. Relative to the JavaScript ecosystem tools, Sprockets was more focused and less complicated and had a simpler mechanism for specifying file relationships. Sprockets also allowed JavaScript and CSS libraries to be packaged as Ruby gems, which was really useful, especially back when NPM was very loose with version management.
Sprockets is still in pretty high use, especially for CSS, which is complicated in Webpacker. But it didn’t really keep up with the JavaScript tooling (in particular, it had a hard time interacting with Babel), and development also stalled for a while.
Rails 4.0: Turbolinks and Russian-Doll Caching
Rails 4.0 added Turbolinks and Russian-Doll Caching – the caching is a server-side feature but it’s a big part of what is supposed to make Turbolinks work.
Turbolinks is very high on the list of Rails features that a lot of teams turn off, largely because it doesn’t always play very well with existing JavaScript code, and also because, while it’s easy to set up, it’s a little tricky to get it working well enough to see the full benefit.
Turbolinks was partially inspired by a tool called pjax, which was similar to link_to_remote
in that it automated the process by which you could get HTML from a server and overwrite part of the page. Turbolinks generalizes this and makes the default behavior of all links to go to the server, strip out the body part of the page response, and replace the body in the DOM with the new HTML. This allows you to effectively refresh part of the page without visible flicker, so it enables frequent refresh of partial pages with new server-side HTML. That’s the goal – to make it very simple to get the perceived-performance of a single page app changing the DOM client side while still keeping the basic structure of a server-side application.
But. You still have to make the server side call, and make it very fast, which is where the caching came in. The Russian-Doll caching setup is a clever way to have most of a page be cached except for one particular part, so if you were showing a calendar page, you could break the cache for only one event, and have the rest of the page be cached and the rendering much faster.
My experience is that the caching works really well in the view template, but getting the controller to limit the database calls appropriately so as not to waste database calls is a pain. And I think a lot of apps don’t lend themselves to the Russian-doll structure. You also need to set up routing so that the Turbolinks calls can go to URLs that replicate the client-side state which can also be tricky.
Sidebar: I think a lot of Rails teams could probably stand to cache more aggressively, but caching a) seems like a lot of work in the early days of an app, b) is something whose value is very hard to see in development mode, and c) can lead to a lot of weird and dangerous bugs if you aren’t careful.
I also think there was kind of a “why are they even trying to deny the awesomeness of single page apps” reaction. Especially early on, there was a lot of tech triumphalism around some of the single page app frameworks, and so why would you yell at the the incoming tide?
For a lot of teams, Turbolinks caused an immediate headache dealing with existing JavaScript, and without re-tooling the server side to take advantage of the caching there just didn’t seem to be much point, so they shut it off. Keeping the state client-side, even single page apps seemed much less complex. But watch this space, because the as-yet-unreleased Turbolinks seems to some of the developer usability problems.
Next up: Webpacker, Stimulus, and whatever the heck Hey.com runs on.
Comment prompts: Is there a CoffeeScript feature you really miss? Why do you turn Turbolinks off?
And, you can subscribe, which will eventually give you access to some longer subscriber-only posts.