Ruby ThreadSafe JSMin Library
December 13, 2009
I needed a thread-safe JSMin library for compressing javascripts on the fly on UploadBooth, so I took an existing ruby implementation and made it thread safe. I don’t think there was license defined when I got it, so I am re-releasing it as-is.
require 'monitor' class JSMin EOF = -1 include MonitorMixin # jsmin -- Copy the input to the output, deleting the characters which are # insignificant to JavaScript. Comments will be removed. Tabs will be # replaced with spaces. Carriage returns will be replaced with linefeeds. # Most spaces and linefeeds will be removed. # thread safe def minimize(jstext) synchronize do @theA = "" @theB = "" @current = 0 @output = "" @text = jstext @theA = "\n" action(3) while (@theA != JSMin::EOF) case @theA when " " if (isAlphanum(@theB)) action(1) else action(2) end when "\n" case (@theB) when "{","[","(","+","-" action(1) when " " action(3) else if (isAlphanum(@theB)) action(1) else action(2) end end else case (@theB) when " " if (isAlphanum(@theA)) action(1) else action(3) end when "\n" case (@theA) when "}","]",")","+","-","\"","\\", "'", '"' action(1) else if (isAlphanum(@theA)) action(1) else action(3) end end else action(1) end end end @output end end private # isAlphanum -- return true if the character is a letter, digit, underscore, # dollar sign, or non-ASCII character def isAlphanum(c) return false if !c || c == JSMin::EOF return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || c[0] > 126) end # get -- return the next character from stdin. Watch out for lookahead. If # the character is a control character, translate it to a space or linefeed. # thread safe def get return JSMin::EOF if @current>(@text.length-1) c = @text[@current] @current += 1 c = c.chr return c if (c >= " " || c == "\n" || c.unpack("c") == JSMin::EOF) return "\n" if (c == "\r") return " " end # Get the next character without getting it. def peek lookaheadChar = @text[@current] return lookaheadChar.chr end # mynext -- get the next character, excluding comments. # peek() is used to see if a '/' is followed by a '/' or '*'. def mynext c = get if (c == "/") if(peek == "/") while(true) c = get if (c <= "\n") return c end end end if(peek == "*") get while(true) case get when "*" if (peek == "/") get return " " end when JSMin::EOF raise "Unterminated comment" end end end end return c end # action -- do something! What you do is determined by the argument: 1 # Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B. # (Delete A). 3 Get the next B. (Delete B). action treats a string as a # single character. Wow! action recognizes a regular expression if it is # preceded by ( or , or =. def action(a) if(a==1) @output << @theA end if(a==1 || a==2) @theA = @theB if (@theA == "\'" || @theA == "\"") while (true) @output << @theA @theA = get break if (@theA == @theB) raise "Unterminated string literal" if (@theA <= "\n") if (@theA == "\\") @output << @theA @theA = get end end end end if(a==1 || a==2 || a==3) @theB = mynext if (@theB == "/" && (@theA == "(" || @theA == "," || @theA == "=" || @theA == ":" || @theA == "[" || @theA == "!" || @theA == "&" || @theA == "|" || @theA == "?" || @theA == "{" || @theA == "}" || @theA == ";" || @theA == "\n")) @output << @theA @output << @theB while (true) @theA = get if (@theA == "/") break elsif (@theA == "\\") @output << @theA @theA = get elsif (@theA <= "\n") raise "Unterminated RegExp Literal" + @output end @output << @theA end @theB = mynext end end end end
Thanks for this implementation as a library. Exactly what I needed. You might want to post this code as a file somewhere as copying and pasting from the codebox directly makes a mess because the HTML translation has replaced all the single quotes with right and left single quotes.
Also the original code with license is at http://www.crockford.com/javascript/jsmin.rb