1

What is the correct way to use page-specific JavaScript in Rails 6 (with turbolinks enabled)?

The articles I have found seem to suggest just putting everything in application.js - but this seems messy as it affects every page (and you also need to check the element you want to manipulate exists every time, then add an eventlistener or do nothing at all if it doesn't exist).

Another suggests adding your own folder, putting your custom JS in there and then requiring it from application.js (in effect, the same as above - it's just slightly more clean to look at but everything is still loaded for every page).

What I'd like to achieve is have my JavaScript split and only include it when needed. However, if using 'javascript_pack_tag' in a view to pull it in, this causes turbolinks to get quite upset and keep adding the event listeners, over and over. If you put 'javascript_pack_tag' in the page head, this then invalidates the cache and stops turbolinks from working.

In an ideal world;

  • The JavaScript would be in it's own custom location
  • It would be required only for the views that needed it
  • It would work with turbolinks
avsq1
  • 83
  • 5

1 Answers1

1

This is a burden Turbolinks brings to the stake. When we need to use the full potential of Turbolinks (or Turbo nowadays), we need to control the state of transformations. Meaning we need to bind/unbind (connect/disconnect) javascript events or html elements.

That's probably why the same team from Turbolinks created Stimulus. You can go this way and split all your JS by Stimulus controller and page. Then use javascript_packs_with_chunks_tag at application.html.erb (no need to import or javascript_tag anywhere else). Basically, Rails Webpack can splits your files to be per-page even though everything is imported at application.js file, but the controllers must be imported normally at application.js using import('controllers'). This way Webpacker can splitchunk files and reduce the bundle. Read more about Webpacker chunks and use something like webpack-bundle-analyzer, this helped me a lot to understand Webpacker and javascript imports.

But, even with Stimulus, we need to be idempotent, to avoid duplication of events or elements. And unfortunately you will need to check if element exists, or check if turbolink is caching.

Here goes some things I use to avoid duplication:

// Use at Stimulus `initialize()` or `connect()` or at `turbolinks:load` event, example:

$(document).on("turbolinks:load", () => {
  // Avoid loading things again when turbolinks is previewing
  if (document.documentElement.hasAttribute("data-turbolinks-preview")) return;

  // Sometimes you'll need to check if plugin is already active before trying to initialize
  if (table.getData().length) return;

  // then starts plugin or your code
  table.init();
});

// Use at Stimulus `disconnect()` or at `turbolinks:before-cache` event, example:

$(document).on("turbolinks:before-cache", () => {
  // before caching we destroy stuff to avoid duplication
  table.destroy();
});

And this post approach can work without Stimulus. Then you can use your custom javascript location, with Turbo and splitting code, as you wanted!

lestra
  • 312
  • 1
  • 7