
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
001 | using System; |
002 | using System.Collections.Generic; |
003 | using System.Linq; |
004 | using System.Text; |
005 | using System.Threading.Tasks; |
006 | using System.IO; |
007 |
008 | namespace JSMinify |
009 | { |
010 | public class Minify |
011 | { |
012 | private string mFileName = "" ; // file to process |
013 | private string mOriginalData = "" ; // data from original file |
014 | private string mModifiedData = "" ; // processed data |
015 | private bool mIsError = false ; // becomes true if any error happens |
016 | private string mErr = "" ; // error message |
017 | private BinaryReader mReader = null ; // stream to process the file byte by byte |
018 |
019 | private const int EOF = -1; // for end of file |
020 |
021 | /// <summary> |
022 | /// Constructor - does all the processing |
023 | /// </summary> |
024 | /// <param name="f">file path</param> |
025 | public Minify( string f) { |
026 |
027 | try |
028 | { |
029 | if (File.Exists(f)) |
030 | { |
031 | mFileName = f; |
032 | |
033 | //read contents completely. This is only for test purposes. The actual processing is done by another stream |
034 | StreamReader rdr = new StreamReader(mFileName); |
035 | mOriginalData = rdr.ReadToEnd(); |
036 | rdr.Close(); |
037 |
038 | mReader = new BinaryReader( new FileStream(mFileName, FileMode.Open)); |
039 | doProcess(); |
040 | mReader.Close(); |
041 |
042 | //write modified data |
043 | string outFile = mFileName + ".min" ; |
044 | StreamWriter wrt = new StreamWriter(outFile); |
045 | wrt.Write(mModifiedData); |
046 | wrt.Close(); |
047 |
048 | } |
049 | else { |
050 | mIsError = true ; |
051 | mErr = "File does not exist" ; |
052 | } |
053 |
054 | } |
055 | catch (Exception ex) { |
056 | mIsError = true ; |
057 | mErr = ex.Message; |
058 | } |
059 | } |
060 |
061 | /// <summary> |
062 | /// Main process |
063 | /// </summary> |
064 | private void doProcess() { |
065 | int lastChar = 1; // current byte read |
066 | int thisChar = -1; // previous byte read |
067 | int nextChar = -1; // byte read in peek() |
068 | bool endProcess = false ; // loop control |
069 | bool ignore = false ; // if false then add byte to final output |
070 | bool inComment = false ; // true when current bytes are part of a comment |
071 | bool isDoubleSlashComment = false ; // '//' comment |
072 |
073 |
074 | // main processing loop |
075 | while (!endProcess) { |
076 | endProcess = (mReader.PeekChar() == -1); // check for EOF before reading |
077 | if (endProcess) |
078 | break ; |
079 |
080 | ignore = false ; |
081 | thisChar = mReader.ReadByte(); |
082 | |
083 | if (thisChar == '\t' ) |
084 | thisChar = ' ' ; |
085 | else if (thisChar == '\t' ) |
086 | thisChar = '\n' ; |
087 | else if (thisChar == '\r' ) |
088 | thisChar = '\n' ; |
089 |
090 | if (thisChar == '\n' ) |
091 | ignore = true ; |
092 |
093 | if (thisChar == ' ' ) |
094 | { |
095 | if ((lastChar == ' ' ) || isDelimiter(lastChar) == 1) |
096 | ignore = true ; |
097 | else { |
098 | endProcess = (mReader.PeekChar() == -1); // check for EOF |
099 | if (!endProcess) |
100 | { |
101 | nextChar = mReader.PeekChar(); |
102 | if (isDelimiter(nextChar) == 1) |
103 | ignore = true ; |
104 | } |
105 | } |
106 | } |
107 |
108 |
109 | if (thisChar == '/' ) |
110 | { |
111 | nextChar = mReader.PeekChar(); |
112 | if (nextChar == '/' || nextChar == '*' ) |
113 | { |
114 | ignore = true ; |
115 | inComment = true ; |
116 | if (nextChar == '/' ) |
117 | isDoubleSlashComment = true ; |
118 | else |
119 | isDoubleSlashComment = false ; |
120 | } |
121 |
122 |
123 | } |
124 |
125 | // ignore all characters till we reach end of comment |
126 | if (inComment) { |
127 | while ( true ) { |
128 | thisChar = mReader.ReadByte(); |
129 | if (thisChar == '*' ) { |
130 | nextChar = mReader.PeekChar(); |
131 | if (nextChar == '/' ) |
132 | { |
133 | thisChar = mReader.ReadByte(); |
134 | inComment = false ; |
135 | break ; |
136 | } |
137 | } |
138 | if (isDoubleSlashComment && thisChar == '\n' ) { |
139 | inComment = false ; |
140 | break ; |
141 | } |
142 |
143 | } // while (true) |
144 | ignore = true ; |
145 | } // if (inComment) |
146 | |
147 |
148 | if (!ignore) |
149 | addToOutput(thisChar); |
150 | |
151 | lastChar = thisChar; |
152 | } // while (!endProcess) |
153 | } |
154 |
155 |
156 | /// <summary> |
157 | /// Add character to modified data string |
158 | /// </summary> |
159 | /// <param name="c">char to add</param> |
160 | private void addToOutput( int c) |
161 | { |
162 | mModifiedData += ( char ) c; |
163 | } |
164 |
165 |
166 | /// <summary> |
167 | /// Original data from file |
168 | /// </summary> |
169 | /// <returns></returns> |
170 | public string getOriginalData() |
171 | { |
172 | return mOriginalData; |
173 | } |
174 |
175 | /// <summary> |
176 | /// Modified data after processing |
177 | /// </summary> |
178 | /// <returns></returns> |
179 | public string getModifiedData() |
180 | { |
181 | return mModifiedData; |
182 | } |
183 |
184 | /// <summary> |
185 | /// Check if a byte is alphanumeric |
186 | /// </summary> |
187 | /// <param name="c">byte to check</param> |
188 | /// <returns>retval - 1 if yes. else 0</returns> |
189 | private int isAlphanumeric( int c) |
190 | { |
191 | int retval = 0; |
192 |
193 | if ((c >= 'a' && c <= 'z' ) || |
194 | (c >= '0' && c <= '9' ) || |
195 | (c >= 'A' && c <= 'Z' ) || |
196 | c == '_' || c == '$' || c == '\\' || c > 126) |
197 | retval = 1; |
198 |
199 | return retval; |
200 |
201 | } |
202 |
203 | /// <summary> |
204 | /// Check if a byte is a delimiter |
205 | /// </summary> |
206 | /// <param name="c">byte to check</param> |
207 | /// <returns>retval - 1 if yes. else 0</returns> |
208 | private int isDelimiter( int c) |
209 | { |
210 | int retval = 0; |
211 |
212 | if (c == '(' || c == ',' || c == '=' || c == ':' || |
213 | c == '[' || c == '!' || c == '&' || c == '|' || |
214 | c == '?' || c == '+' || c == '-' || c == '~' || |
215 | c == '*' || c == '/' || c == '{' || c == '\n' || |
216 | c == ',' |
217 | ) |
218 | { |
219 | retval = 1; |
220 | } |
221 |
222 | return retval; |
223 |
224 | } |
225 |
226 |
227 |
228 | } |
229 | } |
An example is given below:
Original file:
001 | /* jshint define: false */ |
002 |
003 | /** |
004 | * @file This plugin adds on primitive Object (like string, number, array ...) additionnals methods |
005 | * @version 1.0 |
006 | * @author Julien Roche |
007 | * @copyright MIT |
008 | */ |
009 |
010 | ( function (){ |
011 | "use strict" ; |
012 |
013 | function definition($){ |
014 | /* String part */ |
015 | String.prototype.endWith = function (needle) { |
016 | return this && this .match(needle + "$" ) == needle; |
017 | }; |
018 | |
019 | String.prototype.repeat = function (num) { |
020 | // return new Array(num + 1).join(this); |
021 | var arr = []; |
022 | arr.length = num + 1; |
023 | return arr.join( this ); |
024 | }; |
025 | |
026 | String.prototype.startWith = function (needle) { |
027 | return this && this .match( "^" + needle) == needle; |
028 | }; |
029 | |
030 | /* Number part */ |
031 | Number.prototype.toPaddedString = function (length, radix) { |
032 | var string = this .toString(radix || 10), slength = string.length; |
033 | for ( var i = 0; i < (length - slength); i++) { |
034 | string = "0" + string; |
035 | } |
036 | return string; |
037 | }; |
038 | |
039 | /* Array part */ |
040 | |
042 | if (document.documentMode && document.documentMode < 9) { |
043 | // save original function of splice |
044 | var originalSplice = Array.prototype.splice; |
045 | |
046 | // provide a new implementation |
047 | Array.prototype.splice = function () { |
048 | |
049 | // since we can't modify 'arguments' array, |
050 | // let's create a new one and copy all elements of 'arguments' into it |
051 | var arr = [], |
052 | i = 0, |
053 | max = arguments.length; |
054 | |
055 | for (; i < max; i++){ |
056 | arr.push(arguments[i]); |
057 | } |
058 | |
059 | // if this function had only one argument |
060 | // compute 'deleteCount' and push it into arr |
061 | if (arr.length==1) { |
062 | arr.push( this .length - arr[0]); |
063 | } |
064 | |
065 | // invoke original splice() with our new arguments array |
066 | return originalSplice.apply( this , arr); |
067 | }; |
068 | } |
069 | |
070 | // See https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach |
071 | if (!Array.prototype.forEach) { |
072 | Array.prototype.forEach = function forEach(callback, thisArg) { |
073 | var T, k; |
074 |
075 | if ( this == null ) { |
076 | throw new TypeError( "this is null or not defined" ); |
077 | } |
078 |
079 | // 1. Let O be the result of calling ToObject passing the |this| value as the argument. |
080 | var O = Object( this ); |
081 |
082 | // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". |
083 | // 3. Let len be ToUint32(lenValue). |
084 | var len = O.length >>> 0; |
085 | // Hack to convert O.length to a UInt32 |
086 |
087 | // 4. If IsCallable(callback) is false, throw a TypeError exception. |
088 | // See: http://es5.github.com/#x9.11 |
089 | if ( {}.toString.call(callback) !== "[object Function]" ) { |
090 | throw new TypeError(callback + " is not a function" ); |
091 | } |
092 |
093 | // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. |
094 | if (thisArg) { |
095 | T = thisArg; |
096 | } |
097 |
098 | // 6. Let k be 0 |
099 | k = 0; |
100 |
101 | // 7. Repeat, while k < len |
102 | while (k < len) { |
103 |
104 | var kValue; |
105 |
106 | // a. Let Pk be ToString(k). |
107 | // This is implicit for LHS operands of the in operator |
108 | // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. |
109 | // This step can be combined with c |
110 | // c. If kPresent is true, then |
111 | if (Object.prototype.hasOwnProperty.call(O, k)) { |
112 |
113 | // i. Let kValue be the result of calling the Get internal method of O with argument Pk. |
114 | kValue = O[k]; |
115 |
116 | // ii. Call the Call internal method of callback with T as the this value and |
117 | // argument list containing kValue, k, and O. |
118 | callback.call(T, kValue, k, O); |
119 | } |
120 | // d. Increase k by 1. |
121 | k++; |
122 | } |
123 | // 8. return undefined |
124 | }; |
125 | } |
126 | } |
127 |
128 | if ( typeof module === "object" && typeof module.exports === "object" ) { |
129 | // Node approach |
130 | definition(); |
131 |
132 | } else if ( typeof define === "function" && define.amd) { |
133 | // AMD approach |
134 | define( "prototype" , [], definition); |
135 |
136 | } else if (window.jQuery) { |
137 | // Classical way |
138 | definition(); |
139 | } |
140 | }()); |
Minified file:
1 | ( 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