@@ -74,6 +74,20 @@ class Minifier
74
74
*/
75
75
protected $ c ;
76
76
77
+ /**
78
+ * This character is only active when certain look ahead actions take place.
79
+ *
80
+ * @var string
81
+ */
82
+ protected $ last_char ;
83
+
84
+ /**
85
+ * This character is only active when certain look ahead actions take place.
86
+ *
87
+ * @var string
88
+ */
89
+ protected $ output ;
90
+
77
91
/**
78
92
* Contains the options for the current minification process.
79
93
*
@@ -95,6 +109,9 @@ class Minifier
95
109
*/
96
110
protected static $ defaultOptions = ['flaggedComments ' => true ];
97
111
112
+
113
+ protected static $ keywords = ["delete " , "do " , "for " , "in " , "instanceof " , "return " , "typeof " , "yield " ];
114
+
98
115
/**
99
116
* Contains lock ids which are used to replace certain code patterns and
100
117
* prevent them from being minified
@@ -115,17 +132,11 @@ class Minifier
115
132
public static function minify ($ js , $ options = [])
116
133
{
117
134
try {
118
- ob_start ();
119
-
120
135
$ jshrink = new Minifier ();
121
136
$ js = $ jshrink ->lock ($ js );
122
- $ jshrink ->minifyDirectToOutput ($ js , $ options );
123
-
124
- // Sometimes there's a leading new line, so we trim that out here.
125
- $ js = ltrim (ob_get_clean ());
137
+ $ js = ltrim ($ jshrink ->minifyToString ($ js , $ options ));
126
138
$ js = $ jshrink ->unlock ($ js );
127
139
unset($ jshrink );
128
-
129
140
return $ js ;
130
141
} catch (\Exception $ e ) {
131
142
if (isset ($ jshrink )) {
@@ -134,9 +145,6 @@ public static function minify($js, $options = [])
134
145
$ jshrink ->clean ();
135
146
unset($ jshrink );
136
147
}
137
-
138
- // without this call things get weird, with partially outputted js.
139
- ob_end_clean ();
140
148
throw $ e ;
141
149
}
142
150
}
@@ -148,11 +156,12 @@ public static function minify($js, $options = [])
148
156
* @param string $js The raw javascript to be minified
149
157
* @param array $options Various runtime options in an associative array
150
158
*/
151
- protected function minifyDirectToOutput ($ js , $ options )
159
+ protected function minifyToString ($ js , $ options )
152
160
{
153
161
$ this ->initialize ($ js , $ options );
154
162
$ this ->loop ();
155
163
$ this ->clean ();
164
+ return $ this ->output ;
156
165
}
157
166
158
167
/**
@@ -177,7 +186,9 @@ protected function initialize($js, $options)
177
186
// Populate "a" with a new line, "b" with the first character, before
178
187
// entering the loop
179
188
$ this ->a = "\n" ;
180
- $ this ->b = $ this ->getReal ();
189
+ $ this ->b = "\n" ;
190
+ $ this ->last_char = "\n" ;
191
+ $ this ->output = "" ;
181
192
}
182
193
183
194
/**
@@ -192,6 +203,14 @@ protected function initialize($js, $options)
192
203
'[ ' => true ,
193
204
'@ ' => true ];
194
205
206
+
207
+ protected function echo ($ char ) {
208
+ echo ($ char );
209
+ $ this ->output .= $ char ;
210
+ $ this ->last_char = $ char [-1 ];
211
+ }
212
+
213
+
195
214
/**
196
215
* The primary action occurs here. This function loops through the input string,
197
216
* outputting anything that's relevant and discarding anything that is not.
@@ -201,10 +220,11 @@ protected function loop()
201
220
while ($ this ->a !== false && !is_null ($ this ->a ) && $ this ->a !== '' ) {
202
221
switch ($ this ->a ) {
203
222
// new lines
223
+ case "\r" :
204
224
case "\n" :
205
225
// if the next line is something that can't stand alone preserve the newline
206
226
if ($ this ->b !== false && isset ($ this ->noNewLineCharacters [$ this ->b ])) {
207
- echo $ this ->a ;
227
+ $ this -> echo ( $ this ->a ) ;
208
228
$ this ->saveString ();
209
229
break ;
210
230
}
@@ -220,22 +240,23 @@ protected function loop()
220
240
// no break
221
241
case ' ' :
222
242
if (static ::isAlphaNumeric ($ this ->b )) {
223
- echo $ this ->a ;
243
+ $ this -> echo ( $ this ->a ) ;
224
244
}
225
245
226
246
$ this ->saveString ();
227
247
break ;
228
248
229
249
default :
230
250
switch ($ this ->b ) {
251
+ case "\r" :
231
252
case "\n" :
232
253
if (strpos ('}])+-" \'' , $ this ->a ) !== false ) {
233
- echo $ this ->a ;
254
+ $ this -> echo ( $ this ->a ) ;
234
255
$ this ->saveString ();
235
256
break ;
236
257
} else {
237
258
if (static ::isAlphaNumeric ($ this ->a )) {
238
- echo $ this ->a ;
259
+ $ this -> echo ( $ this ->a ) ;
239
260
$ this ->saveString ();
240
261
}
241
262
}
@@ -254,7 +275,7 @@ protected function loop()
254
275
continue 3 ;
255
276
}
256
277
257
- echo $ this ->a ;
278
+ $ this -> echo ( $ this ->a ) ;
258
279
$ this ->saveString ();
259
280
break ;
260
281
}
@@ -263,9 +284,20 @@ protected function loop()
263
284
// do reg check of doom
264
285
$ this ->b = $ this ->getReal ();
265
286
266
- if (($ this ->b == '/ ' && strpos ('(,=:[!&|? ' , $ this ->a ) !== false )) {
267
- $ this ->saveRegex ();
287
+ if ($ this ->b == '/ ' ) {
288
+ $ valid_tokens = "(,=:[!&|? \n" ;
289
+ if (strpos ($ valid_tokens , $ this ->last_char ) !== false || strpos ($ valid_tokens , $ this ->a ) !== false ) {
290
+ // Regex can appear unquoted after these symbols
291
+ $ this ->saveRegex ();
292
+ } else if ($ this ->endsInKeyword ()) {
293
+ // This block checks for the "return" token before the slash.
294
+ $ this ->saveRegex ();
295
+ }
268
296
}
297
+
298
+ // if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
299
+ // $this->saveRegex();
300
+ // }
269
301
}
270
302
}
271
303
@@ -332,8 +364,25 @@ protected function getChar()
332
364
*/
333
365
protected function peek ()
334
366
{
335
- # Pull the next character but don't push the index.
336
- return $ this ->index < $ this ->len ? $ this ->input [$ this ->index ] : false ;
367
+ if ($ this ->index >= $ this ->len ) {
368
+ return false ;
369
+ }
370
+
371
+ $ char = $ this ->input [$ this ->index ];
372
+ # Convert all line endings to unix standard.
373
+ # `\r\n` converts to `\n\n` and is minified.
374
+ if ($ char == "\r" ) {
375
+ $ char = "\n" ;
376
+ }
377
+
378
+ // Normalize all whitespace except for the newline character into a
379
+ // standard space.
380
+ if ($ char !== "\n" && $ char < "\x20" ) {
381
+ return ' ' ;
382
+ }
383
+
384
+ # Return the next character but don't push the index.
385
+ return $ char ;
337
386
}
338
387
339
388
/**
@@ -428,17 +477,17 @@ protected function processMultiLineComments($startIndex)
428
477
// If conditional comments or flagged comments are not the first thing in the script
429
478
// we need to echo a and fill it with a space before moving on.
430
479
if ($ startIndex > 0 ) {
431
- echo $ this ->a ;
480
+ $ this -> echo ( $ this ->a ) ;
432
481
$ this ->a = " " ;
433
482
434
483
// If the comment started on a new line we let it stay on the new line
435
484
if ($ this ->input [($ startIndex - 1 )] === "\n" ) {
436
- echo "\n" ;
485
+ $ this -> echo ( "\n" ) ;
437
486
}
438
487
}
439
488
440
489
$ endPoint = ($ this ->index - 1 ) - $ startIndex ;
441
- echo substr ($ this ->input , $ startIndex , $ endPoint );
490
+ $ this -> echo ( substr ($ this ->input , $ startIndex , $ endPoint) );
442
491
443
492
$ this ->c = $ char ;
444
493
@@ -504,7 +553,7 @@ protected function saveString()
504
553
$ stringType = $ this ->a ;
505
554
506
555
// Echo out that starting quote
507
- echo $ this ->a ;
556
+ $ this -> echo ( $ this ->a ) ;
508
557
509
558
// Loop until the string is done
510
559
// Grab the very next character and load it into a
@@ -523,7 +572,7 @@ protected function saveString()
523
572
// block below.
524
573
case "\n" :
525
574
if ($ stringType === '` ' ) {
526
- echo $ this ->a ;
575
+ $ this -> echo ( $ this ->a ) ;
527
576
} else {
528
577
throw new \RuntimeException ('Unclosed string at position: ' . $ startpos );
529
578
}
@@ -543,14 +592,14 @@ protected function saveString()
543
592
}
544
593
545
594
// echo out the escaped character and restart the loop.
546
- echo $ this ->a . $ this ->b ;
595
+ $ this -> echo ( $ this ->a . $ this ->b ) ;
547
596
break ;
548
597
549
598
550
599
// Since we're not dealing with any special cases we simply
551
600
// output the character and continue our loop.
552
601
default :
553
- echo $ this ->a ;
602
+ $ this -> echo ( $ this ->a ) ;
554
603
}
555
604
}
556
605
}
@@ -563,23 +612,23 @@ protected function saveString()
563
612
*/
564
613
protected function saveRegex ()
565
614
{
566
- echo $ this ->a . $ this ->b ;
615
+ $ this -> echo ( $ this ->a . $ this ->b ) ;
567
616
568
617
while (($ this ->a = $ this ->getChar ()) !== false ) {
569
618
if ($ this ->a === '/ ' ) {
570
619
break ;
571
620
}
572
621
573
622
if ($ this ->a === '\\' ) {
574
- echo $ this ->a ;
623
+ $ this -> echo ( $ this ->a ) ;
575
624
$ this ->a = $ this ->getChar ();
576
625
}
577
626
578
627
if ($ this ->a === "\n" ) {
579
628
throw new \RuntimeException ('Unclosed regex pattern at position: ' . $ this ->index );
580
629
}
581
630
582
- echo $ this ->a ;
631
+ $ this -> echo ( $ this ->a ) ;
583
632
}
584
633
$ this ->b = $ this ->getReal ();
585
634
}
@@ -595,6 +644,20 @@ protected static function isAlphaNumeric($char)
595
644
return preg_match ('/^[\w\$\pL]$/ ' , $ char ) === 1 || $ char == '/ ' ;
596
645
}
597
646
647
+ protected function endsInKeyword () {
648
+ foreach (static ::$ keywords as $ keyword ) {
649
+ if (str_ends_with ($ this ->output , $ keyword )) {
650
+ return true ;
651
+ }
652
+ if (str_ends_with ($ this ->output , $ keyword . " " )) {
653
+ return true ;
654
+ }
655
+ }
656
+ return false ;
657
+ }
658
+
659
+
660
+
598
661
/**
599
662
* Replace patterns in the given string and store the replacement
600
663
*
0 commit comments