3rd party scripts have a strange, partly justified bad reputation in our world of web development. Yes they can hurt website performance and slow your site down, but they can also add to your sites functional and non-functional requirements without you having to reinvent the wheel.

Let’s dive in to 3rd party scripts and find a couple of relatively easy ways to fix (most) of them and improve your website performance.

Here be dragons?

The instructions here go “against manufacturer guidelines” tutorials. Just be aware of that. That said, I’ve done this in the wild with no noticeable downsides.

Async blocks the DOM building, Defer does not

Before we stray from the inspiration for this tutorial, let’s have a quick recap.

(These images are from the tutorial on web.dev)

Timeline of an async load

Timeline of a deferred load

You can see from these 2 images that scripts with the async attribute can potentially block the DOM from building. Scripts with the defer attribute will wait until after the DOM has been built.

3rd party script tag

In their simplest form, a 3rd party script will just be an externally loaded script. I really want to focus on the code snippets later in this tutorial, but for completeness we’ll talk a bit about the simplest tags. For example:

<script src="https://my-cool-tracker.com/tag.js" async></script>

The web.dev tutorial correctly states that you should use async “if it is important to have the script run earlier in the loading process”, and defer for “less critical resources”.

Now, hear me out. When it comes to 3rd party scripts on your site, there is no valid way that an async script will ever need to interrupt DOM building. By it’s definition, an async script will run at some random (because we can’t control download) time before the load event. There is nothing deterministic in there that will be needed for your page. Just don’t use async at all. Ever. Use defer to load third party script tags.

6.39kb

Anatomy of a standard 3rd party code snippet

<script>
  (function(a,b,c,d,e) {
     a=a[e]=a[e]||{q:[],onLoad:function(c){a.q.push(c)}}
     e=b.createElement(u);e.async=1;d.src=d
     d=b.getElementsByTagName(c)[0];d.parentNode.insertBefore(e,d)
  })(window,document,'script','https://some-tracker/script.js','TRACKER_OBJECT')
  TRACKER_OBJECT.onLoad(function() {
    TRACKER_OBJECT.init({
    })
  })
</script>

Your first reaction may be “WTF is that doing”. It’s actually a pretty clever and simple implementation of an asynchronously-loading command buffer. This is just one example, but for the purposes of this tutorial, just know that usually they are:

  • creating an array (queue, or buffer) in which to store commands and/or settings
  • creating a script tag (which will then start reading from that array and acting on it)

Reusing our approach for script tags, all we want to do here is find the script tag creation, and do one of the following:

  • make sure it is already defer
  • change async to defer
  • add the defer attribute

Focusing on this alone means we don’t have to be concerned with actual implementation details of the code snippet. They will all do things slightly differently and if we try too hard we’ll end up breaking something unnecessarily.

In this case we have

<script>
  ...
     e=b.createElement(u);e.async=1;d.src=d
     d=b.getElementsByTagName(c)[0];d.parentNode.insertBefore(e,d)
  ...
</script>

Look for the keywords that cannot be obfuscated/minified - createElement, getElementsByTagName, insertBefore. If we follow the minified variable names we’ll see that this broadly translates to:

<script>
  ...
     const newScriptTag =document.createElement('script');
     newScriptTag.async=1;
     newScriptTag.src='someUrl';
     const existingTag = document.getElementsByTagName('script')[0];
     existingTag.parentNode.insertBefore(existingTag,newScriptTag);
  ...
</script>

Knowing that, we now have the confidence to change `e.async=1` to `e.defer=1`. Simple.
Similarly, if that line was missing, it is easy to add.

It won’t fix performance on its own

None of this is a silver bullet. In fact, in the world of web performance optimisation there are no silver bullets. It’s more like death by a thousand cuts when it comes to website/app slowdown. We have to be aware of many tools at our disposal in order to keep our websites/apps performant. In this case we are essentially making sure our other content is going to be prioritised and not interrupted during the DOM building process.

Thanks for reading.

This is part of a series, gently introducing web performance concepts.

Tags: #frontend