Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 23, 2021 12:49 pm GMT

Using Hotwire Turbo in Rails with legacy JavaScript

When Hotwire Turbo got released around Christmas 2020, it was exciting news for many of us. One of its main appeals is that it helps you create highly reactive web pages in Rails while having to write almost no custom JavaScript. Turbo also seems very easy to use, it just invites you to try and play with your pages. Lets take a look if Turbo can be used in a long-developed project with a lot of old JavaScript code, too (spoiler: with a little tweak, it very much can!).

The road to legacy JavaScript in a long-time Rails project

After all the years that we watched the JavaScript community boost its ecosystem to tremendous heights and after trying (and often failing) to keep up with the pace of language enhancements, new frameworks and build systems, this intended simplicity of Turbo is a very welcome turnaround. To be clear, we do like JavaScript, its a fine language, especially since ES6, but in our opinion its strengths stand out and are sustainable only if you have enough sufficiently specialized JavaScript devs in a team. In other words, for a small Rails team, long-term management of complex JavaScript can be very difficult.

Thats why we have always been cautious about bringing too much JavaScript to the project, especially for things that could be done in other ways. Still, there's always been a kingdom where JavaScript absolutely ruled and that was page reactivity. Most people love reactive pages and we do, too! So, in the end, still a lot of JavaScript managed to get into our codebase.

Over the years, the official support and default conventions for building reactive JavaScript-enabled pages in Rails took many different forms. Lets just run through some of the options for working with JavaScript that we had in our pretty much standard Rails project during the course of its existence, i.e. during the last ~12 years:

  • there was the old and rusty inline vanilla JavaScript since forever,
  • there was the Prototype library since who knows when but it was phased out gradually (~2010),
  • and in Rails 3.1, it was replaced by jQuery (~2011),
  • Rails 3.1 also brought CoffeeScript as a new and encouraged way of writing JavaScript (~2011),
  • there was Unobtrusive JavaScript to replace the inline style; it was pushed further by the jquery-ujs library (~2010), later superseded by the somewhat compatible Rails UJS (2016),
  • there were Server-generated JavaScript Responses (SJR) allowing the server to update pages via JavaScript (~2011),
  • since Rails 4, the Turbolinks library has been included but had a bunch of problems at that time (2013), so
  • Rails 5 came with a major and largely incompatible rewrite of Turbolinks (Turbolinks 5), the previous versions of which were renamed to Turbolinks Classic (2016),
  • Rails 5.1 optionally adopted the webpack bundler and the yarn package manager (2017), the two became the preferred way of handling JavaScript in Rails,
  • Rails 5.1 also removed jQuery from default dependencies (2017)
  • the Stimulus JS framework was released (2018),
  • CoffeeScript, although still soft-supported via a gem, is discouraged in favor of vanilla ES6 JavaScript or Typescript compiled via webpack (~2018),
  • after being in beta for 3 years, Sprockets 4 was released, with support for ES6 and source maps in the asset pipeline (2019), to serve people still hesitant with webpack,
  • and finally Turbo which should become a part of Rails 7 (late 2020),
  • oh and by the way, DHH nowadays explores native ES6 modules which could allow ditching webpacker and returning to Sprockets for handling JavaScript again.

What a ride! In retrospect, to us it really looks as if DHH and others struggled hard to make the JavaScript ecosystem and its goodies available in Rails but not until they were able to come up with a sufficiently elegant way to do that (and if so, thanks for that ). Each iteration made sense and each newly adopted technique was a step forward but still, the overall churn of JavaScript styles has been tremendous. While, in our experience, upgrading Rails itself got easier with each version, the same cannot be said about our JavaScript code. JavaScript in Rails from only a few years ago is quite different from how it looks today.

Turbo changes everything

And here comes Hotwire Turbo to change the situation again but this time with truly good promises. The reasoning for high hopes is simple: Turbo lets you create many of the reactive page patterns without having to write a single line of JavaScript. JavaScript is now pushed behind the scenes and the main focus, even for describing reactive behavior, is on HTML which is easy to author via Rails templates (or anything else). Custom JavaScript code, now preferably written as Stimulus JS controllers, becomes just an icing on the cake if you need some more special interactions with a page.

The new Basecamps flagship the HEY.com service currently uses a total of ~60kB of JavaScript (zipped) while, in terms of reactivity, it feels like a real SPA. In contrast, our web uses twice as much JavaScript while mostly being an ordinary click-and-wait-for-the-whole-page web, oh well

So again, with Turbo, the problem with JavaScript code patterns becoming obsolete is effectively gone because in the future there will simply be no custom JavaScript code to upgrade!

If it all looks that great, why were we hesitant so far about just adding the turbo-rails gem and hitting the shiny new road? Before we actually tried to dive in, we had the following big concern: will Turbo work with Turbo Drive disabled? Turbo Drive, the successor of Turbolinks, is a member of the Turbo family. This library is cool but requires the JavaScript code to be structured in a certain way which is often quite hard to achieve in an older project with a lot of legacy JavaScript. We havent really tried to bite the refactoring bullet yet, although were getting near. Until then, we need to be sure that our web will work OK without Turbo Drive.

And we are happy to find out that the brief answer to this question is a big bold YES! Read on if youd like to know more.

Installing Turbo

We wont go into much detail here, the official procedure just worked for us. If youre still using the Asset Pipeline for your JavaScript files, make sure it supports ES6 syntax (i.e., youll need to upgrade to Sprockets 4). You also need a recent-enough Rails version (Rails 6, it seems). Otherwise, all should be good.

One small catch though: if you have both the Asset Pipeline and webpack enabled (as we do) and if you only want Turbo to be included in the webpack-managed bundles, youll notice that turbo.js gets precompiled also in the Asset Pipeline if you use the turbo-rails gem. It turns out that the gem automatically adds this file into the pipeline upon initialization. To prevent this (and save a bit of hassle with enabling ES6 in Sprockets), you can remove it again during the start of your Rails app:

# config/application.rbclass Application < Rails::Application  ...  # remove Turbo from Asset Pipeline precompilation  config.after_initialize do    config.assets.precompile.delete("turbo")  endend

Disabling Turbo by default

If you try browsing your site now, after some time youll likely notice various glitches and unexpected behavior thats Turbo Drive (Turbolinks) kicking our legacy JavaScript butt. What we need to do now is disable Turbo by default and enable it selectively only in places where well use Turbo Frames or Streams.

Well do the disabling part in a little conditional way that will help us when we try to make our JavaScript code Turbo Drive-ready later. To disable Turbo completely in all pages in Rails, you can put the following instructions in your layout files:

<%# app/views/layouts/application.html.erb %><html>  <head>    <% unless @turbo %>      <meta name="turbo-visit-control" content="reload" />      <meta name="turbo-cache-control" content="no-cache" />    <% end %>    ...  </head>  <body data-turbo="<%= @turbo.present? %>">    ...  </body></html>

The instructions here are all controlled by the @turbo variable. If you do nothing else, this variable will be equal to nil and will render the page with Turbo disabled. If, some bright day later, you manage to get your JavaScript to a better shape on a group of pages, you can selectively switch on Turbo (and thus Turbo Drive) for them using @turbo = true in the corresponding controllers. We are about to explore this migration path ourselves soon.

In particular, what the instructions mean is this:

  • The most important one is the data-turbo="false" attribute in the <body> tag. It tells Turbo to ignore all links and forms on the page and leave them for standard processing by the browser. When Turbo decides whether it should handle a link click or form submit, it searches the target element and all its parents for the data-turbo attribute and if it finds a "false" value, it just backs off. This tree traversal is a great feature that will later allow us to selectively switch Turbo on, see below.

  • The other two meta tags are not strictly necessary, they serve as a kind of backup in case Turbo control leaks in somewhere unexpectedly. The turbo-visit-control meta tag forces Turbo to make a full page reload if it encounters an AJAX response (initiated outside of a Turbo Frame). Finally, the turbo-cache-control meta tag ensures that the page will never be stored in Turbos cache.

OK, so when you browse your site now, it should behave exactly the same as youre used to.

Using Turbo Frames

Turbo Frames act like self-replaceable blocks on a page: they capture link clicks and form submits, issue an AJAX request to the server and replace themselves with the same-named Turbo Frame extracted from the response.

As we have Turbo globally disabled, we need to selectively enable it for each Turbo Frame, again using a data-turbo attribute, for example:

<%# app/views/comments/show.html.erb %><%= turbo_frame_tag @comment, data: { turbo: true } do %>  <h2><%= @comment.title %></h2>  <p><%= @comment.content %></p>  <%= link_to "Edit", edit_comment_path(@comment) %><% end %>...<%= link_to "Homepage", root_path %>

Setting the data-turbo attribute to "true" will make Turbo process all links and forms inside the Turbo Frame block, while still ignoring them anywhere outside the frame. So, in our example above, the "Edit" link will be handled by Turbo (and clicking on it will render an inline edit form), whereas the "Homepage" link will still be processed normally by the browser.

Using Turbo Streams responses

Turbo Streams allow the back-end to explicitly declare changes to be made on the client. Whenever the response from the server contains one or more <turbo-stream> elements, Turbo automatically executes the actions within them, updating the given fragments of the page.

Similarly to Turbo Frames, links or forms that expect a Turbo Stream response must be rendered in a Turbo-enabled context, so again the only change needed to make Streams work is setting the data-turbo attribute:

<%# app/views/comments/show.html.erb %><div id="<%= dom_id(@comment) %>" data-turbo="true">  <%= @comment.content %>  <%= button_to "Approve", approve_comment_path(@comment) %></div>

If the server responds with a Turbo Stream response, e.g. via a respond_to block, Turbo will execute the page update commands, as in this somewhat ugly example:

# app/controllers/comments_controller.rbdef approve  ...  @comment.approve!  respond_to do |format|    format.turbo_stream do      render turbo_stream: turbo_stream.prepend(dom_id(@comment),                                                "<p>approved!<p>")    end  endend

Clicking on the "Approve" link will trigger Turbo (because it is enabled in that context), Turbo will make an AJAX request to the server, the server will respond with a <turbo-stream> element containing a "prepend" action with the target of the given comment. Turbo will intercept this response and execute the action, effectively prepending the "approved!" text inside the comment div.

This is all just normal Turbo Streams handling, all we had to do above that is enable Turbo for the particular page fragment.

Using Turbo Streams broadcasting

Turbo Streams dont even need to respond to user interactions, they can also be used for broadcasting page updates asynchronously from the back-end.

And, you know what? It just works, you dont need to do anything special here. For a simple example, add a broadcast command to your model:

# app/models/comment.rbclass Comment < ApplicationRecord  ...  after_create_commit { broadcast_prepend_to "comments" }end

and structure your index template accordingly and a newly created comment will be automatically prepended to a list of comments on the index page:

<%# app/views/comments/index.html.erb %><%= turbo_stream_from "comments" %><div id="comments">  <%= render @comments %></div>

How cool is that?

Mind the collision with Rails UJS

If you used to render links with non-GET methods or AJAXified links with a remote: true attribute, you need to know that these wont work any more inside Turbo-enabled contexts. These functions are handled by Rails UJS and are not compatible with Turbo. Non-GET links should be converted to inline forms using button_to and remote links should be refactored to normal links handled by Turbo.

Other UJS features, such as disabling buttons or confirm dialogs continue to work normally.

Summary

To sum this all up, Turbo seems to be perfectly usable even if your legacy JavaScript code does not allow you to switch on Turbo Drive (Turbolinks) right away. This is such a great news! Turbo enables us to gradually rewrite (and effectively remove, for the most part) our old hand-written JavaScript. We can bring modern, highly reactive behavior to our newly built and updated pages without having to refactor all that rusty JavaScript prior to that.

Once the amount of JavaScript lowers substantially, we can take care of the remaining bits and switch on Turbo Drive globally to speed up the web experience even more.

Overall we think this begins a new era in our front-end development and we are very excited about it!


Original Link: https://dev.to/nejremeslnici/using-hotwire-turbo-in-rails-with-legacy-javascript-17g1

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To