5 * Calls the SugarMin minify function.
7 * @param string $js Javascript to be minified
8 * @return string Minified javascript
10 public static function minify($js, $filename = '') {
11 return SugarMin::minify($js);
16 * SugarMin is a Javascript minifier with two levels of compression. The default compression
17 * is set to light. Light compression will preserve some line breaks that may interfere with
18 * operation of the script. Deep compression will remove all the line breaks, but before using
19 * deep compression make sure the script has passed JSLint.
22 protected $noSpaceChars = array('\\', '$', '_', '/');
23 protected $postNewLineSafeChars = array('\\', '$', '_', '{', '[', '(', '+', '-');
24 protected $preNewLineSafeChars = array('\\', '$', '_', '}', ']', ')', '+', '-', '"', "'");
25 protected $regexChars = array('(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';');
26 protected $compression;
28 private function __construct() {}
31 * Entry point function to minify javascript.
33 * @param string $js Javascript source code as a string.
34 * @param string $compression Compression option. {light, deep}.
35 * @return string $output Output javascript code as a string.
37 static public function minify($js, $compression = 'light') {
40 $output = $me->jsParser($js, $compression);
42 } catch (Exception $e) {
43 // Exception handling is left up to the implementer.
49 * jsParser will take javascript source code and minify it.
51 * Note: There is a lot of redundant code since both passes
52 * operate similarly but with slight differences. It will probably
53 * be a good idea to refactor the code at a later point when it is stable.
55 * JSParser will perform 3 passes on the code. Pass 1 takes care of single
56 * line and mult-line comments. Pass 2 performs some sanitation on each of the lines
57 * and pass 3 works on stripping out unnecessary spaces.
60 * @param string $currentOptions
63 protected function jsParser($js, $compression = 'light') {
65 // We first perform a few operations to simplify our
66 // minification process. We convert all carriage returns
68 $js = str_replace("\r\n", "\n", $js);
69 $js = str_replace("\r", "\n", $js);
70 $js = preg_replace("/\n+/","\n", $js);
76 // Pass 1, strip out single line and multi-line comments.
77 for ($i = 0; $i < strlen($js); $i++) {
80 if (strlen($stripped_js) > 0) {
81 $lastChar = $stripped_js[strlen($stripped_js) - 1];
84 if ($lastChar != " " && $lastChar != "\n" && $lastChar != null) {
85 $prevChar = $lastChar;
89 case "\\": // If escape character
90 $stripped_js .= $char.$js[$i + 1];
93 case '"': // If string literal
95 $literal = $delimiter = $char;
97 for ($j = $i + 1; $j < strlen($js); $j++) {
100 if ($js[$j] == "\\") {
101 $literal .= $js[$j + 1];
106 if ($js[$j] == $delimiter) {
112 $stripped_js .= $literal;
114 case "/": // If comment or regex
115 if ($js[$i + 1] == "/") {
117 for ($j = $i + 1; $j < strlen($js); $j++) {
118 if ($js[$j] == "\\") {
123 if ($js[$j + 1] == "\n") {
131 } else if ($js[$i + 1] == "*") {
133 for ($j = $i + 1; $j < strlen($js); $j++) {
134 if ($js[$j] == "\\") {
139 if ($js[$j] == "*" && $js[$j + 1] == "/") {
143 $mlcomment .= $js[$j];
148 } else if (in_array($prevChar, $this->regexChars) || $prevChar == null) {
150 $stripped_js .= $js[$i];
152 for ($j = $i + 1; $j < strlen($js); $j++) {
153 if ($js[$j] == "\\") {
154 $stripped_js .= $js[$j].$js[$j + 1];
159 if ($js[$j] == '[') {
161 } else if ($js[$j] == ']') {
165 $stripped_js .= $js[$j];
167 if ($js[$j] == '/' && $nesting == 0 && $prevChar != "\\") {
175 $stripped_js .= $char;
180 // Split our string up into an array and iterate over each line
182 $input = explode("\n", $stripped_js);
185 // Pass 2, remove space and tabs from each line.
186 for ($index = 0; $index < count($input); $index++) {
187 $line = $input[$index];
189 $line = trim($line, " \t");
191 // If the line is empty, ignore it.
192 if (strlen($line) == 0) {
196 $primedInput[] = $line;
199 $input = $primedInput;
202 // Pass 3, remove extra spaces
203 for ($index = 0; $index < count($input); $index++) {
204 $line = $input[$index];
206 $len = strlen($line);
208 $nextLine = ($index < count($input) -1 ) ? $input[$index + 1] : '';
210 $lastChar = ($len > 0) ? $line[$len - 1] : $line[0];
211 $nextChar = ($nextLine) ? $nextLine[0] : null;
213 // Iterate through the string one character at a time.
214 for ($i = 0; $i < $len; $i++) {
217 $newLine .= $line[$i].$line[$i + 1];
221 // Check if regular expression
222 if (strlen($newLine) > 0 && in_array($newLine[strlen($newLine) - 1], $this->regexChars)) {
224 $newLine .= $line[$i];
226 for ($j = $i + 1; $j < $len; $j++) {
227 if ($line[$j] == "\\") {
228 $newLine .= $line[$j].$line[$j + 1];
233 if ($line[$j] == '[') {
235 } else if ($line[$j] == ']') {
239 $newLine .= $line[$j];
240 if ($line[$j] == '/' && $nesting == 0 && $newLine[strlen($newLine) - 1] != "\\") {
246 $newLine .= $line[$i];
249 // String literals shall be transcribed as is.
252 $literal = $delimiter = $line[$i];
254 for ($j = $i + 1; $j < strlen($line); $j++) {
255 $literal .= $line[$j];
257 if ($line[$j] == "\\") {
258 $literal .= $line[$j + 1];
263 if ($line[$j] == $delimiter) {
267 if ($line[$j] == "\n") {
273 $newLine .= $literal;
275 // Tabs must be replaced with spaces and then re-evaluated to see if the space is necessary.
282 (ctype_alnum($line[$i - 1]) || in_array($line[$i - 1], $this->noSpaceChars))
284 (in_array($line[$i+1], $this->noSpaceChars) || ctype_alnum($line[$i+1]))
290 $newLine .= $line[$i];
295 if ((ctype_alnum($lastChar) || in_array($lastChar, $this->preNewLineSafeChars)) && ((in_array($nextChar, $this->postNewLineSafeChars) || ctype_alnum($nextChar)))) {
302 if ($compression == 'deep') {
303 return trim(str_replace("\n", "", $output));
305 return "\n".$output."\n";