Reputation: 613
Most of you are probably familiar with this little tracking code offered by Google Analytics.
<script>
(
function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
}
)(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-00000000-0', 'auto');
ga('send', 'pageview');
</script>
The interesting part is that this little excerpt contains arguments that form the word isogram. This script also uses arguments to declare variables to shave off some bits from the final file size. Arguably, you would not use this pattern when writing the code (?), so my question is, how does Google minify its code and are these techniques available to mere mortals, too?
I found online this example by Stephen Morley that includes a code that looks like something you'd write before minifying it. I took that code and run it through Google's very own Closure Compiler on advanced optimisation. As expected, the resulting code is nothing like the actual script used by Google Analytics.
(function(){window.b="ga";"ga"in window||(window.a=function(){window.a.q.push(arguments)},window.a.q=[]);window.a.c=(new Date).getTime();var c=document.createElement("script");c.src="//www.google-analytics.com/analytics.js";c.async=!0;var d=document.getElementsByTagName("script")[0];d.parentNode.insertBefore(c,d)})();
This time, the code is less DRY and bigger, even without the two extra commands.
So to clarify, I am curious how the Google engineers arrived to the above result (I do not think their code actually looks like the one in Stephen's example), and could this process be replicated even if you are not a part of Google? Thank you in advance!
Upvotes: 14
Views: 6170
Reputation: 6725
You can use npm and a task runner like gulp. Gulp has a plugin called uglify which will eliminate extra spaces and take parameters and variables and reduce them to one letter in order to further reduce the overall number of characters in the code.
Upvotes: -1
Reputation: 4472
Image to have a piece of code like follow:
(function(){
window.GoogleAnalyticsObject = 'ga';
window.ga = window.ga || function(){
(window.ga.q = window.ga.q || []).push(arguments)
},
window.ga.l =1 * new Date();
var a = document.createElement('script'),
var m = document.getElementsByTagName('script')[0];
a.async = 1;
a.src = '//www.google-analytics.com/analytics.js';
m.parentNode.insertBefore(a, m)
})();
Then, modify your code in order to passing all the object you need as parameters:
(function(i, s, o, g, r, a, m){
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function(){
(i[r].q = i[r].q || []).push(arguments)
},
i[r].l =1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
Remove all the spaces and finally you get:
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
I hope I was clear, bye.
Updated: Are you asking why they choose the word "isogram"? It's one of the "known" isogram words, see Wikipedia if you need more parameters.
Upvotes: 2
Reputation: 570
I have a feeling that the word "isogram" is a bit of a sly hint from the Google employee who minified this code.
Since an isogram is a word with no repeating characters, it represents the precise logic necessary for the minification of parameters and other variable names that must be unique from one another.
In all likelihood, this term was baked into the minifier, so that the first collection of minified variables would indicate that they know a bit about the logic of unique letter sequencing.
Since the word isogram
is itself an isogram, the person who created the minification logic could set it to check a parameter or argument list for a case in which there are 7 arguments/params, and in that case simply replace each with the corresponding letter in the word "isogram". This would add some overhead, but such cases are rare, and Google does have a lot of servers and network engineers to optimize their scripts.
Upvotes: 2
Reputation: 1402
I like to think that there are infinitely many possible ways to write code. (although this may not be true) One way to write code to where it's minified and possibly conserve space is to use obfuscation. For example, the following code:
function NewObject(prefix)
{
var count=0;
this.SayHello=function(msg)
{
count++;
alert(prefix+msg);
}
this.GetCount=function()
{
return count;
}
}
var obj=new NewObject("Message : ");
obj.SayHello("You are welcome.");
Can be obfuscated to look like this:
var _0x3c28=["\x53\x61\x79\x48\x65\x6C\x6C\x6F","\x47\x65\x74\x43\x6F\x75\x6E\x74","\x4D\x65\x73\x73\x61\x67\x65\x20\x3A\x20","\x59\x6F\x75\x20\x61\x72\x65\x20\x77\x65\x6C\x63\x6F\x6D\x65\x2E"];function NewObject(_0x12c4x2){var _0x12c4x3=0;this[_0x3c28[0]]= function(_0x12c4x4){_0x12c4x3++;alert(_0x12c4x2+ _0x12c4x4)};this[_0x3c28[1]]= function(){return _0x12c4x3}}var obj= new NewObject(_0x3c28[2]);obj.SayHello(_0x3c28[3])
This was done using a free obfuscating algorithm on https://javascriptobfuscator.com/Javascript-Obfuscator.aspx .
I'm sure google has their own ways of handling their code of course :).
Upvotes: 2
Reputation: 8441
Google is nice, because they give us a full documentation about many thing on https://developers.google.com
So many of your responses can be found on :
Here is the unminified Analytics.js
(function(i, s, o, g, r, a, m){
i['GoogleAnalyticsObject'] = r; // Acts as a pointer to support renaming.
// Creates an initial ga() function.
// The queued commands will be executed once analytics.js loads.
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
},
// Sets the time (as an integer) this tag was executed.
// Used for timing hits.
i[r].l = 1 * new Date();
// Insert the script tag asynchronously.
// Inserts above current tag to prevent blocking in addition to using the
// async attribute.
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
// Creates a default tracker with automatic cookie domain configuration.
ga('create', 'UA-XXXXX-Y', 'auto');
// Sends a pageview hit from the tracker just created.
ga('send', 'pageview');
And here is the minified version they provide (pretty version):
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');
And here the minified version with closure compiler tools
(function (a, e, f, g, b, c, d) {
a.GoogleAnalyticsObject = b;
a[b] = a[b] || function () {(a[b].q = a[b].q || []).push(arguments)};
a[b].l = 1 * new Date;
c = e.createElement(f);
d = e.getElementsByTagName(f)[0];
c.async = 1;
c.src = g;
d.parentNode.insertBefore(c, d)
})(window, document, "script", "//www.google-analytics.com/analytics.js", "ga");
ga("create", "UA-XXXXX-Y", "auto");
ga("send", "pageview");
It's look like the same.
You can find more details on the project on Github repository.
Upvotes: 6
Reputation: 3140
It's actually fairly simple and a fun task to write such scripts.
Here is a long example how to turn a regular function into something like this:
I'd start with an imaginary script. I've included a scriptLoader which loads a javascript file asynchronously:
window.loadScript = function(src){
const scriptTag = document.createElement('script');
scriptTag.async = true;
scriptTag.src = src;
const anyOtherScriptTag = document.getElementsByTagName('script')[0];
anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
When called like this: loadScript("/url.js")
it will insert a new script tag (before the first script tag) in the DOM and the browser will download the script.
So far so good. Let's say I want to pass this script arguments before it has loaded. Inside the script that will be loaded I access a unique global object. Let's call it window.myScriptArgs
. So ideally, once the script has loaded it reads window.myScriptArgs and executes accordingly.
Now I could do window.myScriptArgs = []
and call it a day but since my hypothetical example will only load a single script file, I add the logic to the loadScript function as well.
window.loadScript = function(src){
window.myScriptArgs = window.myScriptArgs || [];
const scriptTag = document.createElement('script');
scriptTag.async = true;
scriptTag.src = src;
const anyOtherScriptTag = document.getElementsByTagName('script')[0];
anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
loadScript("/my-script.js");
Okay, so I check if myScriptArgs is already present and if not I set it to an empty array.
Now I also know that my-script.js
exposes a global myScript() method. So I write a stub for it. This stub will put every argument received to it into the myScriptArgs array:
window.myScript = () => {
window.myScriptArgs = window.myScriptArgs || [];
window.myScriptArgs.push(arguments);
}
Now I can call loadScript and immediately call myScript() with given arguments. No need to worry about loading issues or whatnot. Once "my-script.js" is loaded it reads window.myScriptArgs
and acts as excepted. The code looks like this:
window.myScript = () => {
window.myScriptArgs = window.myScriptArgs || [];
window.myScriptArgs.push(arguments);
}
window.loadScript = function(src){
window.myScriptArgs = window.myScriptArgs || [];
const scriptTag = document.createElement('script');
scriptTag.async = true;
scriptTag.src = src;
const anyOtherScriptTag = document.getElementsByTagName('script')[0];
anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
loadScript("/my-script.js");
myScript('command', 'args', 'args1');
myScript('command2', 'args3', 'args4');
Okay, that works as expected. Let's optimize it. First I combine the loadScript
and myScript
stub to a single function called initMyScript():
window.initMyScript = function(src){
window.myScriptArgs = window.myScriptArgs || [];
window.myScript = window.myScript || function(){
window.myScriptArgs.push(arguments);
}
const scriptTag = document.createElement('script');
scriptTag.async = true;
scriptTag.src = src;
const anyOtherScriptTag = document.getElementsByTagName('script')[0];
anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
initMyScript("/my-script.js");
myScript('command', 'args', 'args1');
myScript('command2', 'args3', 'args4');
It's nothing too fancy atm. Now I'm going to get rid of the multiple window.
calls by passing window
as an argument to initMyScript
. I'll also do this with document
.
The script looks like this:
window.initMyScript = function(p, a, src){
p.myScriptArgs = p.myScriptArgs || [];
p.myScript = p.myScript || function(){
p.myScriptArgs.push(arguments);
}
const scriptTag = a.createElement('script');
scriptTag.async = true;
scriptTag.src = src;
const anyOtherScriptTag = a.getElementsByTagName('script')[0];
anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
initMyScript(window, document, "/my-script.js");
Now let's see where I repeat myself to save some more bits. I use the string script
twice, same for myScript
:
window.initMyScript = function(p, a, s, c, src){
p.myScriptArgs = p.myScriptArgs || [];
p[c] = p[c] || function(){
p.myScriptArgs.push(arguments);
}
const scriptTag = a.createElement(s);
scriptTag.async = true;
scriptTag.src = src;
const anyOtherScriptTag = a.getElementsByTagName(s)[0];
anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag);
}
initMyScript(window, document, 'script', 'myScript', "/my-script.js");
Next step in my journey is to make the variables short. And I also turn this function into a self-executing function to save the window.initMyScript
definition:
(function(p, a, s, c, src){
p.myScriptArgs = p.myScriptArgs || [];
p[c] = p[c] || function(){
p.myScriptArgs.push(arguments);
}
const q = a.createElement(s);
q.async = true;
q.src = src;
const d = a.getElementsByTagName(s)[0];
d.parentNode.insertBefore(q, d);
})(window, document, 'script', 'myScript', "/my-script.js");
And to my last mystery: I edit the function parameters to confuse people and also minify the code even more. You can actually chain functions in javascript using commas ;).
(function(p, a, s, c, A, l, i){
p["myScriptArgs"]=p["myScriptArgs"]||[],p[c] = p[c]||function(){
p["myScriptArgs"].push(arguments)},
l = a.createElement(s);l.async = true;l[A] = A;
i = a.getElementsByTagName(s)[0];
i.parentNode.insertBefore(l, i);
})(window, document, 'script', 'myScript', "/my-script.js");
myScript("arg1", "arg2");
myScript("arg2", "arg3");
Do note that I add two extra parameters in the function, that's because I need to save the element returned by createElement
and don't want to use a var
statement ;).
You can take this even further, but you get the point. For small functions, you can do it yourself without a problem.
Furthermore, you can use a minifier like UglifyJS and then rename the variables yourself afterward if you are really into that whole isogram thing...
Note: I didn't test any of this code. Here be dragons. The imaginary code is my bad attempt at de-obfuscating googles example. The google-analytics snippet works almost the same as my custom snippet. GA optimizes a bit more (e.g. turning true into 1) but you get the point.
Read more about the things used in my example: Immediately Invoked Function Expression Property accessors (especially Bracket notation)
And javascript specific things like passing three arguments to a function which takes 5.
Upvotes: 3