191 lines
5.3 KiB
PHP
191 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace Garfix\JsMinify;
|
|
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* This minifier removes whitespace and comments from a Javascript string.
|
|
*
|
|
* See docs for information.
|
|
*
|
|
* @author Patrick van Bergen
|
|
*/
|
|
class Minifier
|
|
{
|
|
const FLAGGED_COMMENTS = 'flaggedComments';
|
|
|
|
protected static $defaultOptions = [
|
|
self::FLAGGED_COMMENTS => true
|
|
];
|
|
|
|
protected $options = [];
|
|
|
|
/**
|
|
* Processes a javascript string and returns only the required characters,
|
|
* stripping out all unneeded whitespace and comments.
|
|
*
|
|
* @param string $js The raw javascript to be minified
|
|
* @param array $options Various runtime options in an associative array
|
|
* @throws RuntimeException
|
|
*/
|
|
public static function minify($js, $options = [])
|
|
{
|
|
$minifier = new Minifier($options);
|
|
return $minifier->minifyUsingCallbacks($js);
|
|
}
|
|
|
|
public function __construct($options)
|
|
{
|
|
$this->options = array_merge(static::$defaultOptions, $options);
|
|
}
|
|
|
|
/**
|
|
* @param string $js The raw javascript to be minified
|
|
* @return string
|
|
* @throws RuntimeException
|
|
*/
|
|
public function minifyUsingCallbacks($js)
|
|
{
|
|
$e = MinifierExpressions::get();
|
|
|
|
// treat all newlines as unix newlines, to keep working with newlines simple
|
|
$shrunkText = str_replace(["\r\n", "\r"], ["\n", "\n"], $js);
|
|
|
|
// remove comments
|
|
$exp1 = "~(?:" . implode("|", $e->tokenExpressions) . ")~su";
|
|
$shrunkText = preg_replace_callback($exp1, [$this, 'removeComments'], $shrunkText);
|
|
MinifierError::checkRegexpError();
|
|
|
|
// rewrite blocks by inserting whitespace placeholders
|
|
$exp2 = "~(?:" . implode("|", $e->allExpressions) . ")~su";
|
|
$shrunkText = preg_replace_callback($exp2, [$this, 'processBlocks'], $shrunkText);
|
|
MinifierError::checkRegexpError();
|
|
|
|
// remove all remaining space (without the newlines)
|
|
$shrunkText = preg_replace("~" . $e->someWhitespace . "~", '', $shrunkText);
|
|
MinifierError::checkRegexpError();
|
|
|
|
// reduce consecutive newlines to single one
|
|
$shrunkText = preg_replace("~[\\n]+~", "\n", $shrunkText);
|
|
MinifierError::checkRegexpError();
|
|
|
|
// remove newlines that may safely be removed
|
|
foreach ($e->safeNewlines as $safeNewline) {
|
|
$shrunkText = preg_replace("~" . $safeNewline . "~", "", $shrunkText);
|
|
MinifierError::checkRegexpError();
|
|
}
|
|
|
|
// replace whitespace placeholders by their original whitespace
|
|
$shrunkText = str_replace($e->whitespaceArrayPlaceholders, $e->whitespaceArrayNewline, $shrunkText);
|
|
MinifierError::checkRegexpError();
|
|
|
|
// remove leading and trailing whitespace
|
|
return trim($shrunkText);
|
|
}
|
|
|
|
/**
|
|
* Removes all comments that need to be removed
|
|
* The newlines that are added here may later be removed again
|
|
*
|
|
* @param $matches
|
|
* @return mixed|string
|
|
*/
|
|
protected function removeComments($matches)
|
|
{
|
|
$e = MinifierExpressions::get();
|
|
|
|
// the fully matching text
|
|
$match = $matches[0];
|
|
|
|
if (!empty($matches['lineComment'])) {
|
|
// not empty because this might glue words together
|
|
return "\n";
|
|
}
|
|
if (!empty($matches['starComment'])) {
|
|
|
|
// create a version without leading and trailing whitespace
|
|
$trimmed = trim($match, $e->whitespaceCharsNewline);
|
|
|
|
switch ($trimmed[2]) {
|
|
case '@':
|
|
// IE conditional comment
|
|
return $match;
|
|
case '!':
|
|
if ($this->options[self::FLAGGED_COMMENTS]) {
|
|
// option says: leave flagged comments in
|
|
return $match;
|
|
}
|
|
}
|
|
// multi line comment; not empty because this might glue words together
|
|
return "\n";
|
|
}
|
|
|
|
// leave other matches unchanged
|
|
return $match;
|
|
}
|
|
|
|
/**
|
|
* Updates the code for all blocks (they contain whitespace that should be conserved)
|
|
* No early returns: all code must reach `end` and have the whitespace replaced by placeholders
|
|
*
|
|
* @param $matches
|
|
* @return array|mixed|string|string[]
|
|
*/
|
|
protected function processBlocks($matches)
|
|
{
|
|
$e = MinifierExpressions::get();
|
|
|
|
// the fully matching text
|
|
$match = $matches[0];
|
|
|
|
// create a version without leading and trailing whitespace
|
|
$trimmed = trim($match, $e->whitespaceCharsNewline);
|
|
|
|
// Should be handled before optional whitespace
|
|
if (!empty($matches['requiredSpace'])) {
|
|
$match = strpos($matches['requiredSpace'], "\n") === false ? " " : "\n";
|
|
goto end;
|
|
}
|
|
// + followed by +, or - followed by -
|
|
if (!empty($matches['plus']) || !empty($matches['min'])) {
|
|
$match = ' ';
|
|
goto end;
|
|
}
|
|
if (!empty($matches['doubleQuote'])) {
|
|
// remove line continuation
|
|
$match = str_replace("\\\n", "", $match);
|
|
goto end;
|
|
}
|
|
if (!empty($matches['starComment'])) {
|
|
switch ($trimmed[2]) {
|
|
case '@':
|
|
// IE conditional comment
|
|
$match = $trimmed;
|
|
goto end;
|
|
case '!':
|
|
if ($this->options[self::FLAGGED_COMMENTS]) {
|
|
// ensure newlines before and after
|
|
$match = "\n" . $trimmed . "\n";
|
|
goto end;
|
|
}
|
|
}
|
|
// simple multi line comment; will have been removed in the first step
|
|
goto end;
|
|
}
|
|
if (!empty($matches['regexp'])) {
|
|
// regular expression
|
|
// only if the space after the regexp contains a newline, keep it
|
|
preg_match("~^" . $e->regexp . "(?P<post>" . $e->optionalWhitespaceNewline . ")" . "$~su",
|
|
$match, $newMatches);
|
|
$postfix = strpos($newMatches['post'], "\n") === false ? "" : "\n";
|
|
$match = $trimmed . $postfix;
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
|
|
return str_replace($e->whitespaceArrayNewline, $e->whitespaceArrayPlaceholders, $match);
|
|
}
|
|
}
|