Home / AJAX

Default JavaScript Minification

RSS
Modified on 2009/12/14 17:58 by Stephen Walther Categorized as Uncategorized

Default JavaScript Minification

Microsoft Ajax Minifier minifies script by parsing the source code into a JavaScript syntax parse tree using code based on the ROTOR sources published by Microsoft. Once the original sources have been parsed, the tree is manipulated, and then walked to output the minimum amount of JavaScript code required to reproduce a comparable parse tree. Microsoft Ajax Minifier does not alter the source file. Default minification will:

  • Remove unnecessary white space.
  • Remove comments (except for statement-level “important” comments).
  • Remove unnecessary semicolons.
  • Remove curly-braces around most single-statement blocks.
  • Rename local variables and function.
  • Determine best string delimiters (single- or double-quotes) based on which option will generate the fewer escaped characters within the string.
  • Combine multiple adjacent variable declarations.
  • Remove empty parameter lists on constructors.
  • Remove unreferenced names for named function expressions.
  • Remove unreferenced local functions.
  • Remove many instances of unreachable code.

Whitespace and comment removal is pretty well-understood. There is nothing in the JavaScript specifications that requires the code to live on multiple lines; Microsoft Ajax Minifier outputs the entire resulting source into a single line, removes spaces between operators, and leaves spaces only where necessary to delimit keywords and identifiers. Comments are removed unless they are “important” statement-level comments. Important comments are multi-line comments in the format /*! … */ (the exclamation mark must be immediately following the opening delimiter’s asterisk, with no white-space between them). Use the important-comment construct to add license or copyright notices to the top of your script and the comment will be sent to the minified output.

Because semicolons are delimiters, not terminators, extra semicolons are usually removed from the minified output. The last statement in a statement block does not need a semicolon, and the extra byte can be saved. This block:

if ( a == 0 )
{
    a = 10;
    alert(b/a);
}

Reduces to:

if(a==0){a=10;alert(b/a)}

There are exceptions to this rule. For example, Safari will throw an error if a throw statement is not terminated with a semi-colon, so one will be present in those cases, even if the statement is the last statement in a block. If this behavior is not desired, you can turn it off with the –MAC command-line option, specifying a false value.

Removal of unnecessary curly braces is equally simple. If a statement that normally expects a block of code (like if or for or while) only has a single statement within that block, the curly-braces are removed.

if ( a == 0 )
{
    a = 10;
}

Reduces to:

if(a==0)a=10;

There are also exceptions to the curly-brace-single-statement rule. If an if statement contains an else block and the true block contains a single statement, that true block statement will be enclosed in curly braces if it ends with an if statement without an else block. Without the curly-braces, the else in the outer if would instead get parsed with the inner if statement. This code:

if ( a == 0 ) 
{
    for(var o in opts)
    {
        if ( o > 0 )
        {
            a += o;
        }
    }
}
else
{
    b = c / a;
}

Gets changed to:

if(a==0){for(var o in opts)if(o>0)a+=o}else b=c/a

Another exception is when a block contains a function declaration. Strictly speaking, according to the ECMA spec, function declarations are not allowed in the statement blocks of statements such as if, while, or for. However, browsers will parse those declarations in those spots. Safari will throw an error if the block is a single function declaration not enclosed in curly-braces, so curly-braces will always be added in those circumstances (unless the –MAC switch is specified with a false value).

In JavaScript, string literals can be delimited with single- or double-quotes. If a string is delimited with single-quotes, all instances of single-quotes within the string must be escaped with a backslash. Same goes for double-quotes. No matter what the programmer escapes the source string with, the output string will be delimited with whichever character produced the fewest escaped characters within the string, thereby reducing the number of bytes. The string:

var g = "what's his \"name\"?"; 

Gets automatically minified to:

var g='what\'s his "name"?' 

Because double-quote delimiters requires two escapes, but single-quote delimiters only require one.

Well-written code defines each variable on a separate line. This allows for maximum maintainability and readability. However, this means multiple var statements, which means more bytes downloaded. Microsoft Ajax Minifier combines multiple adjacent var statements into a single comma-delimited var statement:

var a = 0;
var b = "some string";
var c = 3.14;

Gets reduced to:

var a=0,b="some string",c=3.14;

The object of a new operator is a function – either the name of a declared function, or a function expression. The new operator creates a new object, sets the this pointer to that object, then calls the function. The parameter list is actually optional; if the function is not passed any parameters, it is not necessary to add an empty set of parenthesis after it. So for instance:

var img = new Image();

becomes:

var img = new Image;

Unless the –NEW:KEEP option is specified, explicit calls to the Object and Array constructors are converted to object and array literals. For example, this code:

var obj = new Object();
var arr = new Array();
var lst = new Array(1, 2, 3);

Gets reduced to:

var obj={},arr=[],lst=[1,2,3];

The exceptions here are when the Object constructor is passed any parameters, or when the Array constructor is passed a single parameter that may be numeric (this indicates an array size, not an element list).

Because JavaScript only has function-level variable scopes, nested blocks do not add anything to the semantics of a function and therefore will be removed from the output. For example:

function foo(p)
{
	var f = 10;
	{
		var g = 0;
		f = p * g;
	}
}

Gets converted to:

function foo(a){var f=10,g=0;f=p*g}

If an if statement contains an empty true-block and a non-empty false-block, the condition will be not-ed and the false-block moved to the true-block:

if (a >= b) {} else { alert("a!=b") }
if (foo.bar()) {} else { alert("not foo.bar()") }
if (!a) {} else { alert("a") }

gets converted to:

if(a<b)alert("a!=b");
if(!foo.bar())alert("not foo.bar()");
if(a)alert("a");

Although it may seem like a silly bit of code to write, sometimes the author of the source script will actually have statements in the true-block that get removed by AjaxMin; for instance, if the statements are debug-only statements.

If there are var statements immediately preceding a for statement, there are several situations where the statements will be combined, depending on the structure of the for statement’s initializer component. If the initializer is empty, the var statement will simply be moved into the for statement’s initializer:

var i = 5;
for (; i > 0; --i)
{
    alert(i);
} 

will be converted to:

for(var i=5;i>0;--i)alert(i)

If the for statement’s initializer is already using the var construct, the var statement and the initializer will be combined:

var n = 10;
for (var i=5; i > 0; --i)
{
    n *= i;
}


will be converted to:

for(var n=10,i=5;i>0;--i)n*=i

If the for statement’s initializer is a simple assignment to a variable, and that variable is in an immediately-preceding var statement, the var statement and the initializer will again be combined:

var n=10, i;
for (i = 5; i > 0; --i)
{
    n *= i;
}

will be converted to:

for(var n=10,i=5;i>0;--i)n*=i

If the for statement’s initializer does not use the var construct, and uses the comma operator to make multiple assignments, no preceding var statements will be combined into the for statement’s initializer. For example:

var i;
for (i = 5, n = 10; i > 0; --i)
{
    n *= i;
} 

will become:

var i;for(i=5,n=10;i > 0;--i)n*=i

because the n variable being assigned to is not part of the preceding var statement, and could possibly be a different context if placed into a var construct.

It would be possible to move the var statement into the for statement if the preceding var statement(s) contained both variables, but for now Microsoft Ajax Minifier will simply not perform the combination logic if the for statement’s initializer uses the comma operator at all outside the var construct.

Another scenario involves a common construct with if statements. In analyzing various pieces of code, a pattern emerged. In order to call a method on an object that might or might not exist, code similar to this was being written:

if (obj.method)
{
    obj.method();
}

The pattern is an if statement with a single statement in the true-block, which is a call statement. This pattern can be reduced to:

obj.method&&obj.method()

This works because the and-operator shortcuts; if the first expression does not evaluate to true, the second expression is not even executed. Technically, the single statement within the true-block simply needs to be an expression statement in order for this pattern to work, however, the other typical construct is a single assignment expression within the true-block:

if (obj.prop)
{
    i += obj.prop;
}

Were Microsoft Ajax Minifier to apply the same reduction pattern to this code, it would not gain anything because the assignment operator expression would require parentheses around it in order for the operator precedence to remain valid:

obj.prop&&(i+=obj.prop)

In that case, it would not be saving any bytes at all over the standard behavior:

if(obj.prop)i+=obj.prop

Therefore the pattern Microsoft Ajax Minifier employs will perform the transition from the if statement to an and-operator only if the single statement within the true-block is a method call expression.