Version 4.7.23

This commit is contained in:
Neo committed 2025-12-19 16:21:27 -08:00
1 parent 7124a02564
commit 25ddeb65d6
1791 files changed
+76990 -44452

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
# Contributing
When you make a contribution, please check:
- If your code runs using PHP 5.6 (and up)
- If the tests run
## Run the tests
To run both unit tests and real-life tests on actual libraries, do
/usr/bin/php7.3 ./vendor/bin/phpunit
To just run the unit tests, do
/usr/bin/php7.3 ./vendor/bin/phpunit --group unit
To just run the real-life tests, do
/usr/bin/php7.3 ./vendor/bin/phpunit --group libraries
The specified version of the PHP runtime is only important for the real-life tests, which test execution time.
If you find that your computer is slower or faster than mine, change the maxDuration values of the real-life tests.
Then, after you made your change, check that it hasn't made the code slower.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Patrick van Bergen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+190
View File
@@ -0,0 +1,190 @@
<?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);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Garfix\JsMinify;
use RuntimeException;
/**
* @author Patrick van Bergen
*/
class MinifierError
{
/**
* @throws RuntimeException
*/
public static function checkRegexpError()
{
$error = preg_last_error();
if ($error === 0) { return; }
$msg = "";
switch ($error) {
case PREG_INTERNAL_ERROR: $msg = "Internal error (no specified)"; break;
case PREG_BACKTRACK_LIMIT_ERROR: $msg = "Backtrace limit error"; break;
case PREG_RECURSION_LIMIT_ERROR: $msg = "Recursion limit error"; break;
case PREG_BAD_UTF8_ERROR: $msg = "Bad utf-8 error"; break;
case PREG_BAD_UTF8_OFFSET_ERROR: $msg = "Bad utf-8 offset error"; break;
case 6 /* PREG_JIT_STACKLIMIT_ERROR */: $msg = "JIT stack limit error"; break;
}
throw new RuntimeException("A regular expression error occurred: " . $msg);
}
}
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace Garfix\JsMinify;
/**
* @author Patrick van Bergen
*/
class MinifierExpressions
{
protected static $expressions = null;
public $tokenExpressions = [];
public $allExpressions = [];
public $safeNewlines = [];
public $whitespaceArrayPlaceholders = [];
public $whitespaceArrayNewline;
public $whitespaceCharsNewline;
public $optionalWhitespaceNewline;
public $someWhitespace;
public $regexp;
/**
* @return MinifierExpressions
*/
public static function get() {
if (self::$expressions === null) {
self::$expressions = self::create();
}
return self::$expressions;
}
protected static function create() {
$e = new MinifierExpressions();
// white space (includes space and tab) https://262.ecma-international.org/6.0/#sec-white-space
$whitespaceArray = [" ", "\t"];
$e->whitespaceArrayNewline = array_merge($whitespaceArray, ["\n"]);
// placeholders are unused code points; see also https://en.wikipedia.org/wiki/Private_Use_Areas
$e->whitespaceArrayPlaceholders = ["\x{E000}", "\x{E001}", "\x{E002}"];
$whitespaceChars = implode("", $whitespaceArray);
$e->whitespaceCharsNewline = "\n" . $whitespaceChars;
$whitespace = "[" . $whitespaceChars . "]";
$whitespaceNewline = "[" . $e->whitespaceCharsNewline . "]";
// possessive quantifier (++) is needed when used in combination with look ahead
$e->someWhitespace = $whitespace . "++";
$someWhitespaceNewline = $whitespaceNewline . "++";
$optionalWhitespace = $whitespace . "*+";
$e->optionalWhitespaceNewline = $whitespaceNewline . "*+";
// a single escaped character in a regexp, like \\ or \n (may not be newline)
$regexEscape = "\\\\[^\n]";
// regexp character class: [..] with escape characters
// [^\n\\]] is any non-] character (also not be newline)
$characterClass = "\\[(?:" . $regexEscape . "|[^\n\\]])*+\\]";
// regexp: /..(..)../i with character class and escaped characters
// [^\n/] is any non-slash character (also not be newline)
$e->regexp = "/(?:" . $regexEscape . "|" . $characterClass . "|[^\n/])++/[igmus]*+";
// characters than can form a word; these characters should not be joined together by the removal of whitespace
$word = "[a-zA-Z0-9_\$\x{0080}-\x{FFFF}]";
// note: ! is not infix and not safe
$safeOperators = ",\.&|*/%=<>?:";
$openingBrackets = "({[";
$closingBrackets = ")}\\]";
$expressionClosingBracket = ")";
$blockOpeningBracket = "{";
// newlines that may be safely removed
$e->safeNewlines = [
// newline preceded by opening bracket or operator
"(?<=[;" . $openingBrackets . $safeOperators . "])" . "\n",
// newline followed by closing bracket or operator
"\n" . "(?=[;" . $closingBrackets. $safeOperators . "])",
// ( \n [
"(?<=[" . $openingBrackets . "])" . "\n" . "(?=[" . $openingBrackets . "])",
// ] \n )
"(?<=[" . $closingBrackets . "])" . "\n" . "(?=[" . $closingBrackets . "])",
// } \n (
"(?<=[" . $expressionClosingBracket . "])" . "\n" . "(?=[" . $blockOpeningBracket . "])",
];
// these expression must always be present, because they keep tokens that contain whitespace together
$e->tokenExpressions = [
// /** comment */
'starComment' => "(?<starComment>" . "/\*.*?\*/" . $e->optionalWhitespaceNewline . ")",
// // comment
'lineComment' => "(?<lineComment>//[^\n]*" . $e->optionalWhitespaceNewline . ")",
// regular expression
'regexp' =>
"(?<regexp>" .
// if there's whitespace, match it all
$e->regexp . $optionalWhitespace .
// to distinguish a regexp from a sequence of dividers (i.e.: x / y / z):
"(?:" .
// it is followed by a newline; add it to the match
"\n" .
"|" .
// it is followed by any of these characters
"(?=[;,\.)])" .
")" .
")",
// "double quotes"
'double' => '(?<doubleQuote>"(?:\\\\.|[^"])*+")',
// 'single quotes'
'single' => "(?<singleQuote>'(?:\\\\.|[^'])*+')",
// `template literal`
'template' => "(?<templateLiteral>`(?:\\\\.|[^`])*+`)",
];
$specificExpressions = [
// a sequence of - and -- operators; i.e. a - --b; b-- -c; d-- - -e; f - -g
'min' => "(?<=-)(?<min>" . $someWhitespaceNewline . ")(?=-)",
// a sequence of + and ++ operators
'plus' => "(?<=\+)(?<plus>" . $someWhitespaceNewline . ")(?=\+)",
// whitespace both preceded and succeeded by a word char
'requiredSpace' => "(?<=" . $word . ")" . "(?<requiredSpace>" . $someWhitespaceNewline . ")" . "(?=" . $word . ")"
];
$e->allExpressions = array_merge($e->tokenExpressions, $specificExpressions);
return $e;
}
}
+25
View File
@@ -0,0 +1,25 @@
# js-minify
A fast Javascript minifier that removes unnecessary whitespace and comments
## Installation
If you are using Composer, use
composer require garfix/js-minify
## Use
The simplest use of the library comes down to this:
$minifiedJs = \Garfix\JsMinify\Minifier::minify($js);
Where `$js` contains the unprocessed code and `$minifiedJs` holds the minified version.
If you want to change the default options, use `minify($js, $options)`, where `$options` is an array of one or more of the following:
* `\Garfix\JsMinify\Minifier::FLAGGED_COMMENTS` (bool, default: `true`) When set to `false`, `/*! ... */` flagged comments are removed as well.
## Background
I started this library because I believed [JShrink](https://github.com/tedious/JShrink) could be made much faster by the use of dedicated regular expressions. This turned out to be true. It is about 10x faster on PHP 7 and 5x faster on PHP 8.
+23
View File
@@ -0,0 +1,23 @@
{
"name": "garfix/js-minify",
"description": "A fast Javascript minifier that removes unnecessary whitespace and comments",
"type": "library",
"license": "MIT",
"autoload": {
"psr-4": {
"Garfix\\": "src/"
}
},
"authors": [
{
"name": "Patrick van Bergen",
"email": "patrick.vanbergen@gmail.com"
}
],
"require": {
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
}
}