Web Video Performance 1 – Load (y)our scripts as fast as possible

To present a video play button on your product pages DemoUp uses the fastest approaches available. Here you can learn what we do for what reason and learn which old but still used optimization rules are harmful today. The findings are generally helpful for building fast loading web pages.

This is the first part in a series of three parts. Part 2 is about rendering fast after having the loaded a script already. You will find some real world patterns that should be avoided in part 3.

Only fast play buttons are good play buttons – DemoUp specific introduction

 

Only read this when you are interested in our code

DemoUp’s video integration is script based. All what is required to integrate our service is to insert the following HTML code into your web page:

<script type="text/javascript">(function() {var s=document.createElement('script');
s.async=true;s.className='demoup_stage1_script';
s.src='//static.demoup.com/api/stages/<shopid>/stage1.js?url='+encodeURI(document.URL);
var m=document.getElementsByTagName('head')[0];m.parentNode.insertBefore(s, m);})();</script>
<script async id='demoup_stage2_script' src='//static.demoup.com/<shopname>/stage2.min.js'></script>

Our integration works by loading a small chunk of page specific code called stage1 (which contains the information what videos are available for a page) and a larger script file doing the real work called stage2. That script is the same for your whole domain and is cached by the browser.

Our stage2 will wait for the video information loaded by stage1, check whether there are videos in the correct language available and add a play button to the page if that is the case.

To make our play button appear as early as possible during page load we need both scripts to load as fast as possible. Read on to learn how we do it.

General knowledge about resource loading in browsers

 

The time required to load a script is the time needed for requesting, downloading and executing a script file.

This shot is taken directly from Chrome Devtools. You can see the various stages of loading our script file. The actual download of the file is only requiring a small fraction of the download time. A similar amount of time is spent on the DNS lookup and establishing the connection.

Note that the exact timings for downloading this file would be very different for you. Much more time might be spent on each step of the download process and we are missing a SSL handshake here which usually takes place. On the other hand the DNS lookup or the whole file might already be cached by your browser what would make following loads of the script much faster.

However, many people on the world are stuck with a slow internet connection and will have a much longer download time than the one pictured above.

A file loading in under one second might still feel fast enough, so you might not see much need for optimization here. But:

For the perceived loading time of a file the delay until the download starts is at least as important as the the required time to download the file

When opening a page, let’s say www.demoup.com, the browser has no clue that it has to load our script file. So before it can even start load the script, it has to download at least one other file. (There is actually a “Server Push” feature in HTTP 2, but that is an optimization topic in its own.) In practice we usually see a much worse case where a page loads a script which loads a script which loads a script which loads some data and afterwards shows some content on the screen. In such cases the loading and execution times of all that files are summed up. On desktop computers you perceive such pages as slow and on mobile phones they probably become unusable.

A loading cascade at its worst. Created with the help of dojo

Consequently such loading cascades are to be avoided. Instead:

The best place to load scripts resources fast is to reference them directly in your HTML code.

This is not only true because it avoids loading cascades, but it makes use of one of the browser optimizations. All browsers today have a preparser which reads in every byte of the HTML page as soon as it trickles through the network interface and looks for references for style-sheets, scripts and images. As soon as such are found they are queued to be downloaded which means that the browser is starting to download that files before it even tries to start rendering the page. This provides an additional loading boost for all resources referenced in the HTML source.

All this why our integration snippet contains script tags only. It cannot be loaded any faster.

Unfortunately ten years ago it was common to not load scripts via tags but to to delay their loading by using i.e. inline scripts. The loading of our stage1 is done in such a way (for a good reason, see below).

<script type="text/javascript">
(function() {var s=document.createElement('script');
s.async=true;s.className='demoup_stage1_script';
s.src='//static.demoup.com/api/stages/<shopid>/stage1.js?url='+encodeURI(document.URL);
var m=document.getElementsByTagName('head')[0];m.parentNode.insertBefore(s, m);})();</script>

This was done because the HTML parser of browsers stops parsing and building the page and loads the referenced script file before it continues its work. The former rule was to avoid those script tags to make initial rendering of your page faster. This had downside even at that time because the delayed scripts usually build some UI components and their initialization was heavily delayed by that approach.

Today that approach is just an anti-optimization pattern because:

You should use the async attribute in script tags

<!-- Bad for loading performance. Blocks rendering until loaded -->
<script src='//static.demoup.com/<shopname>/stage2.min.js'></script>
<!-- Good for loading performance, but you get no guarantees about the order of script loads -->
<script async src='//static.demoup.com/<shopname>/stage2.min.js'></script>
<!-- Good for loading performance, but execution takes place after render of the page -->
<script defer src='//static.demoup.com/<shopname>/stage2.min.js'></script>

Async makes the browser not to wait for script files. The browser will just download the file and execute it when it is available without delaying anything else. Note that executing this might block the rendering thread, big and heavy scripts might slow down rendering when loaded with the async attribute. For scripts like that you can alternatively use the defer attribute, which will execute the script not before the page has been rendered.

How to do it fast (shot taken on demoup.com). Critical CSS is in-lined and all relevant resources are directly referenced in the HTML source. Rendering is complete in less than 400ms.

An additional hint for all the real-world web-pages out there:

Loading a few big files is faster than loading many small files. Use code bundlers like webpack to concatenate your code in one file

You can find out the details why and all about the life of a resource request in the great blog of Ilya Grigorik. The penalty for having many resource files is getting smaller with HTTP-2, still loading hundreds of javascript files separately is no good idea. We often see this in shops, especially for loading jQuery modules. If you want a specific file to load fast it is probably slowed down only by the fact that there are those many other scripts to download, too. Code bundling is the answer here. Do not forget to minify the created scripts afterwards.

 

Summary:

To load a script file asynchronously but still fast, throw away all script loading solutions and just use a script tag with the async attribute. There are only very ancient browser which do ignore that attribute, but it would be unwise to make you web page slower for 99% of your users to optimize for 1% of users which will have a slow usage experience anyhow.

Using module loaders like requireJS and so on might still be justified to ensure a required execution order of scripts. We would suggest using script bundles instead or the usage of at least a <link rel=”preload”> hint the HTML source for the required script files. But preloading is only supported by very modern browsers, so script bundling is the better approach.

The reason why DemoUp loads its stage1 in this ancient looking way is because the exact URL of that script must be build using the URL of the page loading. There is no way to make use of the preparser here.

The standard integration snippet from DemoUp is able to load very fast. What is left to our customers is the hint to place it somewhere at the top of the page, ideally before any blocking resource.

 

Read on in the series and learn about rendering your loaded scripts fast in part 2. You will find some real world patterns that should be avoided in part 3.

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *