I recently had to enhance the analytic tracking capabilities at work. We use Google Tag Manager (GTM) along with Google Analytics (GA).

If you haven’t heard of GTM, it’s essentially a code snippet you include that provides capability to dynamically modify ‘tags’ which include code snippets such as Google Analytics.

I unminified the GTM snippet due to timing issues related to our implementation that couldn’t be resolved by recommended solutions found online in order to better understand how the script is loaded asynchronously.

Here is the original GTM snippet provided:

(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-XXXXXX');

Using a tool can automatically insert whitespace which greatly helps in readability. I used http://unminify.com/

(function(w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({
‘gtm.start’: new Date().getTime(),
event: ‘gtm.js’
});
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != ‘dataLayer’ ? ‘&l=’ + l : ‘’;
j.async = true;
j.src = ‘//www.googletagmanager.com/gtm.js?id=’ + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, ‘script’, ‘dataLayer’, ‘GTM-XXXXXX’);

Here we can see a function is defined and immediately executed with a set of values. A function is used to create an scope apart from the global scope, and parameters are used instead of local variables in order to save characters from an extra var.

Next I renamed the function parameters and internal variables. Luckily in this example the characters are the first letter of the values applied.

There are other methods of parameter naming such as in the Google Analytics snippet where the letters ‘I S O G R A M’ are used. An isogram can be: ‘a word or phrase in which each letter appears the same number of times’, which is a nice easter egg :)

(function(window, document, script, datalayer, id) {
window[datalayer] = window[datalayer] || [];
window[datalayer].push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
});
var firstScript = document.getElementsByTagName(script)[0];
var gtmScript = document.createElement(script);
var dataLayerParam = datalayer != 'dataLayer' ? '&l=' + datalayer : '';
gtmScript.async = true;
gtmScript.src = '//www.googletagmanager.com/gtm.js?id=' + id + dataLayerParam;
firstScript.parentNode.insertBefore(gtmScript, firstScript);
})(window, document, 'script', 'datalayer', 'GTM-XXXXXX');

Above is the final unminified code. In summary:

  • A global datalayer array is created for event tracking
  • A timestamped datalayer event for ‘GTM initializtion’ is added to the datalayer array
  • An async script element with a configurable GTM id and an optional dynamic datalayer name are prepared
  • The async script element is finalised and inserted into the DOM before all other scripts on the page

For a more comprehensive analysis, please view the annotated source at the bottom of the file.

After I finished I was curious what the referenced script actually does. I decided not to investigate since it was hundreds of lines long, but would guess it’s responsible for modifying and handling events within the datalayer variable created within the snippet.

I then found an article related to asynchronous script loading here: https://www.igvita.com/2014/05/20/script-injected-async-scripts-considered-harmful/ where I found that not only does the property async = true provide asynchronous loading, but by creating an element and then inserting it into the DOM it also provides async support for older browsers that don’t support the async flag.

After understanding the snippet and reading the above article, I now felt a lot more confident about asynchronous script loading and managed to fix my issue.

ANNOTATED SOURCE

(function(window, document, script, datalayer, id) {
// Retrieve datalayer array from window scope or create an empty array if it doesn't exist
window[datalayer] = window[datalayer] || [];

// Push a timestamped event for GTM initialisation
window[datalayer].push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
});

// Get the first script included on the page
var firstScript = document.getElementsByTagName(script)[0];

// Create a script element
var gtmScript = document.createElement(script);

// Optionally prepare the query parameter string for the datalayer name
var dataLayerParam = datalayer != 'dataLayer' ? '&l=' + datalayer : '';

// Enable asynchronous loading for the GTM script
gtmScript.async = true;

// Finalise link to GTM script
gtmScript.src = '//www.googletagmanager.com/gtm.js?id=' + id + dataLayerParam;

// Insert new script element into DOM
firstScript.parentNode.insertBefore(gtmScript, firstScript);

})(window, document, 'script', 'datalayer', 'GTM-XXXXXX');