A Rudimentary JavaScript Minifier

The code below implements a rudimentary JavaScript minifier. A “minifier” takes a code listing and removes unnecessary space in it, primarily by renaming functions and variables using shorter names. Minifying JavaScript allows it to be transported and compiled more efficiently.

This implementation is fairly primitive. It’s unlikely to work for code that references more built-in functions and objects, or that declares more that about 52 variables or functions. Like many of the programs on this site, it is intended mostly as a basis for further work.

To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript.


<html>
<body>

<div id="divUI">

	<label><b>JavaScript Minifier</b></label>
	
	<p>Enter JavaScript code in the textbox and click the button to minify it.</p>

	<label>Keywords to Ignore:</label><br />
	<textarea id="textareaKeywordsToIgnore" cols="80" rows="8">
alert
function
var
return
	</textarea><br />

	<label>Code to Minify:</label><br />
	<textarea id="textareaCodeToMinify" cols="80" rows="16">
// This is a test!

function add(addend0, addend1)
{
	var result = addend0 + addend1;
	return result;
}

var addend0 = 1; // test comment
var addend1 = 2;

var sum = add(addend0, addend1);

alert("The sum of " + addend0 + " and " + addend1 + " is " + sum);

	</textarea><br />

	<button onclick="buttonMinify_Clicked();">Minify</button><br />

	<label>Code after Minification:</label><br />
	<textarea id="textareaCodeMinified" cols="80" rows="8"></textarea><br />

</div>

<script type="text/javascript">

// ui event handlers

function buttonMinify_Clicked()
{
	var textareaKeywordsToIgnore = 
		document.getElementById("textareaKeywordsToIgnore");

	var keywordsToIgnoreAsString = 
		textareaKeywordsToIgnore.value;
	var keywordsToIgnore = 
		keywordsToIgnoreAsString.split("\n"); 

	var textareaCodeToMinify = 
		document.getElementById("textareaCodeToMinify");

	var codeToMinify = textareaCodeToMinify.value;

	var minifier = new Minifier(keywordsToIgnore);

	var codeMinified = minifier.minifyCode(codeToMinify);

	var textareaCodeMinified = 
		document.getElementById("textareaCodeMinified");

	textareaCodeMinified.value = codeMinified;
}

// classes

function Minifier(keywords)
{
	this.keywords = keywords;

	var identifiersMinifiedAsString = 
		"a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y;z;"
		+ "A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z";

	this.identifiersMinified = 
		identifiersMinifiedAsString.split(";");
}
{
	Minifier.prototype.minifyCode = function(codeToMinify)
	{
		var codeAsLines = codeToMinify.split("\n");
		var codeAsLinesMinusComments = [];

		for (var i = 0; i < codeAsLines.length; i++)
		{
			var codeLine = codeAsLines[i];
			var indexOfDoubleSlash = codeLine.indexOf("//");

			var codeLineMinusComments;

			if (indexOfDoubleSlash == -1)
			{
				codeLineMinusComments = codeLine;
			}
			else
			{
				codeLineMinusComments = 
					codeLine.substr(0, indexOfDoubleSlash);
			}

			codeAsLinesMinusComments.push
			(
				codeLineMinusComments
			);

		}

		var codeMinusComments = codeAsLinesMinusComments.join("\n");

		var tokenizer = new Tokenizer();

		var codeAsTokens = 
			tokenizer.tokenizeString(codeMinusComments);

		var identifierLookup = [];

		for (var k = 0; k < this.keywords.length; k++)
		{
			var keyword = this.keywords[k];
			identifierLookup[keyword] = keyword + " ";	
		}

		var codeMinifiedAsTokens = [];

		for (var t = 0; t < codeAsTokens.length; t++)
		{
			var tokenToMinify = codeAsTokens[t];

			var tokenMinified;
		
			if (tokenToMinify.isIdentifier() == false)
			{
				tokenMinified = tokenToMinify;
			}
			else
			{
				tokenMinified = identifierLookup[tokenToMinify];

				if (tokenMinified == null)
				{
					var numberOfIdentifiersSoFar = 
						identifierLookup.length;

					tokenMinified = 
						this.identifiersMinified[numberOfIdentifiersSoFar];
	
					identifierLookup[tokenToMinify] = tokenMinified;
	 				identifierLookup.push(tokenToMinify);
				}
			}

			codeMinifiedAsTokens.push(tokenMinified);
		}

		var codeMinified = codeMinifiedAsTokens.join(""); // todo

		return codeMinified;
	}
}

function StringHelper()
{
	// do nothing
}
{
	String.prototype.isLetter = function()
	{
		var charCode = this.charCodeAt(0);
		var returnValue = 
		(
			(
				charCode >= "A".charCodeAt(0) 
				&& charCode <= "Z".charCodeAt(0) 
			)
			||
			(
				charCode >= "a".charCodeAt(0) 
				&& charCode <= "z".charCodeAt(0) 
			)

		);

		return returnValue;
	}

	String.prototype.isIdentifier = function()
	{
		var returnValue = 
		( 
			this.length >= 1
			&& isNaN( parseFloat(this) ) 
			&& this[0].isLetter()
		);

		return returnValue;
	}

	String.prototype.isNumber = function()
	{
		var returnValue = 
		(
			isNaN(parseFloat(this)) == false
			|| this == "."
		)

		return returnValue;
	}

	String.prototype.isWhitespace = function()
	{
		var returnValue = true;

		for (var i = 0; i < this.length; i++)
		{
			var char = this[i];
			if 
			(
				char != " "
				&& char != "\t"
				&& char != "\r"
				&& char != "\n"
			)
			{
				returnValue = false;
				break;
			}
		}

		return returnValue;
	}
}

function Tokenizer()
{
	// do nothing
}
{
	Tokenizer.prototype.tokenizeString = function(stringToTokenize)
	{
		var tokensSoFar = [];

		var tokenInProgress = "";

		var isWithinQuotes;

		for (var i = 0; i < stringToTokenize.length; i++)
		{
			var char = stringToTokenize[i];
			
			if (isWithinQuotes == true)
			{
				tokenInProgress += char;

				if (char == "\"")
				{
					isWithinQuotes = false;
					tokensSoFar.push(tokenInProgress);
					tokenInProgress = "";
				}
				else if (char == "\\")
				{
					i++;
					var charNext = stringToTokenize[i];
					tokenInProgress += charNext;
				}
			}
			else if (char.isWhitespace())
			{
				if (tokenInProgress.length > 1)
				{
					tokensSoFar.push(tokenInProgress);
					tokenInProgress = "";
				}
			}
			else if (char.isLetter() || char.isNumber())
			{
				tokenInProgress += char;	
			}
			else if (char == "\"")
			{
				isWithinQuotes = true;
				tokenInProgress += char;
			}
			else
			{
				tokensSoFar.push(tokenInProgress);
				tokensSoFar.push(char);
				tokenInProgress = "";
			}
		}		

		return tokensSoFar;
	}
}

</script>

</body>
</html>

Advertisements
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s