Skip to main content

Lazy loading in the HTTP/2 world

Already touched upon this topic in my HTTP/2 write-up (don’t do this without setting up HTTP/2 first), but although I did initial testing back then only got back to it seriously yesterday. So, let met give some very quick instructions on how to get it working in PSK, though likely it will be similar in any other Polymer application.

<page-*> elements

Start of with making every page an element. In my case these elements are called <page-*> elements, so your index.html looks something like:

<iron-pages attr-for-selected="data-route" selected="{{route}}">
    <page-welcome data-route="home"></page-welcome>
    [...]
</iron-pages>

It’s possible to set this up in different ways as well, but this is the best way I have found so far to organize my application in Polymer.

Lazy loading them

My approach is to trigger the loading of the elements from the router. This might be somewhat debatable from a logical point of view (a router shouldn’t be loading anything), but the alternative of creating an entire element which on becoming visible would lazy load the <page-*> seemed uglier to me than simply loading the element from the router.

So, on my app I defined a function loadDependency as follows:

var loadedDependencies = [];
app.loadDependency = function(el){
  if(loadedDependencies.indexOf(el) === -1){
    loadedDependencies.push(el);
    app.importHref('/elements/' + el + '.html', function(){
      window.dispatchEvent(new Event('resize'));
    }, function(){
      alert('Failed loading the requested page.');
    });
  }
};

and my routing functions look like

page('/', function () {
  app.loadDependency('dashboard/page-welcome');
  app.route = 'home';
});

Things of note:

  1. I have to trigger a resize event after the element is loaded. The reason for this is that some elements - like iron-list do not correctly initialize otherwise, it might be worth trying out commenting out that line and checking whether everything works for you.
  2. The alert is just a stop-gap solution and of course it’s not wise to leave it that way.

The elements.html file

One very common mistake is that people list all their elements in the elements.html file. This file is meant to only list the dependencies from your index.html (excluding the lazy loaded elements of course). Do not list dependencies of dependencies here (e.g. an element used by a <page-*>). Dependencies of <page-*> elements should - like with every other custom element - be listed at the top of the file. That way those dependencies are only loaded once required.

Passing attributes

UPDATE: It seems this has been solved in Polymer 1.3, see the last item on the release notes.

To pass attributes from the router to the pages it will look like

page('/search/:query', function (data) {
    [...]
    app.set('params.searchQuery', data.params.query);
});

and

<page-search data-route="search" search-query$="{{params.searchQuery}}"></page-search>

The reason for the odd usage of $= is that during page load the page-search element isn’t registered and thus a plain DOM element on which the Polymer DOM magic can’t work. By making it an attribute property however Polymer won’t worry about ‘properly’ binding it and instead will simply set it as an attribute anytime the property is changed.

Flash of unloaded elements

One problem you might encounter is that you get to see a page which isn’t fully loaded yet. Now, depending on the approach you use there are a lot of different ways to ‘fix’ this, but in the end all of them depend on having some code in the success callback of the importHref method. In my case I have a simple overlay (which looks like the barebones of my application) with pointer-events: none; and

document.getElementById('loading').style.opacity = 0;

in my loadDependency success callback. Maybe not the most generic setup and I am sure there are neater ways to program this, but it gets the job done perfectly.

Last thoughts

All considered with HTTP/2 setting up lazy loading is easier than it’s ever been before, but there are still some pain points, but none of them are too bad. Once Polymer releases it’s new router stuff might get either easier or harder, we shall see, but I hope it won’t be too different all considered. For that matter, creating a lazy loading <lazy-pages> would actually be relatively easy as well and once Polymer properly supports extending custom elements that might actually be the best option.

Comments

Popular posts from this blog

Do JS libraries belong in the DOM?

UI-less custom elements? So, when you start using Polymer you will at some point encounter <iron-ajax> and if you’re like me you will be somewhat confused by the fact that it’s an element. After all, it has no UI and and seems to be entirely misplaced in the DOM. Now, after that you can do one of two things: Either accept it as ‘the way it works in Polymer land’ or you can just ignore it and not use elements like that. I - somewhat uncomfortably - did the former 1 and continued making a lot of elements like that. I made complex <auth-api> elements that would consume my API and toolbox elements like <text-utils> that provided a set of functions to work with text. And all seemed good in the land of Polymer. Till on a fateful day I was implementing a complex web worker and I realized I couldn’t use any of those libraries as web workers (and service workers) do not implement the DOM. Which sucked big time as I need functions on my <text-utils> element. Now,

Components which refuse to live in the Shadows

I still owe you guys two more posts about Letsencrypt and Service Workers, but let me just share a short article about my struggle getting Recaptcha working in the Shadow DOM… and a crazy solution how to get such components working none the less. You can’t use the standard <div class="g-recaptcha"></div> , because Googles script simply won’t see it. You can’t use the standard Javascript API rendering mechanism, because first of all most of the documentation uses it with an id reference (which isn’t going to be found inside the Shadow DOM), but even if you use a direct element reference there (which will actually show the Recaptcha box) we reach the most painful issue: Recaptcha stays stuck on the spinner after clicking the checkbox . And yes, I checked, it’s really the Shadow DOM causing the issue, likely due to some crazy callback to some global or something. So how do we get it to work? By putting it in to the light DOM… which is far easier said than done

The road to HTTP/2

Getting HTTP/2 up and running is such a pain that I decided to that I would try sparing the rest of the world some of the pain by sharing the story. Let’s get the basics out of the way first: The biggest advantage of HTTP/2 is that files get interleaved over a single connection, which means in normal language that you can send one thousand one kilobyte sized files for virtually the same cost as one huge concatenated one megabyte file. Practically this thus means you don’t need to have huge complex build steps where you attempt to inline all your dependencies and concatenate the entire thing into one huge file. Want to read up on other advantages then this is the best resource I found. So, in this article we’re going to assume that our intention is to deploy just the Polymer Starter Kit and a simple node backend API. Additionally the goal of implementing HTTP/2 is not about getting marginal speed benefits, but about easing development by making production more like development and si