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