JavaScript Day 11: Tools Part 2: Minification!

Mar 11 2011 Published by under JavaScript

Send to Kindle

I just like the word “minification”.

Anyway, here’s the deal. in ActionScript, and most other languages that go through some sort of compilation process, you write your code in source files and that human readable text gets distilled down into something else. In AS3, it’s byte code that runs in the Flash Player. In Java, it’s byte code that runs in the Java Virtual Machine. In .net languages it’s also similar, as things run on the CLR. Other forms of C or C++ will compile down into something closer to what the computer itself can understand.

With JavaScript, however, you put your files up on your website and the browser pulls them down and executes them. This means that every line of code, every character you write, comment, whitespace, long variable name, etc. has to be uploaded in full, and downloaded in full by every person who visits your site. This further explains a lot of the terse structure of much of the JavaScript you see.

This, of course, presents a problem. Good code, maintainable, readable code, needs to have some structure and some comments. If you’re writing an API, you need to comment it, ideally in the code, and have something that generates documents from those comments. Other, non-obvious points need to be documented as well. Whitespace is important for readability, as are well named functions and variables. But all of this makes bigger files.

Furthermore, while you might want (should want) your code to be understandable and maintainable by your team, you probably don’t care so much whether the end users of your site are able to read the code, and in fact in many cases, may prefer that it is UNreadable by them.

Computer people being smart guys and gals, someone came up with a solution called minification. The idea is to take your source files and run them through a program that makes them as small as possible, while still retaining the same functionality. You continue to code and upgrade and maintain the original source files, but only the minified versions go out on the web. It’s essentially the same workflow as compiling a release version of an app. You keep the original, uncompiled source, you send out the compiled product, once you are ready to ship.

So what does a minifier do? Well, obviously, it can remove any comments and unnecessary whitespace. It can also replace variable and function names with smaller names. If that’s all they did, you’d see some moderate decreases in size and readability. But some go way beyond that, creating files that you would bet money would not execute, and bear no resemblance to what you wrote.

There are probably multiple tools out there that do this, but here’s one for example: http://jscompress.com/

All you do is paste in your code, hit the button, and save the results. Let’s try it with the source of wirelib.js. I guess you are pretty familiar with what that looks like. At this writing, it’s 217 lines of code. Plenty readable. If I paste it in and hit the compress button with the default “Minify (JSMin)” option, I get this:

var wirelib=(function(){var canvas,context,width,height,lines=[],cx,cy,cz,fl=250,interval,running;function initWithCanvas(aCanvas){canvas=aCanvas;if(canvas!==undefined){width=canvas.width;height=canvas.height;cx=width/2;cy=height/2;cz=fl*2;context=canvas.getContext("2d");}}
function project(p3d){var p2d={},scale=fl/(fl+p3d.z+cz);p2d.x=cx+p3d.x*scale;p2d.y=cy+p3d.y*scale;return p2d;}
function addLine(){var i,numPoints,points,line;points=(typeof arguments[0]==="object")?arguments[0]:arguments;numPoints=points.length;if(numPoints>=6){line={style:this.strokeStyle,width:this.lineWidth,points:[]};lines.push(line);for(i=0;i<numPoints;i+=3){line.points.push({x:points[i],y:points[i+1],z:points[i+2]});}}else{console.error("wirelib.addLine: You need to add at least two 3d points (6 numbers) to make a line.");}
return line;}
function addBox(x,y,z,w,h,d){this.addLine(x-w/2,y-h/2,z-d/2,x+w/2,y-h/2,z-d/2,x+w/2,y+h/2,z-d/2,x-w/2,y+h/2,z-d/2,x-w/2,y-h/2,z-d/2);this.addLine(x-w/2,y-h/2,z+d/2,x+w/2,y-h/2,z+d/2,x+w/2,y+h/2,z+d/2,x-w/2,y+h/2,z+d/2,x-w/2,y-h/2,z+d/2);this.addLine(x-w/2,y-h/2,z-d/2,x-w/2,y-h/2,z+d/2);this.addLine(x+w/2,y-h/2,z-d/2,x+w/2,y-h/2,z+d/2);this.addLine(x+w/2,y+h/2,z-d/2,x+w/2,y+h/2,z+d/2);this.addLine(x-w/2,y+h/2,z-d/2,x-w/2,y+h/2,z+d/2);}
function addRect(x,y,z,w,h){this.addLine(x-w/2,y-h/2,z,x+w/2,y-h/2,z,x+w/2,y+h/2,z,x-w/2,y+h/2,z,x-w/2,y-h/2,z);}
function addCircle(x,y,z,radius,segments){var i,points=[],a;for(i=0;i<segments;i+=1){a=Math.PI*2*i/segments;points.push(x+Math.cos(a)*radius,y+Math.sin(a)*radius,z);}
points.push(points[0],points[1],points[2]);this.addLine(points);}
function draw(){var i,j,line,p2d;if(this.clearCanvas){context.clearRect(0,0,width,height);}
for(i=0;i<lines.length;i+=1){context.beginPath();line=lines[i];p2d=project(line.points[0]);context.moveTo(p2d.x,p2d.y);for(j=1;j<line.points.length;j+=1){p2d=project(line.points[j]);context.lineTo(p2d.x,p2d.y);}
context.lineWidth=line.width;context.strokeStyle=line.style;context.stroke();}
if(this.showCenter){p2d=project({x:0,y:0,z:0});context.strokeStyle="#ff0000";context.lineWidth=0.5;context.beginPath();context.arc(p2d.x,p2d.y,5,0,Math.PI*2,false);context.stroke();}}
function loop(fps,callback){if(!running){running=true;interval=setInterval(function(){callback();wirelib.draw();},1000/fps);}}
function stop(){running=false;clearInterval(interval);}
function rotateX(radians){var i,j,p,y1,z1,line,cos=Math.cos(radians),sin=Math.sin(radians);for(i=0;i<lines.length;i+=1){line=lines[i];for(j=0;j<line.points.length;j+=1){p=line.points[j];y1=p.y*cos-p.z*sin;z1=p.z*cos+p.y*sin;p.y=y1;p.z=z1;}}}
function rotateY(radians){var i,j,p,x1,z1,line,cos=Math.cos(radians),sin=Math.sin(radians);for(i=0;i<lines.length;i+=1){line=lines[i];for(j=0;j<line.points.length;j+=1){p=line.points[j];z1=p.z*cos-p.x*sin;x1=p.x*cos+p.z*sin;p.x=x1;p.z=z1;}}}
function rotateZ(radians){var i,j,p,x1,y1,line,cos=Math.cos(radians),sin=Math.sin(radians);for(i=0;i<lines.length;i+=1){line=lines[i];for(j=0;j<line.points.length;j+=1){p=line.points[j];y1=p.y*cos-p.x*sin;x1=p.x*cos+p.y*sin;p.x=x1;p.y=y1;}}}
function translate(x,y,z){var i,j,p,line;for(i=0;i<lines.length;i+=1){line=lines[i];for(j=0;j<line.points.length;j+=1){p=line.points[j];p.x+=x;p.y+=y;p.z+=z;}}}
function jitter(amount){var i,j,line,p;for(i=0;i<lines.length;i+=1){line=lines[i];for(j=0;j<line.points.length;j+=1){p=line.points[j];p.x+=Math.random()*amount*2-amount;p.y+=Math.random()*amount*2-amount;p.z+=Math.random()*amount*2-amount;}}}
function setCenter(x,y,z){cx=x===null?cx:x;cy=y===null?cy:y;cz=z===null?cz:z;}
return{initWithCanvas:initWithCanvas,addLine:addLine,addBox:addBox,addRect:addRect,addCircle:addCircle,draw:draw,rotateX:rotateX,rotateY:rotateY,rotateZ:rotateZ,translate:translate,jitter:jitter,clearCanvas:true,strokeStyle:"#000000",lineWidth:1,loop:loop,stop:stop,showCenter:false,setCenter:setCenter};}());

As you can see, that’s pretty conservative. It killed the whitespace and put as much on a single line as it could. It’s definitely more compact, but not hugely so. But let’s try with the other option, “Packer (Dean Edwards)”:

eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('o U=(8(){o t,b,r,E,c=[],C,B,D,J=1w,P,H;8 12(1h){t=1h;F(t!==1v){r=t.r;E=t.E;C=r/2;B=E/2;D=J*2;b=t.1u("1t")}}8 I(L){o e={},14=J/(J+L.z+D);e.x=C+L.x*14;e.y=B+L.y*14;16 e}8 m(){o i,K,7,4;7=(1s Y[0]==="1y")?Y[0]:Y;K=7.g;F(K>=6){4={1d:n.O,r:n.N,7:[]};c.G(4);9(i=0;i<K;i+=3){4.7.G({x:7[i],y:7[i+1],z:7[i+2]})}}1B{1A.1z("U.m: 1C 1m 1f 1p 1n 1o 1q 1r 7 (6 1x) 1f 1M a 4.")}16 4}8 1a(x,y,z,w,h,d){n.m(x-w/2,y-h/ 2, z - d /2,x+w/2,y-h/ 2, z - d /2,x+w/2,y+h/ 2, z - d /2,x-w/2,y+h/ 2, z - d /2,x-w/2,y-h/ 2, z - d /2);n.m(x-w/2,y-h/ 2, z + d /2,x+w/2,y-h/ 2, z + d /2,x+w/2,y+h/ 2, z + d /2,x-w/2,y+h/ 2, z + d /2,x-w/2,y-h/ 2, z + d /2);n.m(x-w/2,y-h/ 2, z - d /2,x-w/2,y-h/ 2, z + d /2);n.m(x+w/2,y-h/ 2, z - d /2,x+w/2,y-h/ 2, z + d /2);n.m(x+w/2,y+h/ 2, z - d /2,x+w/2,y+h/ 2, z + d /2);n.m(x-w/2,y+h/ 2, z - d /2,x-w/2,y+h/ 2, z + d /2)}8 13(x,y,z,w,h){n.m(x-w/2,y-h/2,z,x+w/2,y-h/2,z,x+w/2,y+h/2,z,x-w/2,y+h/2,z,x-w/2,y-h/2,z)}8 18(x,y,z,V,10){o i,7=[],a;9(i=0;i<10;i+=1){a=k.1e*2*i/10;7.G(x+k.l(a)*V,y+k.f(a)*V,z)}7.G(7[0],7[1],7[2]);n.m(7)}8 M(){o i,j,4,e;F(n.1i){b.1J(0,0,r,E)}9(i=0;i<c.g;i+=1){b.1b();4=c[i];e=I(4.7[0]);b.1K(e.x,e.y);9(j=1;j<4.7.g;j+=1){e=I(4.7[j]);b.1F(e.x,e.y)}b.N=4.r;b.O=4.1d;b.1g()}F(n.1c){e=I({x:0,y:0,z:0});b.O="#1E";b.N=0.5;b.1b();b.1G(e.x,e.y,5,0,k.1e*2,R);b.1g()}}8 Z(1j,1l){F(!H){H=1k;P=1H(8(){1l();U.M()},1D/1j)}}8 W(){H=R;1I(P)}8 17(q){o i,j,p,u,A,4,l=k.l(q),f=k.f(q);9(i=0;i<c.g;i+=1){4=c[i];9(j=0;j<4.7.g;j+=1){p=4.7[j];u=p.y*l-p.z*f;A=p.z*l+p.y*f;p.y=u;p.z=A}}}8 15(q){o i,j,p,v,A,4,l=k.l(q),f=k.f(q);9(i=0;i<c.g;i+=1){4=c[i];9(j=0;j<4.7.g;j+=1){p=4.7[j];A=p.z*l-p.x*f;v=p.x*l+p.z*f;p.x=v;p.z=A}}}8 X(q){o i,j,p,v,u,4,l=k.l(q),f=k.f(q);9(i=0;i<c.g;i+=1){4=c[i];9(j=0;j<4.7.g;j+=1){p=4.7[j];u=p.y*l-p.x*f;v=p.x*l+p.y*f;p.x=v;p.y=u}}}8 S(x,y,z){o i,j,p,4;9(i=0;i<c.g;i+=1){4=c[i];9(j=0;j<4.7.g;j+=1){p=4.7[j];p.x+=x;p.y+=y;p.z+=z}}}8 Q(s){o i,j,4,p;9(i=0;i<c.g;i+=1){4=c[i];9(j=0;j<4.7.g;j+=1){p=4.7[j];p.x+=k.19()*s*2-s;p.y+=k.19()*s*2-s;p.z+=k.19()*s*2-s}}}8 T(x,y,z){C=x===11?C:x;B=y===11?B:y;D=z===11?D:z}16{12:12,m:m,1a:1a,13:13,18:18,M:M,17:17,15:15,X:X,S:S,Q:Q,1i:1k,O:"#1L",N:1,Z:Z,W:W,1c:R,T:T}}());',62,111,'||||line|||points|function|for||context|lines||p2d|sin|length||||Math|cos|addLine|this|var||radians|width|amount|canvas|y1|x1|||||z1|cy|cx|cz|height|if|push|running|project|fl|numPoints|p3d|draw|lineWidth|strokeStyle|interval|jitter|false|translate|setCenter|wirelib|radius|stop|rotateZ|arguments|loop|segments|null|initWithCanvas|addRect|scale|rotateY|return|rotateX|addCircle|random|addBox|beginPath|showCenter|style|PI|to|stroke|aCanvas|clearCanvas|fps|true|callback|need|at|least|add|two|3d|typeof|2d|getContext|undefined|250|numbers|object|error|console|else|You|1000|ff0000|lineTo|arc|setInterval|clearInterval|clearRect|moveTo|000000|make'.split('|'),0,{}))

Whoa! First of all, it squished it all down into a single line. Sorry this means you have to scroll. But as you scroll through, there’s virtually nothing that makes any sense at all or looks anything at all like the original file until you get to the very end, where you basically see a list of method and var names. Amazingly, it still works just the same as the original.

In terms of raw size, the original was 7,184 bytes.

The minified version came in at 3,879 bytes.

And the compressed was 3,143 bytes.

So the minified reduced file size by 46% and the compressed by 56%. The compression also offers a good deal of obfuscation. I imagine there are ways to reverse it like anything else, but it’s better than putting your code out there in plain text.

Generally, you’d probably want to name your minified file to show that is is minified. Like, wirelib.min.js.

There are probably other similar tools out there. I haven’t investigated them in depth, but wanted to put that out there.

Send to Kindle

8 responses so far. Comments will be closed after post is one year old.