Get that google PSI score higher

Ibrahim Tanyalcin
4 min readApr 18, 2018

--

PSI can be useful, but did you know that it does not record DOMContentLoaded and load event firing time marks?

So you worked hard on your recent project, did a lot of js optimizations, checked your functions, algorithms, tackled how each browser behaves differently and now its about time you check site wide loading performance.

There are several tools with somewhat opinionated metrics but they all can help a bit once you understand what they actually check. One of these tools is Google’s page speed insights tool known as the PSI.

PSI will point out caching problems, image optimizations etc. A particular metric where you can have an issue is the Eliminate render-blocking Javascript alert. First let’s get to why it is render blocking in the first place:

When you deploy a script tag with no async attribute or not deferred (you can also use the defer attribute, it means this script is executed right before the closing body tag, so there is no html nodes of nodeType 1 below it other than the body and the html itself.), the script has to be executed before the rest of the html is parsed. This becomes a problem when there are awaiting nodes to be parsed below that script and that script is executing something taxing on the CPU (eventually starving the event loop). This will result in those nodes below not being rendered until the script executed. Hence that’s why it is called render blocking. Here is a theoretical example:

Here you have 3 scripts above the body. The browser has to execute these scripts before it can get to display the contents in the body. Here are the contents of these scripts:

So each script pushes a string into a global texts object, and eventually the last script tag within the html (beware that if you want the html to validate you can put the last inline script right before the closing body tag) will render these as p elements in a div. This is the end result:

Now, there are several ways to convert them to non render blocking script tags.

  • One option is to add the scripts dynamically using insertBefore or appendChild and proceed to the next one on the load events.
  • One other option is turn the async attribute on the script tags.
  • A third option to reduce the number of script tags by using a bundler.

The first option is not the most flexible. For a large number of script tags, managing the orders and dependencies can be challenging.

The second options momentarily resolves the problem until you realize that now you have lost control over the execution order of these scripts.

The third option requires you to use a third party build time tool.

Besides none of the options explore spreading burden to microtask queues using requestAnimationFrame and Promises.

For some of my projects, I decided to implement a workflow package that allows me to:

  • Take advantage of requestAnimationFrame + Promises while executing script tags.
  • Support the latest ES6 specs while still working on ie9.
  • Manage dependencies
  • Allow exporting just like native ES6 exports without leaking to global namespace

It turns out that since this workflow (taskq.js) operates on async tags, it also have a nice side effect, the scripts become non-render blocking. Instead of converting all your scripts to taskq modules, here I will describe a quick hack you can do to immediately get some benefit out of the box. Turning back to our example:

Here the original html is changed, the 3 render blocking scripts are removed. Instead we have now taskq.js, and then 2 new scripts which are both async.

The execution life time of async scripts vary between browsers, but you can safely assume that this lifetime is between the moment the async script tag is parsed (this is before the DOMContentLoaded) and the onload event on the window is fired. Therefore in your applications, you only load taskq.js first so that the global taskq object is available the moment the parser starts processing async scripts. All the rest of your application can be async as in a moment you will see that you retain full control over their execution order.

So let’s see what is in the nonRenderBlocking.js:

Here we are loading the script tags dynamically so they re not render blocking. Moreover, taskq.load(…) in truth returns a thennable object, so you can do taskq.load(…).then(function(){…}).then… etc. These are explained in more detail here and here. For simplicity I will skip over them.

Above, the function nonRenderBlocking is not executed immediately, but rather pushed to queue using taskq.push. We also attach this function a unique id by setting proprietary _taskqId property so we can refer to it later.

Let’s look at the other script someOtherProcess.js:

Here we have moved the original inline script into an async script. As before we create the function someOtherProcess and use taskq.push. This time we also add a proprietary _taskqWaitFor property which is an unordered list of function ids that this function should wait to execute. This way we make sure someOtherProcess executes after nonRenderBlocking.

Now you your app logic is non-render blocking. For more examples make sure you read the other posts and check taskq.ibrahimtanyalcin.com

--

--

Ibrahim Tanyalcin

Postdoc in Bioinformatics. Former genetic engineer. All time geometry enthusiast. Interested in Viz. Works? mutaframe.com, i-pv.org, ibrahimtanyalcin.com/gwent