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();}}());
Leave a Reply