

Turbolinks for Rails (like pjax) - timf
https://github.com/rails/turbolinks

======
threedaymonk
I understand the motivation for this kind of stuff, and it's neat, but I'm
wary of it because of the additional complexity it introduces for a relatively
small benefit.

I may be misleading myself, but it's rare (on a desktop browser, at least)
that it's the page rendering time that I really notice: far more significant
is usually the latency, or the time taken to transfer the significant
proportion of a megabyte of HTML that's smothering a few kilobytes of text.

On the downside, it replaces something that just works with something that ...
mostly just works. See elsewhere on this page: "Loads blank white screens in
firefox 15" / "This is now fixed". And that's the problem: you've replaced
something that works in every browser with something that you have to (or
someone has to) test in every browser, and whose failure modes must all be
handled. What happens when you click on a "turbolink" on an overloaded server,
for example? My experience so far has been that this kind of _enhanced_ link
is usually faster, but the probability of _nothing happening_ in response to a
click is not insignificant.

I'm aware that I probably sound like an old grouch.

~~~
dhh
You're right that if your server is very slow at generating the response,
turbolinks is not going to add much in terms of perceived performance. Same is
true if you download multi-megabyte pages. So don't use it for either of those
cases!

We use it for pages that have low latency (50-100ms) and a light weight
(<100KB) for Basecamp. It makes a big different for apps built like that. This
is a project that works with and encourages you to build apps like that.

~~~
twerquie
No web service is immune to fluctuating network conditions. How can I tell if
my browser is loading a turbolink? What happens when the request fails?

------
timf
DHH said on Twitter "this will ship as default-on in Rails 4.0 Gemfile" -
<https://twitter.com/dhh/status/251024691337244672>

------
nchuhoai
Really looking forward to Rails 4.0. After the big stuctural changes, it's
nice to see these additions alongside with the famous doll-caching. It's also
clear what Rails tell the Rails-is-only-an-API-layer-for-single-page-apps-
haters:

Yes you can write single page apps, but with Rails you get the speed of single
page apps with the ease of traditional Rails

------
illdave
I've been playing with pjax lately, and it works really well - there's a very
subtle improvement with rendering pages, and it does feel faster. Looking
forward to trying out Turbolinks.

------
mmahemoff
So glad this will be default in Rails. I spent ages building a custom version
of the same kind of PJAX framework, using headers to transmit flash messages,
interpreting XHR calls in new and exciting ways, etc. It really needs to be
standardised for modern web apps.

If Rails 4 is all about PJAX, my hope for Rails 5 is it's the one that
embraces fat-client JS apps and exposes JSON services in a standard way, so
they can easily be consumed by convention-based frameworks such as EmberJS.

~~~
joshpeek
> my hope for Rails 5 is it's the one that embraces fat-client JS apps

Pretty sure DHH would say not a chance.

~~~
dhh
Rails is great for fat-client JS apps as it stands today. Everything on the
controller and model side of things makes for a wonderful backend for fat-
client JS apps.

But if you're going to use Rails for the view, the focus will remain on making
Basecamp-style apps (turbolinks, russian caching dolls, etc).

------
romaniv
I like the idea, although I don't like that someone has to write this for a
framework. I mean, ideally this would be built into browser in some way.
Recompiling cached assets seems like a waste of resources. Even re-executing
JavaScript on page load could be controlled via some link parameters or
headers.

~~~
FuzzyDunlop
In terms of pushState, I think it is, and the framework only truly exists to
abstract the differing implementations across browsers. But naturally, when a
page is refreshed, the code _has_ to be re-executed, because you're resetting
state and performing all sorts of setup and initialisation.

That being said, and I'm only putting this out there, but lisp-esque
continuations in the browser for this sort of stuff?

Also, part of me thinks that if you require this sort of thing, Rails is the
wrong tool. Rails is great, but a full stack framework is pretty intensive for
this sort of thing.

~~~
jarin
I think the thing about turbolinks is you get a lot of the advantages of
single-page apps but you can write it like a normal Rails app.

------
jrochkind1
I would really like to see an option in Turbolinks to be opt-in instead of
opt-out. Instead of applying the pjax-type behavior to all links unless they
have data-no-turbolink, only apply to links that have data-turbolink (or
ideally a completely unobtrusive option to do $(node).turbolink() or
$(node).data("turboLink", true) or something. Although this is made more
complicated by turbolinks intentional lack of jquery dependency).

Probably not too hard to add it to it. Maybe i'll get around to making up a
patch myself if nobody else does.

But I'd be a lot happier with turbolink on a link-by-link opt-in basis.

------
mountaineer
This is a little like jQuery Mobile's ajax loading strategy? Though I like
that it's just links, as doing ajax form submits in jqm seems like it always
needs data-ajax=false with ajax forms needing to be very well thought out.

~~~
gingerlime
It instantly reminded me of JQM's ajax loading too. I've had good experience
with it, but the framework (JQM) feels quite heavy and definitely an overkill
for something like this. If turbolinks provides just this functionality and
with much lighter-weight (and hopefully similar browser-support as JQM) this
could be quite a promising approach.

------
charlietran
Tried this out and noticed that it maintains scroll position between pageloads
(scroll halfway down a page, click a link, new page loads halfway scrolled
down). Figured it was a bug, but then saw this item in the readme's To Do
section: "Remember scroll position when using back button".

Is it a widely accepted convention to maintain scroll position between PJAX
page loads? When a large portion of the page content is changing, one might
expect the page to scroll back to the top.

~~~
brown9-2
Unless I'm missing something, it seems that the goal would be to maintain the
illusion that different pages have loaded, and therefore the back button would
bring you back to where you were on the previous page (as it would for non-
pjax/non-Turbogears pages).

~~~
charlietran
Ah, so I misunderstood that todo, but the issue still remains that scroll
position is remembered when going to a new page.

------
danmaz74
This is interesting, but what happens to the javascript execution environment?
Does it get reset, or does it inherit all the objects that were instantiated
in the old page?

~~~
dpcx
It's part of [pjax](<https://github.com/defunkt/jquery-pjax>). pjax works by
returning all content stuffed in a single node, which then updates the current
page with those contents. It fire's Google Analytics updates, etc. It can also
change the page title.

The matching nodes from the old page are replaced.

~~~
dmix
How do you handle JS tied to document.ready?

~~~
jarin
Works great, but yeah this is the only thing preventing me from pushing it
live right now (I already moved any page-specific JS to inside the body tags
instead of in the head).

I imagine you have to rebind all your $(function() {}) code to the page:update
event (and maybe also leave it bound to document.ready for the initial page
load??), but I'm not quite sure the best way to do that.

Edit: ok is rough but it seems to work

    
    
        $(function() {
          alert("document.ready");
        });
        $(document).bind('page:change', function() {
          alert("page:change");
        });

~~~
dmix
I tried it out and binding to the pjax:end works. So just wrap any code called
by document.ready into a separate function. Then bind it to both
document.ready and:

    
    
      $(window).bind('pjax:end', ->
        # document ready code
      )

~~~
jrochkind1
Hmm, I wonder if there's any way to have an event on page:change that somehow
_sends_ a document.ready trigger. So it will trigger all document.ready hooks,
without any changes to the code hooking into document.ready (which of course
could come from third-party dependencies you can't easily change to use
page:change themselves, and which may not want to be changed to have
turbolinks-related code in them).

Might be easier if your code exclusively uses jquery's ready rather than
native DOM onload or whatever.

But yeah, either way, this is a bunch of added logic that can go wrong, I'm
not super enthused about it myself.

------
MartinMond
I dislike that this small gem is written in coffeescript.

[https://github.com/rails/turbolinks/blob/master/lib/assets/j...](https://github.com/rails/turbolinks/blob/master/lib/assets/javascripts/turbolinks.js.coffee)

For various reasons we don't use coffeescript in our projects but now if we
want to use turbolinks (which sounds great) we'll get a coffeescript
dependency.

~~~
jashkenas
Putting the ab initio strangeness of having JavaScript code in a Ruby gem
aside -- what it happens to be written in shouldn't be an issue. If the gem is
released with the compiled JavaScript inside of it, then it can just be used,
without any additional dependency.

~~~
MartinMond
Good point.

~~~
rockyj
Guys hang on. When Rails assets are compiled all Coffeescript is converted to
JavaScript, which is then compressed and minified. So unless you are writing
any Coffescript yourselves you will never have to deal with it. Asset pre-
compilation has been a Rails feature since Rails 3.1.

------
sudhirj
How would this work with something like Angular JS? I'm guessing that JS
frameworks that do DOM manipulation won't be very happy when all of their DOM
references just vanish - I'm guessing there's also the possibility of leaking
tons of memory here if the event handlers aren't unbound.

------
ashayh
Seriously. I spent many hours building my own pjax like framework with
pushState, and what I've done is (quote) "worry about what element on the page
to replace, and tailoring the server-side response to fit, " . A standardized
way of doing this would have been so much better.

~~~
JuDue
Agree with this.

From what I can think there will be two broad use cases:

1) Sibling pages within a section will often share RHS and header content. So
makes sense to replace the inner area where the actual page content lives and
leave the rest alone.

2) Cases where the above doesn't hold or it's too complicated to care about,
so load in the whole body.

I'm sure DHH chose the #2 rule because #1 turns out to cause a can of worms?

~~~
JuDue
Looking forward to some blog analysis on all this.

And the inevitable vs PJAX conversation (ie: is it flexible enough? Does it
need to be?)

~~~
halfdan
[http://geekmonkey.org/articles/28-introducing-turbolinks-
for...](http://geekmonkey.org/articles/28-introducing-turbolinks-for-
rails-4-0)

------
majofi
It really feels fast and good! I'm evaluating the impact on my projects with a
simple bookmarklet loading a js version of turbolinks (js is not always up to
date) -> <https://gist.github.com/3793194>

------
jodyalbritton
Works fine and chrome, and yes it is very fast. Loads blank white screens in
firefox 15 on Ubuntu.

~~~
dhh
This is now fixed.

------
amalag
How does it work when the pages have different javascript & CSS links on them?

~~~
jrochkind1
pretty sure it doesn't.

~~~
amalag
I see it is on the TODO list:

> Work left to do

> CSS/JS asset change detection and reload

~~~
jrochkind1
how about, say, RSS/Atom feed autodiscovery links in <head>?

how about <script>s at end of <body> for load performance instead of in
<head>?

there's always edge cases. it's asking for trouble, sez me.

------
ryan_f
I am curious at what default on means, such as will all links use this by
default?

~~~
dhh
Correct. You can opt-out links with data-no-turbolink. Or, of course, you can
opt out of the whole thing by removing the turbolinks gem from your Gemfile.

