Testing for unused functions with Grunt

I’m relatively new to Grunt. I had used it before in several projects but it was already set up and working fine, to I didn’t really look into it. Besides, the sheer amount of options/plugins available put me off a bit. That and time being money.

But in between projects I have a bit of breathing space so the last couple of days I’ve been getting my hands really dirty.

I’ve used Ant before, but usually I just write some .bat files to run tasks. Although that always bugged me a bit. I mean DOS, come on… I used that thirty years ago when I first touched Windows and I’m still using it! But not any more (well a lot less anyway).
Grunt and Node are my new best friends.

So anyway, after Grunting a dummy project I fixed it for TinySort. In the lib folder of that project I found jquery.zen.min.js, a script I quickly threw together in the beginning of this year. So I decided to turn that into a new project (removing the jQuery depencency).

My Zen script is for using Emmet (formerly know as Zen) in your regular browser Javascripts. Emmet, although written in Javascript, is really intended for IDE plugins, and does much more than just expand HTML abbreviations. Which finally brings us to the title of this post.

Even though I just included a subset of the scripts I still ended up with 44kB in my minified result. The subset I used was still about 5000 lines but a lot of it was redundant.
So I needed a way to find out what functions were not used and remove them. And I had to do it automatically so I wouldn’t have any problems updating to a newer Emmet version (also, I had no intention of sifting through those 5000 lines by hand).

Finding the unused functions was tricky, but relatively simple. I injected a method call to each function telling me the line number it was called from. Then I wrote a test suite using every possible Emmet call that the script could encounter. This told me 274 out of 433 functions were not used.

So now what?

Finding unused functions is one thing, removing them is quite another. We’re dealing with named and anonymous functions, plus the named ones can be either expressions or declarations (function foo(){} -or- var foo = function(){};). I thought about prepending a dummy function and replacing all the unused functions with the dummy one. But that would not work for declarations because they could be referenced elsewhere.

Then there was the problem of selecting the entire function for removal. A simple regex wouldn’t cut it because you could can have nested functions, statements, comments and all those can have curly braces. I knew where the function started, so the only thing I could think of was meticulously step through every subsequent character and count up and down for every valid curly brace until I’d reach zero.

Then I had a brainfart: since it’s only the minified version that really counts, why not start every redundant function with ‘return false;’ and let Uglify do the heavy lifting? A few minutes later I had gone from 44kB to 26kB.
It left the file littered with occurrences of ‘function(a,b){return!1}’. Replacing those expressions with a dummy shaved off another 3kB.

So there you have it: a 50% decrease in filesize using code injection and Uglify.

The script is called ‘unused-functions’ and is up at NPM and Github.

Tagged , , , ,

Post a Comment

*
*

A freelance developer’s tool box

As a freelancer it’s always a challenge to find the right tools for the job. No collective knowledge base of colleagues you can quickly pop a question to. On the other hand, being a freelancer also gives you the unique opportunity to have a peek in the kitchen of various companies. And I’m always curious as to what tools are used, not only software or IDE’s but also frameworks, libraries etc…

more

Tagged , , , ,

Bookmarklet for fullscreen Google Maps

gm2

Here’s a bookmarklet that strips everything on Google Maps but the map itself. Put the browser fullscreen and you’ve got the whole world to printscreen desktop images from. Siberia has some pretty awesome landscapes.

more

Migrating a subversion repository from Google Code to Github

Recently I migrated TinySort from Google Code to Github. I’m a real Git noob so I expected a full history migration to be a real pain in the ass. Plus I also wanted to move both the open and closed issues (since they correspond to the regression tests). Luckily it turned out to be a lot easier than I anticipated.

more

Tagged , , , , , ,

Test Rainbow line numbering

function mediaSendToEditor($html, $attachment_id, $attachment) {
	// check for the GET vars added in boxView
	parse_str($_POST['_wp_http_referer'], $aPostVars);
	if (isset($aPostVars['target'])&&$aPostVars['target']=='foobarbaz') {
		// add extra data to the $attachement array prior to returning the json_encoded string
		$attachment['id'] = $attachment_id;
		$attachment['edit'] = get_edit_post_link($attachment_id);
		$attachment['input'] = $aPostVars['input'];
		$attachment['preview'] = $aPostVars['preview'];
		if ($aPostVars['type']=='image') {
			foreach (array('thumb','medium','large') as $size) {
				$aImg = wp_get_attachment_image_src( $attachment_id, $size);
				if ($aImg) $attachment['img_'.$size] = $aImg[0];
			}
		}
		$html = json_encode($attachment);
	}
	return $html;
}
(function($){
	var fnOldSendToEditor;
	$(function(){
		// store original send_to_editor function
		fnOldSendToEditor = window.send_to_editor;
		// add a different send_to_editor function to each thickbox anchor
		var $Box = $('#foobarbaz-box');
		if ($Box.length) {
			$Box.find('a.thickbox').each(function(i,el){
				var $A = $(el).click(function(){
					window.send_to_editor = getFnSendToEditor($A.data('type'));
				});
			});
		}
		// hack tb_remove to reset window.send_to_editor
		var fnTmp = window.tb_remove;
		window.tb_remove = function(){
			if (fnOldSendToEditor) window.send_to_editor = fnOldSendToEditor;
			fnTmp();
		};
	});
	function getFnSendToEditor(type){
		return function(fileHTML){
			var oData = JSON.parse(fileHTML);
			//console.log(oData);
			$('#'+oData.input).val(oData.url);
			$('#'+oData.preview).text(type+': '+oData.url.split('/').pop());
			tb_remove();
		}
	}
})(jQuery);

more