In setting up this Jekyll-powered blog, I had cause to dust off my Google Analytics account and finally take Disqus for a spin. Both services provide small snippets of JavaScript for inclusion in your web pages; Google Analytics’ looks much like this (with line breaks added for readability):
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
'.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
Disqus’ is as follows (with line breaks added and extraneous comments removed):
<script type="text/javascript">
var disqus_shortname = 'example';
// var disqus_identifier = 'unique_dynamic_id_1234';
// var disqus_url = 'http://example.com/permalink-to-page.html';
(function() {
var dsq = document.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
Both seem terse enough and are meant for inclusion at the bottom
of your web page (Update: enomar on Hacker News pointed out that
Google’s asynchronous tracking code is ideally meant to be included at the end
of the head
element while Disqus’ JavaScript is meant for inclusion
wherever you want your comments to appear but both can be placed at the bottom
of your page, see “Getting Started with the Asynchronous Snippet” and the end
of this post for more information).
A quick check with Amy Hoy and Thomas Fuchs’ “DOM Monster” shows that
the number of script
tags should ideally be kept to a minimum so let’s combine
the two into one (and let’s specify those recommended Disqus variables while we’re
at it):
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
'.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
var disqus_shortname = 'example';
var disqus_identifier = 'unique_dynamic_id_1234';
var disqus_url = 'http://example.com/permalink-to-page.html';
(function() {
var dsq = document.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
We could stop at this point but I am something of a sucker for compressing web pages and JavaScript down to their bare minimum (I have been known to pore through the HTML5 specification’s “optional tags” section and butcher my markup appropriately).
We might decide to put the whole thing into something like
Google’s Closure Compiler to optimise the code for us but let’s take it more
slowly and start by looking at the script
tag itself: it turns out that the type
attribute is optional and is specified as having a default value of
text/javascript
so we can immediately lose that
(Update: this is only true if you’re using HTML5). It
turns out that the actual JavaScript itself is also creating script
tags
and specifying the type
attribute so let’s remove those as well:
<script>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
'.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
var disqus_shortname = 'example';
var disqus_identifier = 'unique_dynamic_id_1234';
var disqus_url = 'http://example.com/permalink-to-page.html';
(function() {
var dsq = document.createElement('script');
dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
Another thing that has caught my eye is the odd set up of the _gaq
variable:
it checks to see if it has already been defined (which it has not in my case),
assigns an empty array and then pushes two arrays onto it. This is due to the fact
that _gaq
can be modified after Google’s tracking code has loaded to use more
advanced tracking features (see the documentation for _gaq.push
for
more information) but seeing as I am only doing basic analytics and taking a cue from
Mark Pilgrim’s code from “dive into mark”, we can simplify
this quite drastically:
<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']];
// ... omitted for brevity ...
</script>
While we are looking at variable declarations, another easy optimisation we can make is to take advantage of JavaScript’s syntax to declare multiple variables at once:
<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']],
disqus_shortname = 'example',
disqus_identifier = 'unique_dynamic_id_1234',
disqus_url = 'http://example.com/permalink-to-page.html';
// ... omitted for brevity ...
</script>
With those low-hanging fruit out of the way, we need to consider what both snippets
are actually doing. They are, in fact, very similar: they are both creating
script
tags set to load some external JavaScript on your page in the following way:
- Create a new
script
element withdocument.createElement
; - Inform the script that it is to load asynchronously by setting the
async
attribute; - Set the
src
of the element thereby identifying the location of the external script; - Insert the finished element into the web page.
With this knowledge, we can start to remove some repetition in the code. Firstly,
we can streamline the creation of the two script
tags by setting them up
simultaneously (and moving them into the same closure while we’re at it):
<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']],
disqus_shortname = 'example',
disqus_identifier = 'unique_dynamic_id_1234',
disqus_url = 'http://example.com/permalink-to-page.html';
(function() {
var ga = document.createElement('script'),
dsq = document.createElement('script');
ga.async = dsq.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
'.google-analytics.com/ga.js';
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
We’ve stated that both snippets insert their newly-created script
elements into
the DOM but they are currently doing it in different ways. Let’s change this
and, in the spirit of following Google’s Performance Best Practices, let’s
append the script
elements to the body
tag by using document.body
and appendChild
:
<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']],
disqus_shortname = 'example',
disqus_identifier = 'unique_dynamic_id_1234',
disqus_url = 'http://example.com/permalink-to-page.html';
(function() {
var ga = document.createElement('script'),
dsq = document.createElement('script');
ga.async = dsq.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') +
'.google-analytics.com/ga.js';
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
document.body.appendChild(ga);
document.body.appendChild(dsq);
})();
</script>
We’re now in much better shape than when we started and a lot closer to following the Don’t Repeat Yourself (DRY) principle.
If you want to take this even further then there are a few other changes that you might consider making:
- If your site is only served via HTTP and not HTTPS then you can lose the
'https:' == document.location.protocol
check; - You can remove the string concatenation that uses the
disqus_shortname
by explicitly setting thesrc
to your own Disqus URL such ashttp://example.disqus.com/embed.js
; - Instead of calling
document.body
twice, you can cache it once in a variable which you can then reuse. - A quick check with JSLint reveals that invoking a function defined inside
parentheses (e.g.
(function() { ... })()
) should be done inside the parentheses instead of outside (e.g.(function() { ... }())
).
If you decide to take these (admittedly more drastic) steps then you might end up with something like the following:
<script>
var _gaq = [['_setAccount', 'UA-XXXXX-X'], ['_trackPageview']],
disqus_shortname = 'example',
disqus_identifier = 'unique_dynamic_id_1234',
disqus_url = 'http://example.com/permalink-to-page.html';
(function() {
var ga = document.createElement('script'),
dsq = document.createElement('script'),
body = document.body;
ga.async = dsq.async = true;
ga.src = 'http://www.google-analytics.com/ga.js';
dsq.src = 'http://example.disqus.com/embed.js';
body.appendChild(ga);
body.appendChild(dsq);
}());
</script>
At this point you can now turn to more brutal compressors such as YUI Compressor or the aforementioned Closure Compiler which (after some tweaks to keep JSLint happy) will result in a single snippet like so (line breaks added for some semblance of readability):
<script>
var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]],
disqus_shortname="example",disqus_identifier="unique_dynamic_id_1234",
disqus_url="http://example.com/permalink-to-page.html";(function(){
var a=document.createElement("script"),b=document.createElement("script"),
c=document.body;a.async=b.async=true;a.src="http://www.google-analytics.com/ga.js";
b.src="http://example.disqus.com/embed.js";c.appendChild(a);c.appendChild(b);}());
</script>
Of course, there’s no reason for it to end there. I’ve got it down to 412 characters while still passing JSLint, feel free to post your own attempts in the comments:
<script>
var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]],
disqus_shortname="example",disqus_identifier="unique_dynamic_id_1234",
disqus_url="http://example.com/permalink-to-page.html";(function(){
var a=document,b=a.createElement("script"),c=a.body,d;
b.async=true;d=b.cloneNode(false);b.src="http://www.google-analytics.com/ga.js";
d.src="http://example.disqus.com/embed.js";c.appendChild(b);c.appendChild(d);}());
</script>
There are only two rules:
- Your solution must not produce any errors from JSLint with “Assume a browser” ticked;
- The only global variables that should be declared are
_gaq
,disqus_identifier
,disqus_shortname
and(Update: Disqus software engineer Anton Kovalyov says in the comments thatdisqus_url
disqus_url
is not required ifdisqus_identifier
is set so feel free to leave that one out).
Update: Based on feedback from Steve Klabnik, Anton Kovalyov and Andrew Walker, here is a solution that consists of only 345 characters:
<script>
var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]],
disqus_shortname="example",disqus_identifier="unique_dynamic_id_1234";
(function(a,b){
var c=a.createElement("script"),d=a.body,e;
c.async=b;e=c.cloneNode(b);c.src="//www.google-analytics.com/ga.js";
e.src="//example.disqus.com/embed.js";d.appendChild(c);d.appendChild(e);
}(document,true));
</script>
If you want to follow Google’s recommendation of inserting their
snippet at the bottom of the head
element (and continue inserting Disqus’ snippet at the bottom of
the body
) then you will have to sacrifice the single, unified script
for two
separate ones:
<head>
<!-- Usual HTML head elements here... -->
<script>
var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]],
disqus_shortname="example",disqus_identifier="unique_dynamic_id_1234";
function a(b){var c=document,d=c.createElement("script");
d.async=true;d.src=b;
c.documentElement.firstChild.appendChild(d);}
a("//www.google-analytics.com/ga.js");
</script>
</head>
<body>
<!-- Page content here... -->
<script>a("//example.disqus.com/embed.js");</script>
</body>
This version consists of a total of 325 characters but introduces a new global
function named a
which will create a new script
element and append it to
the head
element of the page. The benefit of this approach is that it will
immediately start loading the Google Analytics tracking code which might
otherwise be delayed (particularly on large web pages) but the extra script
and population of the head
element won’t win you any favours with the DOM
Monster.
Update #2: I have since taken to another approach that reduces the number of script
elements created by synchronously requiring the Disqus JavaScript yourself at
the bottom of the body
:
<head>
<!-- Usual HTML head elements here... -->
<script>
var _gaq=[["_setAccount","UA-XXXXX-X"],["_trackPageview"]],
disqus_shortname="example",disqus_identifier="unique_dynamic_id_1234";
(function(a){var b=a.createElement("script");b.async=true;
b.src="//www.google-analytics.com/ga.js";
a.documentElement.firstChild.appendChild(b);}(document));
</script>
</head>
<body>
<!-- Page content here... -->
<script src=//example.disqus.com/embed.js async></script>
</body>