Please bear in mind that what is written here is all dug up from the HTML / JS sources of the Hey site and is limited by my current understanding of them. If you happen to find errors in my thinking, go ahead and tell me!
Update as of Dec 2020: now that the Hey support code has been extracted and officially released under the name Hotwire, we know that this article actually speaks about the Turbo Frames. Basecamp renamed and polished a few things but the principles stay the same as described here.
When you open up the Developer tools Network tab while clicking on any Hey page, you’ll soon notice a pattern of HTML resources loading: only the most important page content is loaded with the first request while the less relevant stuff is loaded asynchronously afterwards.
For example, the initial request of the main page (Imbox) will load the main content (the page layout plus the mail listings, green area) but not the top yellow upgrade banner or the “Reply later” and “Set aside” feeds at the bottom (red areas) − these three areas are loaded async:
It looks like this in the Network tab:
Why even bother with such a pattern? Let’s emphasize a few things here:
Loading only the page skeleton and some most relevant stuff is good for the speed. Neither the server, network, nor browser is slowed down by rendering the less important things during the initial request. The user can start scanning the page a bit sooner.
The requests can be cached more easily. See the
304status codes in the Network tab? Those are cached requests - the server still has to calculate and render their template but the browser does not have to download or paint anything as the response is all the same! Smaller requests covering only a portion of a page are more likely to stay the same and thus be cacheable. Again, this is a speed optimization!
Let’s use the Page inspector picker tool to reveal the HTML code of the yellow upgrade banner. It shows something like this:
turbolinks-frame element, it has a
src attribute pointing to the
/account/trial/callouts back-end action which we saw in the Network requests listing above. OK, but how does it obtain its content, the banner
The answer lies in the fact that the
turbolinks-frame tag is a Custom HTML element. This means a few important things that lead to the following investigation:
elements/turbolinks_frame.tsfile in the JS sources. The extension tells us that this is a TypeScript file.
This is actually a very similar concept to the Stimulus framework, invented by the Rails team. The difference is that Custom elements are directly supported by (modern) browsers. On the other hand, Stimulus controllers are way more opinionated, so the JS code is usually smaller and (I’d say) more readable.
The Web components standard states that the custom element must be
define-d to bring it to life on a page. OK, we can find this
definestatement at the bottom of the
turbolinks_frame.tsfile. This statement connects the HTML tag to the
TurbolinksFrameElementclass in the same file.
This class has quite some code to read through and it even cooperates with a few sibling classes… nevertheless we can ignore all that for now and focus on only one thing − the
attributeChangedCallback. The docs say that this callback is automatically invoked by the browser whenever an “observed attribute” changes its value or is added to the custom element. For which attributes the change is noticed is specified in the
observedAttributesmethod. A quick look into this method reveals that this custom element is observing its
So, whenever the
srcattribute of the custom element changes, the callback method will be invoked by the browser. The same happens when the whole element first appears on page. Remember that the
srcattribute contains the URL of the resource that defines the content of the page part. Let’s see what happens when the callback is invoked:
Oh now we’re getting somewhere: when the browser discovers a new URL in the
srcattribute, it grabs that URL and calls a Turbolinks
visitmethod which fetches the URL via AJAX and calls the
FrameController.requestSucceededWithResponsecallback upon success. Further jumping around the source code finally gets us to the
loadFrameElementmethod which takes the response from the AJAX call and replaces the custom element with it. It looks we’ve just updated that page part with a new content from the server!
To sum up this workflow, this is all you need to do for autoloading a page part upon page load: add an empty
<turbolinks-frame> tag to the page somewhere and fill in its
src attribute. The tag contents will get auto-updated via an AJAX request just after the main page loads. I guess the Turbolinks team will provide some nice back-end helper, too, to make things here even simpler.
Let’s show the upgrade banner server response that we got here:
Oh well, this got a little longer than expected, I will continue this topic in the next post about interactively loaded page parts, have a nice day and stay tuned…