Turbolinks and beyond

@h6165

Abhishek Yadav

ரூபீ ப்ரோக்ராமர்

Co-organizer: Chennai.js and Chennai.rb

The idea

Turbolinks

  • Short-circuit all the links on the page
  • Load them using Ajax
  • Extract <body> from the response and replace in current page 

 

The goal

Turbolinks

  • Make link loads feel faster
  • Avoid having to use front-end frameworks

Demo

Turbolinks

  • Turbolinks demo
  • Pjax demo

Demo

Turbolinks Demo

Pjax Demo

Turbolinks

http://pjax.herokuapp.com

The history

Turbolinks

  • pjax (https://github.com/defunkt/jquery-pjax)
  • Turbolinks-classic
  • Turbolinks-5

The idea

Turbolinks

  • Short-circuit all the links on the page - only the application links
  • Load them using Ajax - XmlHttpRequest
  • Extract <body> from the response and replace in current page 
    • ​Change the page Url
    • Hack the back button too
  • ​Cache visited pages
    • ​Use cached pages for back navigation
    • Use cache for temp-preview in forward navigation
  • ​Show a CSS based loader

Problems

Turbolinks

  • Loss of events
    • No page load -> no DOMContentLoaded -> no window.onload -> $(document).ready won't work
  • Over-caching
    • Back button doesn't reload the previous page - can show stale data
  • Over-scripting
    • <script> tags in header and body are evaluated on every page load
    • If the script makes a non-idempotent change on the page, it will be repeatedly applied.

Problems

Turbolinks

  • Application may need to be rewritten (in parts at least)
  • Many plugins can stop working

Problems

Turbolinks

Problems - Solutions

Turbolinks

Loss of events

  • No page load -> no DOMContentLoaded -> no window.onload -> $(document).ready won't work
    • Listen on the turbolinks:load event

    • Use event delegation

    • Use MutationObserver

Problems - Loss of events

Turbolinks

Use turbolinks:load

After loading a new page, Turbolinks fires the turbolinks:load event. All/most $(document).ready can be replaced with that -


$(document).ready(function(){
  // page load stuff
})

// becomes

$(document).on("turbolinks:load", function(){
  // page load stuff
})

// or

document.addEventListener("turbolinks:load", function() {
  // page load stuff
})

Problems - Loss of events

Turbolinks

Listening on turbolinks:load is not the best approach.

  • Makes the app dependant on library specific event
  • Plugins can't use this approach

Problems - Loss of events

Turbolinks

Event delegation

  • Is a popular technique (not specific to Turbolinks)
  • Attach event to future-elements (elements that may get added via Ajax) by binding to a top level element
  • eg - React event handling uses delegation only
  • Depends on event bubbling - comes with its own challenges - but is in general more reliable

Problems - Loss of events

Turbolinks

Event delegation

// Instead of -

$('.dance-button').on('click', function(){
  // start dancing
})

// This -
// Will work if .dance-button is loaded later by an Ajax call
// Will work with Turbolinks without change

$('body').on('click', '.dance-button' function(){
  // start dancing
})

Problems - Loss of events

Turbolinks

Mutation Observers

  • Is a somewhat novel technique
  • We can observe changes to the DOM and respond to them
  • The idea is, DOM content can be changed by entities other than Turbolinks. Like Websockets.
  • This could be one place where all the content changes are captured and managed

Problems - Loss of events

Turbolinks


var el = document.getElementById('#my-widget');
 
// create an observer instance
var observer = new MutationObserver(function(mutations) {
  console.log("It changed");
});

observer.observe(el, { childList: true })

Mutation Observers

Problems - Over caching

Turbolinks

Opt out

Cache will not be used in restoration (back button)

or for previews (for visited links)


<meta name="turbolinks-cache-control" content="no-cache">

Problems - Over caching

Turbolinks

Persist elements

  • Elements marked as permanent will be left out during page loads
  • Suitable for things like cart-counter - whose change logic is independent of page loads

<div id="cart-counter" data-turbolinks-permanent>1 item</div>

Problems - Over scripting

Turbolinks

Idempotent scripts

Scripts that can be run one or many times with the same result

// Overscripting example -
// This is an inline script 

<script>

  function foo(){
    $("#welcome").append("<b> Welcome to our site </b>");
  }

  function bar(){
    $("#welcome").html("<b> Welcome to our site </b>");
  }

  
  foo();
  bar();

</script>

// foo is not-idempotent
// bar is idempotent

Similar projects

Turbolinks

  • Turbolinks classic
  • Jquery-turbolinks
  • Turbograft

Conclusion 

Turbolinks

Why resist client-side Js frameworks - cons

  • Your Ain't Gonna Need It Yet (YAGNI) - chances are -  you are over-engineering.
  • Cost of addition
    • Learning curve
    • Api
  • Poor UX
  • The Just use Jquery philosophy - Its still alive

Conclusion 

Turbolinks

Why resist client-side Js frameworks - pros

  • Are client-side frameworks the new Jquery ?
  • Your app will become complex enough eventually ? Plan for UI complexity
  • Resume driven development - if we don't know React, someone else gets the job

Open scenario

Turbolinks

  • Form with dropdown
  • Subsequent elements change on differing value

Turbolinks and beyond

By Abhishek Yadav

Turbolinks and beyond

  • 1,346