Minify Javascript with C#

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.

The C# 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 js = new Minify(@"c:\myfiles\test.js");

The modified file will be saved as c:\myfiles\test.js.min

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace JSMinify
{
    public class Minify
    {
        private string mFileName = "";              // file to process
        private string mOriginalData = "";           // data from original file
        private string mModifiedData = "";          // processed data
        private bool mIsError = false;               // becomes true if any error happens
        private string mErr = "";                    // error message 
        private BinaryReader mReader = null;         // stream to process the file byte by byte

        private const int EOF = -1;                 // for end of file

        /// <summary>
        /// Constructor - does all the processing
        /// </summary>
        /// <param name="f">file path</param>
        public Minify(string f) {

            try
            {
                if (File.Exists(f))
                {
                    mFileName = f;
                    
                    //read contents completely. This is only for test purposes. The actual processing is done by another stream
                    StreamReader rdr = new StreamReader(mFileName);
                    mOriginalData = rdr.ReadToEnd();
                    rdr.Close();

                    mReader = new BinaryReader(new FileStream(mFileName, FileMode.Open));
                    doProcess();
                    mReader.Close();

                    //write modified data
                    string outFile = mFileName + ".min";
                    StreamWriter wrt = new StreamWriter(outFile);
                    wrt.Write(mModifiedData);
                    wrt.Close();

                }
                else {
                    mIsError = true;
                    mErr = "File does not exist";
                }

            }
            catch (Exception ex) {
                mIsError = true;
                mErr = ex.Message;
            }
        }

        /// <summary>
        /// Main process
        /// </summary>
        private void doProcess() { 
            int lastChar = 1;                   // current byte read
            int thisChar = -1;                  // previous byte read
            int nextChar = -1;                  // byte read in peek()
            bool endProcess = false;            // loop control
            bool ignore = false;                // if false then add byte to final output
            bool inComment = false;             // true when current bytes are part of a comment
            bool isDoubleSlashComment = false;  // '//' comment


            // main processing loop
            while (!endProcess) {
                endProcess = (mReader.PeekChar() == -1);    // check for EOF before reading
                if (endProcess)
                    break;

                ignore = false;
                thisChar = mReader.ReadByte();
                
                if (thisChar == '\t')
                    thisChar = ' ';
                else if (thisChar == '\t')
                    thisChar = '\n';
                else if (thisChar == '\r')
                    thisChar = '\n';

                if (thisChar == '\n')
                    ignore = true;

                if (thisChar == ' ')
                {
                    if ((lastChar == ' ') || isDelimiter(lastChar) == 1)
                        ignore = true;
                    else {
                        endProcess = (mReader.PeekChar() == -1); // check for EOF
                        if (!endProcess)
                        {
                            nextChar = mReader.PeekChar();
                            if (isDelimiter(nextChar) == 1)
                                ignore = true;
                        }
                    }
                }


                if (thisChar == '/')
                {
                    nextChar = mReader.PeekChar();
                    if (nextChar == '/' || nextChar == '*')
                    {
                        ignore = true;
                        inComment = true;
                        if (nextChar == '/')
                            isDoubleSlashComment = true;
                        else
                            isDoubleSlashComment = false;
                    }


                }

                // ignore all characters till we reach end of comment
                if (inComment) {
                    while (true) {
                        thisChar = mReader.ReadByte();
                        if (thisChar == '*') {
                            nextChar = mReader.PeekChar();
                            if (nextChar == '/')
                            {
                                thisChar = mReader.ReadByte();
                                inComment = false;
                                break;
                            }
                        }
                        if (isDoubleSlashComment && thisChar == '\n') {
                                inComment = false;
                                break;
                        }

                     } // while (true)
                    ignore = true;  
                } // if (inComment) 
                

                if (!ignore)
                    addToOutput(thisChar);
                    
                lastChar = thisChar;
            } // while (!endProcess) 
        }


        /// <summary>
        /// Add character to modified data string
        /// </summary>
        /// <param name="c">char to add</param>
        private void addToOutput(int c)
        {
            mModifiedData += (char) c;
        }


        /// <summary>
        /// Original data from file
        /// </summary>
        /// <returns></returns>
        public string getOriginalData()
        {
            return mOriginalData;
        }

        /// <summary>
        /// Modified data after processing
        /// </summary>
        /// <returns></returns>
        public string getModifiedData()
        {
            return mModifiedData;
        }

        /// <summary>
        /// Check if a byte is alphanumeric
        /// </summary>
        /// <param name="c">byte to check</param>
        /// <returns>retval - 1 if yes. else 0</returns>
        private int isAlphanumeric(int c)
        {
            int retval = 0;

            if ((c >= 'a' && c <= 'z') ||
                (c >= '0' && c <= '9') ||
                (c >= 'A' && c <= 'Z') ||
                c == '_' || c == '$' || c == '\\' || c > 126)
                retval = 1;

            return retval;

        }

        /// <summary>
        /// Check if a byte is a delimiter 
        /// </summary>
        /// <param name="c">byte to check</param>
        /// <returns>retval - 1 if yes. else 0</returns>
        private int isDelimiter(int c)
        {
            int retval = 0;

            if (c == '(' || c == ',' || c == '=' || c == ':' ||
                c == '[' || c == '!' || c == '&' || c == '|' ||
                c == '?' || c == '+' || c == '-' || c == '~' ||
                c == '*' || c == '/' || c == '{' || c == '\n' ||
                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.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.