Minify Javascript with Python

The code below compresses and minifies javascript files. Minification is not the same as obfuscation. Minification removes all whitespace , comments and needless characters so that the end result is much smaller and is more difficult to read . Obfuscation does minification as well as mangles variables and functions so that the code is completely unreadable.

This code uses Python 2.7 so the new features in Python 3.0 are not used here eg. file.peek() . The Python class takes in a javascript file and minifies and writes it out to another file.
The class can be called in a single line of code :

 Minify("test.js");

The modified file will be saved as test.js.min

#
# Minify javascript file
# (C) Amit Sengupta, Sep 2015
# Written in Python 2.7



class Minify():

    EOF = -1

    # constructor
    # params: f -f filepath
    #
    def __init__(self, f):
	
	if f == '':
	    print "No file specified"
	    return
	    
	self.filename = f		# file name
	self.original_data = ""		# original file data
	self.modified_data = ""		# data after processing
	self.is_error = False		# becomes true when error occurs
	self.error_msg = "";		# error message

	try:
	    # open file and read and print original contents
	    self.file_handle = open(self.filename, "r")
	    self.original_data = self.file_handle.read()
	    self.file_handle.close()
	    print self.original_data

	    
	    # process file
	    self.file_handle = open(self.filename, "rb")
	    self.doProcess()
	    self.file_handle.close()

	    #write modified data
	    outfile= self.filename + ".min"
	    handle = open(outfile, "w")
	    handle.write(self.modified_data)
	    handle.close()
	    



	except IOError as e:
	    self.is_error = True
	    self.error_msg = "Error occured:", e.strerror


    # main process 
    def doProcess(self):
	last_char = -1			    # previous byte read
	this_char = -1			    # current byte read
	next_char = -1			    # byte read in peek
	end_process = False		    # terminate flag
	ignore = False			    # if false then add byte to final output
	in_comment = False		    # true when current byte is part of a comment
	is_double_slash_comment = False	    # true when current comment is //


	while (end_process == False):
	    end_process = self.peek() == Minify.EOF 
	    if end_process:
		break
	    ignore = False
	    self.this_char = self.file_handle.read(1)

	    if self.this_char == '\t':
		self.this_char = ' '
	    elif self.this_char == '\r':
		self.this_char =- '\n'
	    if self.this_char == '\n':
		ignore = True

	    if self.this_char == ' ':
		if self.last_char == ' ' or self.isDelimiter(self.last_char) == 1:
		    ignore = True
		else:
		     end_process = self.peek() == Minify.EOF 
		     if not end_process:
			self.next_char = self.peek()
			if self.isDelimiter(self.next_char) == 1:
			    ignore = True
			    

	    if self.this_char == '/':
		self.next_char = self.peek()
		if self.next_char == '/' or self.next_char == '*':
		    ignore = True
		    in_comment = True
		    if self.next_char == '/':
			is_double_slash_comment = True
		    else:
			is_double_slash_comment = False



	    if in_comment == True:
		while (1):
		    self.this_char = self.file_handle.read(1)
		    if self.this_char == '*':
			self.next_char = self.peek()
			if self.next_char == '/':
			    self.this_char = self.file_handle.read(1)
			    in_comment = False
			    break;
	    		
		    if is_double_slash_comment == True and self.this_char == '\n':
			in_comment = False;
			break;
			
		ignore = True
		

	    if not ignore:
		self.addToOutput(self.this_char)
	
	    self.last_char = self.this_char
	    

    
    #
    # add byte to modified data
    # 
    def addToOutput(self, c):
	self.modified_data += str(c);

	
    #
    # python 2.x does not support file.peek so make our own
    #
    def peek(self):
	b = self.file_handle.read(1)
	self.file_handle.seek(-1, 1)

	if not b:
	    return Minify.EOF
	else:
	    return b
	
    #
    #check if a byte is a delimiter
    #
    def isDelimiter(self, c):
	retval = 0
	if c == '(' or c == ',' or c == '=' or c == ':' or \
           c == '[' or c == '!' or c == '&' or c == '|' or \
           c == '?' or c == '+' or c == '-' or c == '~' or \
	   c == '*' or c == '/' or c == '{' or c == '\n' or c == ',':

	   retval = 1
	   
	return retval


An example is given below:
Original File:

/* jshint define: false */

/**
 * @file This plugin adds on primitive Object (like string, number, array ...) additionnals methods
 * @version 1.0
 * @author Julien Roche
 * @copyright MIT
 */

(function(){
	"use strict";

	function definition($){
		/* String part */
		String.prototype.endWith = function (needle) { 
			return this && this.match(needle + "$") == needle;
		};
		
        String.prototype.repeat = function (num) { 
//			return new Array(num + 1).join(this);
			var arr = [];
			arr.length = num + 1;
			return arr.join(this);
		};
		
        String.prototype.startWith = function (needle) {
			return this && this.match("^" + needle) == needle;
		};
		
		/* Number part */
		Number.prototype.toPaddedString = function (length, radix) {
			var string = this.toString(radix || 10), slength = string.length;
			for (var i = 0; i < (length - slength); i++) {
				string = "0" + string;
			} 
			return string;
		};
		
		/* Array part */
		
		// See http://www.to-string.com/2012/05/29/fixing-splice-in-older-versions-of-internet-explorer-8-and-olders/
		if (document.documentMode && document.documentMode < 9) {
			// save original function of splice
			var originalSplice = Array.prototype.splice;
			
			// provide a new implementation
			Array.prototype.splice = function() {
				
				// since we can't modify 'arguments' array, 
				// let's create a new one and copy all elements of 'arguments' into it
				var arr = [],
					i = 0,
					max = arguments.length;
				
				for (; i < max; i++){
					arr.push(arguments[i]);
				}
				
				// if this function had only one argument
				// compute 'deleteCount' and push it into arr
				if (arr.length==1) {
					arr.push(this.length - arr[0]);
				}
				
				// invoke original splice() with our new arguments array
				return originalSplice.apply(this, arr);
			};
		}
		
		// See https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
		if(!Array.prototype.forEach) {
			Array.prototype.forEach = function forEach(callback, thisArg) {
				var T, k;

				if(this == null) {
					throw new TypeError("this is null or not defined");
				}

				// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
				var O = Object(this);

				// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
				// 3. Let len be ToUint32(lenValue).
				var len = O.length >>> 0;
				// Hack to convert O.length to a UInt32

				// 4. If IsCallable(callback) is false, throw a TypeError exception.
				// See: http://es5.github.com/#x9.11
				if( {}.toString.call(callback) !== "[object Function]") {
					throw new TypeError(callback + " is not a function");
				}

				// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
				if(thisArg) {
					T = thisArg;
				}

				// 6. Let k be 0
				k = 0;

				// 7. Repeat, while k < len
				while(k < len) {

					var kValue;

					// a. Let Pk be ToString(k).
					//   This is implicit for LHS operands of the in operator
					// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
					//   This step can be combined with c
					// c. If kPresent is true, then
					if(Object.prototype.hasOwnProperty.call(O, k)) {

						// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
						kValue = O[k];

						// ii. Call the Call internal method of callback with T as the this value and
						// argument list containing kValue, k, and O.
						callback.call(T, kValue, k, O);
					}
					// d. Increase k by 1.
					k++;
				}
				// 8. return undefined
			};
		}
	}

	if (typeof module === "object" && typeof module.exports === "object") {
		// Node approach
		definition();

	} else if (typeof define === "function" && define.amd) {
		// AMD approach
		define("prototype", [], definition);

	} else if (window.jQuery) {
		// Classical way
		definition();
	}
}());

Minified File:

(function(){"use strict";function definition($){String.prototype.endWith=function(needle){return this&&this.match(needle+"$")==needle;};String.prototype.repeat=function(num){var arr=[];arr.length=num+1;return arr.join(this);};String.prototype.startWith=function(needle){return this&&this.match("^"+needle)==needle;};Number.prototype.toPaddedString=function(length,radix){var string=this.toString(radix||10),slength=string.length;for(var i=0; i <(length-slength); i++){string="0"+string;} return string;};if(document.documentMode&&document.documentMode < 9){var originalSplice=Array.prototype.splice;Array.prototype.splice=function(){var arr=[],i=0,max=arguments.length;for(; i < max; i++){arr.push(arguments[i]);}if(arr.length==1){arr.push(this.length-arr[0]);}return originalSplice.apply(this,arr);};}if(!Array.prototype.forEach){Array.prototype.forEach=function forEach(callback,thisArg){var T,k;if(this==null){throw new TypeError("this is null or not defined");}var O=Object(this);var len=O.length >>> 0;if({}.toString.call(callback)!=="[object Function]"){throw new TypeError(callback+" is not a function");}if(thisArg){T=thisArg;}k=0;while(k < len){var kValue;if(Object.prototype.hasOwnProperty.call(O,k)){kValue=O[k];callback.call(T,kValue,k,O);}k++;}};}}if(typeof module==="object"&&typeof module.exports==="object"){definition();} else if(typeof define==="function"&&define.amd){define("prototype",[],definition);} else if(window.jQuery){definition();}}());

Be the first to comment

Leave a Reply

Your email address will not be published.


*