recommited fixed lib
This commit is contained in:
parent
e6a99daa6d
commit
30f29c3607
9
lib/.gitattributes
vendored
Normal file
9
lib/.gitattributes
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
* text=auto
|
||||
|
||||
/tests export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.travis.yml export-ignore
|
||||
/makefile export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
/README.md export-ignore
|
||||
10
lib/.gitignore
vendored
Normal file
10
lib/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/build
|
||||
/docs
|
||||
/vendor
|
||||
/.idea
|
||||
/.project
|
||||
/.cache
|
||||
/.buildpath
|
||||
/.settings
|
||||
.*.swp
|
||||
.DS_Store
|
||||
18
lib/.travis.yml
Normal file
18
lib/.travis.yml
Normal file
@ -0,0 +1,18 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7
|
||||
- 5.6
|
||||
- 5.5
|
||||
- 5.4
|
||||
- hhvm
|
||||
|
||||
before_script:
|
||||
- pip install --user codecov
|
||||
- composer self-update && composer install --dev
|
||||
|
||||
script:
|
||||
- ./vendor/bin/phpunit
|
||||
|
||||
after_success:
|
||||
- codecov
|
||||
18
lib/LICENSE
Normal file
18
lib/LICENSE
Normal file
@ -0,0 +1,18 @@
|
||||
Copyright (c) 2012 Matthias Mullie
|
||||
|
||||
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.
|
||||
140
lib/README.md
Normal file
140
lib/README.md
Normal file
@ -0,0 +1,140 @@
|
||||
# Minify
|
||||
|
||||
[](https://travis-ci.org/matthiasmullie/minify)
|
||||
[](https://codecov.io/github/matthiasmullie/minify)
|
||||
[](https://scrutinizer-ci.com/g/matthiasmullie/minify)
|
||||
[](https://packagist.org/packages/matthiasmullie/minify)
|
||||
[](https://packagist.org/packages/matthiasmullie/minify)
|
||||
[](https://github.com/matthiasmullie/minify/blob/master/LICENSE)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### CSS
|
||||
|
||||
```php
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
$sourcePath = '/path/to/source/css/file.css';
|
||||
$minifier = new Minify\CSS($sourcePath);
|
||||
|
||||
// we can even add another file, they'll then be
|
||||
// joined in 1 output file
|
||||
$sourcePath2 = '/path/to/second/source/css/file.css';
|
||||
$minifier->add($sourcePath2);
|
||||
|
||||
// or we can just add plain CSS
|
||||
$css = 'body { color: #000000; }';
|
||||
$minifier->add($css);
|
||||
|
||||
// save minified file to disk
|
||||
$minifiedPath = '/path/to/minified/css/file.css';
|
||||
$minifier->minify($minifiedPath);
|
||||
|
||||
// or just output the content
|
||||
echo $minifier->minify();
|
||||
```
|
||||
|
||||
### JS
|
||||
|
||||
```php
|
||||
// just look at the CSS example; it's exactly the same, but with the JS class & JS files :)
|
||||
```
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
Available methods, for both CSS & JS minifier, are:
|
||||
|
||||
### __construct(/* overload paths */)
|
||||
|
||||
The object constructor accepts 0, 1 or multiple paths of files, or even complete CSS/JS content, that should be minified.
|
||||
All CSS/JS passed along, will be combined into 1 minified file.
|
||||
|
||||
```php
|
||||
use MatthiasMullie\Minify;
|
||||
$minifier = new Minify\JS($path1, $path2);
|
||||
```
|
||||
|
||||
### add($path, /* overload paths */)
|
||||
|
||||
This is roughly equivalent to the constructor.
|
||||
|
||||
```php
|
||||
$minifier->add($path3);
|
||||
$minifier->add($js);
|
||||
```
|
||||
|
||||
### minify($path)
|
||||
|
||||
This will minify the files' content, save the result to $path and return the resulting content.
|
||||
If the $path parameter is omitted, the result will not be written anywhere.
|
||||
|
||||
*CAUTION: If you have CSS with relative paths (to imports, images, ...), you should always specify a target path! Then those relative paths will be adjusted in accordance with the new path.*
|
||||
|
||||
```php
|
||||
$minifier->minify('/target/path.js');
|
||||
```
|
||||
|
||||
### gzip($path, $level)
|
||||
|
||||
Minifies and optionally saves to a file, just like `minify()`, but it also `gzencode()`s the minified content.
|
||||
|
||||
```php
|
||||
$minifier->gzip('/target/path.js');
|
||||
```
|
||||
|
||||
### setMaxImportSize($size) *(CSS only)*
|
||||
|
||||
The CSS minifier will automatically embed referenced files (like images, fonts, ...) into the minified CSS, so they don't have to be fetched over multiple connections.
|
||||
|
||||
However, for really large files, it's likely better to load them separately (as it would increase the CSS load time if they were included.)
|
||||
|
||||
This method allows the max size of files to import into the minified CSS to be set (in kB). The default size is 5.
|
||||
|
||||
```php
|
||||
$minifier->setMaxImportSize(10);
|
||||
```
|
||||
|
||||
### setImportExtensions($extensions) *(CSS only)*
|
||||
|
||||
The CSS minifier will automatically embed referenced files (like images, fonts, ...) into minified CSS, so they don't have to be fetched over multiple connections.
|
||||
|
||||
This methods allows the type of files to be specified, along with their data:mime type.
|
||||
|
||||
The default embedded file types are gif, png, jpg, jpeg, svg & woff.
|
||||
|
||||
```php
|
||||
$extensions = array(
|
||||
'gif' => 'data:image/gif',
|
||||
'png' => 'data:image/png',
|
||||
);
|
||||
|
||||
$minifier->setImportExtensions($extensions);
|
||||
```
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Simply add a dependency on matthiasmullie/minify to your composer.json file if you use [Composer](https://getcomposer.org/) to manage the dependencies of your project:
|
||||
|
||||
```sh
|
||||
composer require matthiasmullie/minify
|
||||
```
|
||||
|
||||
Although it's recommended to use Composer, you can actually [include these files](https://github.com/matthiasmullie/minify/issues/83) anyway you want.
|
||||
|
||||
|
||||
## Try it
|
||||
|
||||
Simply try it out online at <http://www.minifier.org>.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Minify is [MIT](http://opensource.org/licenses/MIT) licensed.
|
||||
|
||||
|
||||
## Challenges
|
||||
|
||||
If you're interested in learning some of the harder technical challenges I've encountered building this, you probably want to take a look at [what I wrote about it](http://www.mullie.eu/dont-build-your-own-minifier/) on my blog.
|
||||
45
lib/bin/minifycss
Normal file
45
lib/bin/minifycss
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
// command line utility to minify CSS
|
||||
if (file_exists(__DIR__ . '/../../../autoload.php')) {
|
||||
// if composer install
|
||||
require_once __DIR__ . '/../../../autoload.php';
|
||||
} else {
|
||||
require_once __DIR__ . '/../src/Minify.php';
|
||||
require_once __DIR__ . '/../src/CSS.php';
|
||||
require_once __DIR__ . '/../src/Exception.php';
|
||||
}
|
||||
|
||||
error_reporting(E_ALL);
|
||||
// check PHP setup for cli arguments
|
||||
if (!isset($_SERVER['argv']) && !isset($argv)) {
|
||||
fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
|
||||
exit(1);
|
||||
} elseif (!isset($argv)) {
|
||||
$argv = $_SERVER['argv'];
|
||||
}
|
||||
// check if path to file given
|
||||
if (!isset($argv[1])) {
|
||||
fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
// check if script run in cli environment
|
||||
if ('cli' !== php_sapi_name()) {
|
||||
fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
// check if source file exists
|
||||
if (!file_exists($argv[1])) {
|
||||
fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$minifier = new Minify\CSS($argv[1]);
|
||||
echo $minifier->minify();
|
||||
} catch (Exception $e) {
|
||||
fwrite(STDERR, $e->getMessage(), PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
45
lib/bin/minifyjs
Normal file
45
lib/bin/minifyjs
Normal file
@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
// command line utility to minify JS
|
||||
if (file_exists(__DIR__ . '/../../../autoload.php')) {
|
||||
// if composer install
|
||||
require_once __DIR__ . '/../../../autoload.php';
|
||||
} else {
|
||||
require_once __DIR__ . '/../src/Minify.php';
|
||||
require_once __DIR__ . '/../src/JS.php';
|
||||
require_once __DIR__ . '/../src/Exception.php';
|
||||
}
|
||||
|
||||
error_reporting(E_ALL);
|
||||
// check PHP setup for cli arguments
|
||||
if (!isset($_SERVER['argv']) && !isset($argv)) {
|
||||
fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
|
||||
exit(1);
|
||||
} elseif (!isset($argv)) {
|
||||
$argv = $_SERVER['argv'];
|
||||
}
|
||||
// check if path to file given
|
||||
if (!isset($argv[1])) {
|
||||
fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
// check if script run in cli environment
|
||||
if ('cli' !== php_sapi_name()) {
|
||||
fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
// check if source file exists
|
||||
if (!file_exists($argv[1])) {
|
||||
fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
$minifier = new Minify\JS($argv[1]);
|
||||
echo $minifier->minify();
|
||||
} catch (Exception $e) {
|
||||
fwrite(STDERR, $e->getMessage(), PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
34
lib/composer.json
Normal file
34
lib/composer.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
"type": "library",
|
||||
"description": "CSS & JS minifier",
|
||||
"keywords": ["minify", "minifier", "css", "js", "javascript"],
|
||||
"homepage": "http://www.minifier.org",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matthias Mullie",
|
||||
"homepage": "http://www.mullie.eu",
|
||||
"email": "minify@mullie.eu",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"ext-pcre": "*",
|
||||
"matthiasmullie/path-converter": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/scrapbook": "~1.0",
|
||||
"phpunit/phpunit": "~4.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MatthiasMullie\\Minify\\": "src/"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/minifycss",
|
||||
"bin/minifyjs"
|
||||
]
|
||||
}
|
||||
7
lib/data/js/keywords_after.txt
Normal file
7
lib/data/js/keywords_after.txt
Normal file
@ -0,0 +1,7 @@
|
||||
in
|
||||
public
|
||||
extends
|
||||
private
|
||||
protected
|
||||
implements
|
||||
instanceof
|
||||
27
lib/data/js/keywords_before.txt
Normal file
27
lib/data/js/keywords_before.txt
Normal file
@ -0,0 +1,27 @@
|
||||
do
|
||||
in
|
||||
let
|
||||
new
|
||||
var
|
||||
case
|
||||
else
|
||||
enum
|
||||
void
|
||||
with
|
||||
class
|
||||
const
|
||||
yield
|
||||
delete
|
||||
export
|
||||
import
|
||||
public
|
||||
static
|
||||
typeof
|
||||
extends
|
||||
package
|
||||
private
|
||||
continue
|
||||
function
|
||||
protected
|
||||
implements
|
||||
instanceof
|
||||
47
lib/data/js/keywords_reserved.txt
Normal file
47
lib/data/js/keywords_reserved.txt
Normal file
@ -0,0 +1,47 @@
|
||||
do
|
||||
if
|
||||
in
|
||||
for
|
||||
let
|
||||
new
|
||||
try
|
||||
var
|
||||
case
|
||||
else
|
||||
enum
|
||||
eval
|
||||
null
|
||||
this
|
||||
true
|
||||
void
|
||||
with
|
||||
break
|
||||
catch
|
||||
class
|
||||
const
|
||||
false
|
||||
super
|
||||
throw
|
||||
while
|
||||
yield
|
||||
delete
|
||||
export
|
||||
import
|
||||
public
|
||||
return
|
||||
static
|
||||
switch
|
||||
typeof
|
||||
default
|
||||
extends
|
||||
finally
|
||||
package
|
||||
private
|
||||
continue
|
||||
debugger
|
||||
function
|
||||
arguments
|
||||
interface
|
||||
protected
|
||||
implements
|
||||
instanceof
|
||||
45
lib/data/js/operators_after.txt
Normal file
45
lib/data/js/operators_after.txt
Normal file
@ -0,0 +1,45 @@
|
||||
+
|
||||
-
|
||||
*
|
||||
/
|
||||
%
|
||||
=
|
||||
+=
|
||||
-=
|
||||
*=
|
||||
/=
|
||||
%=
|
||||
<<=
|
||||
>>=
|
||||
>>>=
|
||||
&=
|
||||
^=
|
||||
|=
|
||||
&
|
||||
|
|
||||
^
|
||||
~
|
||||
<<
|
||||
>>
|
||||
>>>
|
||||
==
|
||||
===
|
||||
!=
|
||||
!==
|
||||
>
|
||||
<
|
||||
>=
|
||||
<=
|
||||
&&
|
||||
||
|
||||
.
|
||||
[
|
||||
]
|
||||
?
|
||||
:
|
||||
,
|
||||
;
|
||||
(
|
||||
)
|
||||
{
|
||||
}
|
||||
43
lib/data/js/operators_before.txt
Normal file
43
lib/data/js/operators_before.txt
Normal file
@ -0,0 +1,43 @@
|
||||
+
|
||||
-
|
||||
*
|
||||
/
|
||||
%
|
||||
=
|
||||
+=
|
||||
-=
|
||||
*=
|
||||
/=
|
||||
%=
|
||||
<<=
|
||||
>>=
|
||||
>>>=
|
||||
&=
|
||||
^=
|
||||
|=
|
||||
&
|
||||
|
|
||||
^
|
||||
~
|
||||
<<
|
||||
>>
|
||||
>>>
|
||||
==
|
||||
===
|
||||
!=
|
||||
!==
|
||||
>
|
||||
<
|
||||
>=
|
||||
<=
|
||||
&&
|
||||
||
|
||||
!
|
||||
.
|
||||
[
|
||||
?
|
||||
:
|
||||
,
|
||||
;
|
||||
(
|
||||
{
|
||||
5
lib/makefile
Normal file
5
lib/makefile
Normal file
@ -0,0 +1,5 @@
|
||||
docs:
|
||||
wget http://apigen.org/apigen.phar
|
||||
chmod +x apigen.phar
|
||||
php apigen.phar generate --source=src --destination=docs --template-theme=bootstrap
|
||||
rm apigen.phar
|
||||
12
lib/phpunit.xml.dist
Normal file
12
lib/phpunit.xml.dist
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit colors="true" bootstrap="tests/bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="Minify test suite">
|
||||
<directory suffix="Test.php">tests/css</directory>
|
||||
<directory suffix="Test.php">tests/js</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<log type="coverage-clover" target="build/logs/clover.xml"/>
|
||||
</logging>
|
||||
</phpunit>
|
||||
573
lib/src/CSS.php
Normal file
573
lib/src/CSS.php
Normal file
@ -0,0 +1,573 @@
|
||||
<?php
|
||||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
//use MatthiasMullie\PathConverter\Converter;
|
||||
|
||||
/**
|
||||
* CSS minifier.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @author Tijs Verkoyen <minify@verkoyen.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
|
||||
* @license MIT License
|
||||
*/
|
||||
class CSS extends Minify
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxImportSize = 5;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $importExtensions = array(
|
||||
'gif' => 'data:image/gif',
|
||||
'png' => 'data:image/png',
|
||||
'jpe' => 'data:image/jpeg',
|
||||
'jpg' => 'data:image/jpeg',
|
||||
'jpeg' => 'data:image/jpeg',
|
||||
'svg' => 'data:image/svg+xml',
|
||||
'woff' => 'data:application/x-font-woff',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'xbm' => 'image/x-xbitmap',
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the maximum size if files to be imported.
|
||||
*
|
||||
* Files larger than this size (in kB) will not be imported into the CSS.
|
||||
* Importing files into the CSS as data-uri will save you some connections,
|
||||
* but we should only import relatively small decorative images so that our
|
||||
* CSS file doesn't get too bulky.
|
||||
*
|
||||
* @param int $size Size in kB
|
||||
*/
|
||||
public function setMaxImportSize($size)
|
||||
{
|
||||
$this->maxImportSize = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of extensions to be imported into the CSS (to save network
|
||||
* connections).
|
||||
* Keys of the array should be the file extensions & respective values
|
||||
* should be the data type.
|
||||
*
|
||||
* @param string[] $extensions Array of file extensions
|
||||
*/
|
||||
public function setImportExtensions(array $extensions)
|
||||
{
|
||||
$this->importExtensions = $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move any import statements to the top.
|
||||
*
|
||||
* @param $content string Nearly finished CSS content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function moveImportsToTop($content)
|
||||
{
|
||||
if (preg_match_all('/@import[^;]+;/', $content, $matches)) {
|
||||
|
||||
// remove from content
|
||||
foreach ($matches[0] as $import) {
|
||||
$content = str_replace($import, '', $content);
|
||||
}
|
||||
|
||||
// add to top
|
||||
$content = implode('', $matches[0]).$content;
|
||||
};
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine CSS from import statements.
|
||||
*
|
||||
* @import's will be loaded and their content merged into the original file,
|
||||
* to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to combine imports for.
|
||||
* @param string $content The CSS content to combine imports for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function combineImports($source, $content)
|
||||
{
|
||||
$importRegexes = array(
|
||||
// @import url(xxx)
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
# (optional) open path enclosure
|
||||
(?P<quotes>["\']?)
|
||||
|
||||
# fetch path
|
||||
(?P<path>
|
||||
|
||||
# do not fetch data uris or external sources
|
||||
(?!(
|
||||
["\']?
|
||||
(data|https?):
|
||||
))
|
||||
|
||||
.+?
|
||||
)
|
||||
|
||||
# (optional) close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) media statement(s)
|
||||
(?P<media>[^;]*)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) closing semi-colon
|
||||
;?
|
||||
|
||||
/ix',
|
||||
|
||||
// @import 'xxx'
|
||||
'/
|
||||
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>
|
||||
|
||||
# do not fetch data uris or external sources
|
||||
(?!(
|
||||
["\']?
|
||||
(data|https?):
|
||||
))
|
||||
|
||||
.+?
|
||||
)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) media statement(s)
|
||||
(?P<media>[^;]*)
|
||||
|
||||
# (optional) trailing whitespace
|
||||
\s*
|
||||
|
||||
# (optional) closing semi-colon
|
||||
;?
|
||||
|
||||
/ix',
|
||||
);
|
||||
|
||||
// find all relative imports in css
|
||||
$matches = array();
|
||||
foreach ($importRegexes as $importRegex) {
|
||||
if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
||||
$matches = array_merge($matches, $regexMatches);
|
||||
}
|
||||
}
|
||||
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop the matches
|
||||
foreach ($matches as $match) {
|
||||
// get the path for the file that will be imported
|
||||
$importPath = dirname($source).'/'.$match['path'];
|
||||
|
||||
// only replace the import with the content if we can grab the
|
||||
// content of the file
|
||||
if (strlen($importPath) < PHP_MAXPATHLEN && file_exists($importPath) && is_file($importPath)) {
|
||||
// grab referenced file & minify it (which may include importing
|
||||
// yet other @import statements recursively)
|
||||
$minifier = new static($importPath);
|
||||
$importContent = $minifier->execute($source);
|
||||
|
||||
// check if this is only valid for certain media
|
||||
if ($match['media']) {
|
||||
$importContent = '@media '.$match['media'].'{'.$importContent.'}';
|
||||
}
|
||||
|
||||
// add to replacement array
|
||||
$search[] = $match[0];
|
||||
$replace[] = $importContent;
|
||||
}
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
$content = str_replace($search, $replace, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import files into the CSS, base64-ized.
|
||||
*
|
||||
* @url(image.jpg) images will be loaded and their content merged into the
|
||||
* original file, to save HTTP requests.
|
||||
*
|
||||
* @param string $source The file to import files for.
|
||||
* @param string $content The CSS content to import files for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function importFiles($source, $content)
|
||||
{
|
||||
$extensions = array_keys($this->importExtensions);
|
||||
$regex = '/url\((["\']?)((?!["\']?data:).*?\.('.implode('|', $extensions).'))\\1\)/i';
|
||||
if ($extensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop the matches
|
||||
foreach ($matches as $match) {
|
||||
// get the path for the file that will be imported
|
||||
$path = $match[2];
|
||||
$path = dirname($source).'/'.$path;
|
||||
$extension = $match[3];
|
||||
|
||||
// only replace the import with the content if we're able to get
|
||||
// the content of the file, and it's relatively small
|
||||
$import = strlen($path) < PHP_MAXPATHLEN;
|
||||
$import = $import && file_exists($path);
|
||||
$import = $import && is_file($path);
|
||||
$import = $import && filesize($path) <= $this->maxImportSize * 1024;
|
||||
if (!$import) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// grab content && base64-ize
|
||||
$importContent = $this->load($path);
|
||||
$importContent = base64_encode($importContent);
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
|
||||
}
|
||||
|
||||
// replace the import statements
|
||||
$content = str_replace($search, $replace, $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data.
|
||||
* Perform CSS optimizations.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to.
|
||||
*
|
||||
* @return string The minified data.
|
||||
*/
|
||||
public function execute($path = null)
|
||||
{
|
||||
$content = '';
|
||||
|
||||
// loop files
|
||||
foreach ($this->data as $source => $css) {
|
||||
/*
|
||||
* Let's first take out strings & comments, since we can't just remove
|
||||
* whitespace anywhere. If whitespace occurs inside a string, we should
|
||||
* leave it alone. E.g.:
|
||||
* p { content: "a test" }
|
||||
*/
|
||||
$this->extractStrings();
|
||||
$this->stripComments();
|
||||
$css = $this->replace($css);
|
||||
|
||||
$css = $this->stripWhitespace($css);
|
||||
$css = $this->shortenHex($css);
|
||||
$css = $this->shortenZeroes($css);
|
||||
$css = $this->stripEmptyTags($css);
|
||||
|
||||
// restore the string we've extracted earlier
|
||||
$css = $this->restoreExtractedData($css);
|
||||
|
||||
$source = $source ?: '';
|
||||
$css = $this->combineImports($source, $css);
|
||||
$css = $this->importFiles($source, $css);
|
||||
|
||||
/*
|
||||
* If we'll save to a new path, we'll have to fix the relative paths
|
||||
* to be relative no longer to the source file, but to the new path.
|
||||
* If we don't write to a file, fall back to same path so no
|
||||
* conversion happens (because we still want it to go through most
|
||||
* of the move code...)
|
||||
*/
|
||||
//$converter = new Converter($source, $path ?: $source);
|
||||
//$css = $this->move($converter, $css);
|
||||
|
||||
// combine css
|
||||
$content .= $css;
|
||||
}
|
||||
|
||||
$content = $this->moveImportsToTop($content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moving a css file should update all relative urls.
|
||||
* Relative references (e.g. ../images/image.gif) in a certain css file,
|
||||
* will have to be updated when a file is being saved at another location
|
||||
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
|
||||
*
|
||||
* @param Converter $converter Relative path converter
|
||||
* @param string $content The CSS content to update relative urls for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function move(Converter $converter, $content)
|
||||
{
|
||||
/*
|
||||
* Relative path references will usually be enclosed by url(). @import
|
||||
* is an exception, where url() is not necessary around the path (but is
|
||||
* allowed).
|
||||
* This *could* be 1 regular expression, where both regular expressions
|
||||
* in this array are on different sides of a |. But we're using named
|
||||
* patterns in both regexes, the same name on both regexes. This is only
|
||||
* possible with a (?J) modifier, but that only works after a fairly
|
||||
* recent PCRE version. That's why I'm doing 2 separate regular
|
||||
* expressions & combining the matches after executing of both.
|
||||
*/
|
||||
$relativeRegexes = array(
|
||||
// url(xxx)
|
||||
'/
|
||||
# open url()
|
||||
url\(
|
||||
|
||||
\s*
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])?
|
||||
|
||||
# fetch path
|
||||
(?P<path>
|
||||
|
||||
# do not fetch data uris or external sources
|
||||
(?!(
|
||||
\s?
|
||||
["\']?
|
||||
(data|https?):
|
||||
))
|
||||
|
||||
.+?
|
||||
)
|
||||
|
||||
# close path enclosure
|
||||
(?(quotes)(?P=quotes))
|
||||
|
||||
\s*
|
||||
|
||||
# close url()
|
||||
\)
|
||||
|
||||
/ix',
|
||||
|
||||
// @import "xxx"
|
||||
'/
|
||||
# import statement
|
||||
@import
|
||||
|
||||
# whitespace
|
||||
\s+
|
||||
|
||||
# we don\'t have to check for @import url(), because the
|
||||
# condition above will already catch these
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])
|
||||
|
||||
# fetch path
|
||||
(?P<path>
|
||||
|
||||
# do not fetch data uris or external sources
|
||||
(?!(
|
||||
["\']?
|
||||
(data|https?):
|
||||
))
|
||||
|
||||
.+?
|
||||
)
|
||||
|
||||
# close path enclosure
|
||||
(?P=quotes)
|
||||
|
||||
/ix',
|
||||
);
|
||||
|
||||
// find all relative urls in css
|
||||
$matches = array();
|
||||
foreach ($relativeRegexes as $relativeRegex) {
|
||||
if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
||||
$matches = array_merge($matches, $regexMatches);
|
||||
}
|
||||
}
|
||||
|
||||
$search = array();
|
||||
$replace = array();
|
||||
|
||||
// loop all urls
|
||||
foreach ($matches as $match) {
|
||||
// determine if it's a url() or an @import match
|
||||
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
|
||||
|
||||
// fix relative url
|
||||
$url = $converter->convert($match['path']);
|
||||
|
||||
// build replacement
|
||||
$search[] = $match[0];
|
||||
if ($type == 'url') {
|
||||
$replace[] = 'url('.$url.')';
|
||||
} elseif ($type == 'import') {
|
||||
$replace[] = '@import "'.$url.'"';
|
||||
}
|
||||
}
|
||||
|
||||
// replace urls
|
||||
$content = str_replace($search, $replace, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand hex color codes.
|
||||
* #FF0000 -> #F00.
|
||||
*
|
||||
* @param string $content The CSS content to shorten the hex color codes for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shortenHex($content)
|
||||
{
|
||||
$content = preg_replace('/(?<![\'"])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?![\'"])/i', '#$1$2$3', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand 0 values to plain 0, instead of e.g. -0em.
|
||||
*
|
||||
* @param string $content The CSS content to shorten the zero values for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shortenZeroes($content)
|
||||
{
|
||||
// reusable bits of code throughout these regexes:
|
||||
// before & after are used to make sure we don't match lose unintended
|
||||
// 0-like values (e.g. in #000, or in http://url/1.0)
|
||||
// units can be stripped from 0 values, or used to recognize non 0
|
||||
// values (where wa may be able to strip a .0 suffix)
|
||||
$before = '(?<=[:(, ])';
|
||||
$after = '(?=[ ,);}])';
|
||||
$units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
|
||||
|
||||
// strip units after zeroes (0px -> 0)
|
||||
// NOTE: it should be safe to remove all units for a 0 value, but in
|
||||
// practice, Webkit (especially Safari) seems to stumble over at least
|
||||
// 0%, potentially other units as well. Only stripping 'px' for now.
|
||||
// @see https://github.com/matthiasmullie/minify/issues/60
|
||||
$content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
|
||||
|
||||
// strip 0-digits (.0 -> 0)
|
||||
$content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
|
||||
// strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
|
||||
$content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
|
||||
// strip trailing 0: 50.00 -> 50, 50.00px -> 50px
|
||||
$content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
|
||||
// strip leading 0: 0.1 -> .1, 01.1 -> 1.1
|
||||
$content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
|
||||
|
||||
// strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
|
||||
$content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip comments from source code.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function stripEmptyTags($content)
|
||||
{
|
||||
return preg_replace('/(^|\})[^\{\}]+\{\s*\}/', '\\1', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip comments from source code.
|
||||
*/
|
||||
protected function stripComments()
|
||||
{
|
||||
$this->registerPattern('/\/\*.*?\*\//s', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip whitespace.
|
||||
*
|
||||
* @param string $content The CSS content to strip the whitespace for.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function stripWhitespace($content)
|
||||
{
|
||||
// remove leading & trailing whitespace
|
||||
$content = preg_replace('/^\s*/m', '', $content);
|
||||
$content = preg_replace('/\s*$/m', '', $content);
|
||||
|
||||
// replace newlines with a single space
|
||||
$content = preg_replace('/\s+/', ' ', $content);
|
||||
|
||||
// remove whitespace around meta characters
|
||||
// inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
|
||||
$content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
|
||||
$content = preg_replace('/([\[(:])\s+/', '$1', $content);
|
||||
$content = preg_replace('/\s+([\]\)])/', '$1', $content);
|
||||
$content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
|
||||
|
||||
// whitespace around + and - can only be stripped in selectors, like
|
||||
// :nth-child(3+2n), not in things like calc(3px + 2px) or shorthands
|
||||
// like 3px -2px
|
||||
$content = preg_replace('/\s*([+-])\s*(?=[^}]*{)/', '$1', $content);
|
||||
|
||||
// remove semicolon/whitespace followed by closing bracket
|
||||
$content = str_replace(';}', '}', $content);
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
}
|
||||
10
lib/src/Exception.php
Normal file
10
lib/src/Exception.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
/**
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
490
lib/src/JS.php
Normal file
490
lib/src/JS.php
Normal file
File diff suppressed because one or more lines are too long
382
lib/src/Minify.php
Normal file
382
lib/src/Minify.php
Normal file
@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
namespace MatthiasMullie\Minify;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
|
||||
/**
|
||||
* Abstract minifier class.
|
||||
*
|
||||
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
||||
*
|
||||
* @author Matthias Mullie <minify@mullie.eu>
|
||||
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
|
||||
* @license MIT License
|
||||
*/
|
||||
abstract class Minify
|
||||
{
|
||||
/**
|
||||
* The data to be minified.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Array of patterns to match.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $patterns = array();
|
||||
|
||||
/**
|
||||
* This array will hold content of strings and regular expressions that have
|
||||
* been extracted from the JS source code, so we can reliably match "code",
|
||||
* without having to worry about potential "code-like" characters inside.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $extracted = array();
|
||||
|
||||
/**
|
||||
* Init the minify class - optionally, code may be passed along already.
|
||||
*/
|
||||
public function __construct(/* $data = null, ... */)
|
||||
{
|
||||
// it's possible to add the source through the constructor as well ;)
|
||||
if (func_num_args()) {
|
||||
call_user_func_array(array($this, 'add'), func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file or straight-up code to be minified.
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function add($data /* $data = null, ... */)
|
||||
{
|
||||
// bogus "usage" of parameter $data: scrutinizer warns this variable is
|
||||
// not used (we're using func_get_args instead to support overloading),
|
||||
// but it still needs to be defined because it makes no sense to have
|
||||
// this function without argument :)
|
||||
$args = array($data) + func_get_args();
|
||||
|
||||
// this method can be overloaded
|
||||
foreach ($args as $data) {
|
||||
// redefine var
|
||||
$data = (string) $data;
|
||||
|
||||
// load data
|
||||
$value = $this->load($data);
|
||||
$key = ($data != $value) ? $data : count($this->data);
|
||||
|
||||
// store data
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data.
|
||||
*
|
||||
* @param string $data Either a path to a file or the content itself.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function load($data)
|
||||
{
|
||||
// check if the data is a file
|
||||
if (strlen($data) < PHP_MAXPATHLEN && file_exists($data) && is_file($data)) {
|
||||
$data = file_get_contents($data);
|
||||
|
||||
// strip BOM, if any
|
||||
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save to file.
|
||||
*
|
||||
* @param string $content The minified data.
|
||||
* @param string $path The path to save the minified data to.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function save($content, $path)
|
||||
{
|
||||
// create file & open for writing
|
||||
if (($handler = @fopen($path, 'w')) === false) {
|
||||
throw new Exception('The file "'.$path.'" could not be opened. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
// write to file
|
||||
if (@fwrite($handler, $content) === false) {
|
||||
throw new Exception('The file "'.$path.'" could not be written to. Check if PHP has enough permissions.');
|
||||
}
|
||||
|
||||
// close the file
|
||||
@fclose($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data & (optionally) saves it to a file.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to.
|
||||
*
|
||||
* @return string The minified data.
|
||||
*/
|
||||
public function minify($path = null)
|
||||
{
|
||||
$content = $this->execute($path);
|
||||
|
||||
// save to path
|
||||
if ($path !== null) {
|
||||
$this->save($content, $path);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify & gzip the data & (optionally) saves it to a file.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to.
|
||||
* @param int[optional] $level Compression level, from 0 to 9.
|
||||
*
|
||||
* @return string The minified & gzipped data.
|
||||
*/
|
||||
public function gzip($path = null, $level = 9)
|
||||
{
|
||||
$content = $this->execute($path);
|
||||
$content = gzencode($content, $level, FORCE_GZIP);
|
||||
|
||||
// save to path
|
||||
if ($path !== null) {
|
||||
$this->save($content, $path);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data & write it to a CacheItemInterface object.
|
||||
*
|
||||
* @param CacheItemInterface $item Cache item to write the data to.
|
||||
*
|
||||
* @return CacheItemInterface Cache item with the minifier data.
|
||||
*/
|
||||
public function cache(CacheItemInterface $item)
|
||||
{
|
||||
$content = $this->execute();
|
||||
$item->set($content);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the data.
|
||||
*
|
||||
* @param string[optional] $path Path to write the data to.
|
||||
*
|
||||
* @return string The minified data.
|
||||
*/
|
||||
abstract public function execute($path = null);
|
||||
|
||||
/**
|
||||
* Register a pattern to execute against the source content.
|
||||
*
|
||||
* @param string $pattern PCRE pattern.
|
||||
* @param string|callable $replacement Replacement value for matched pattern.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function registerPattern($pattern, $replacement = '')
|
||||
{
|
||||
// study the pattern, we'll execute it more than once
|
||||
$pattern .= 'S';
|
||||
|
||||
$this->patterns[] = array($pattern, $replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* We can't "just" run some regular expressions against JavaScript: it's a
|
||||
* complex language. E.g. having an occurrence of // xyz would be a comment,
|
||||
* unless it's used within a string. Of you could have something that looks
|
||||
* like a 'string', but inside a comment.
|
||||
* The only way to accurately replace these pieces is to traverse the JS one
|
||||
* character at a time and try to find whatever starts first.
|
||||
*
|
||||
* @param string $content The content to replace patterns in.
|
||||
*
|
||||
* @return string The (manipulated) content.
|
||||
*/
|
||||
protected function replace($content)
|
||||
{
|
||||
$processed = '';
|
||||
$positions = array_fill(0, count($this->patterns), -1);
|
||||
$matches = array();
|
||||
|
||||
while ($content) {
|
||||
// find first match for all patterns
|
||||
foreach ($this->patterns as $i => $pattern) {
|
||||
list($pattern, $replacement) = $pattern;
|
||||
|
||||
// no need to re-run matches that are still in the part of the
|
||||
// content that hasn't been processed
|
||||
if ($positions[$i] >= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = null;
|
||||
if (preg_match($pattern, $content, $match)) {
|
||||
$matches[$i] = $match;
|
||||
|
||||
// we'll store the match position as well; that way, we
|
||||
// don't have to redo all preg_matches after changing only
|
||||
// the first (we'll still know where those others are)
|
||||
$positions[$i] = strpos($content, $match[0]);
|
||||
} else {
|
||||
// if the pattern couldn't be matched, there's no point in
|
||||
// executing it again in later runs on this same content;
|
||||
// ignore this one until we reach end of content
|
||||
unset($matches[$i]);
|
||||
$positions[$i] = strlen($content);
|
||||
}
|
||||
}
|
||||
|
||||
// no more matches to find: everything's been processed, break out
|
||||
if (!$matches) {
|
||||
$processed .= $content;
|
||||
break;
|
||||
}
|
||||
|
||||
// see which of the patterns actually found the first thing (we'll
|
||||
// only want to execute that one, since we're unsure if what the
|
||||
// other found was not inside what the first found)
|
||||
$discardLength = min($positions);
|
||||
$firstPattern = array_search($discardLength, $positions);
|
||||
$match = $matches[$firstPattern][0];
|
||||
|
||||
// execute the pattern that matches earliest in the content string
|
||||
list($pattern, $replacement) = $this->patterns[$firstPattern];
|
||||
$replacement = $this->replacePattern($pattern, $replacement, $content);
|
||||
|
||||
// figure out which part of the string was unmatched; that's the
|
||||
// part we'll execute the patterns on again next
|
||||
$content = substr($content, $discardLength);
|
||||
$unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
|
||||
|
||||
// move the replaced part to $processed and prepare $content to
|
||||
// again match batch of patterns against
|
||||
$processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
|
||||
$content = $unmatched;
|
||||
|
||||
// first match has been replaced & that content is to be left alone,
|
||||
// the next matches will start after this replacement, so we should
|
||||
// fix their offsets
|
||||
foreach ($positions as $i => $position) {
|
||||
$positions[$i] -= $discardLength + strlen($match);
|
||||
}
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where a pattern is matched against $content and the matches
|
||||
* are replaced by their respective value.
|
||||
* This function will be called plenty of times, where $content will always
|
||||
* move up 1 character.
|
||||
*
|
||||
* @param string $pattern Pattern to match.
|
||||
* @param string|callable $replacement Replacement value.
|
||||
* @param string $content Content to match pattern against.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function replacePattern($pattern, $replacement, $content)
|
||||
{
|
||||
if (is_callable($replacement)) {
|
||||
return preg_replace_callback($pattern, $replacement, $content, 1, $count);
|
||||
} else {
|
||||
return preg_replace($pattern, $replacement, $content, 1, $count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strings are a pattern we need to match, in order to ignore potential
|
||||
* code-like content inside them, but we just want all of the string
|
||||
* content to remain untouched.
|
||||
*
|
||||
* This method will replace all string content with simple STRING#
|
||||
* placeholder text, so we've rid all strings from characters that may be
|
||||
* misinterpreted. Original string content will be saved in $this->extracted
|
||||
* and after doing all other minifying, we can restore the original content
|
||||
* via restoreStrings()
|
||||
*
|
||||
* @param string[optional] $chars
|
||||
*/
|
||||
protected function extractStrings($chars = '\'"')
|
||||
{
|
||||
// PHP only supports $this inside anonymous functions since 5.4
|
||||
$minifier = $this;
|
||||
$callback = function ($match) use ($minifier) {
|
||||
if (!$match[1]) {
|
||||
/*
|
||||
* Empty strings need no placeholder; they can't be confused for
|
||||
* anything else anyway.
|
||||
* But we still needed to match them, for the extraction routine
|
||||
* to skip over this particular string.
|
||||
*/
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
$count = count($minifier->extracted);
|
||||
$placeholder = $match[1].$count.$match[1];
|
||||
$minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
|
||||
|
||||
return $placeholder;
|
||||
};
|
||||
|
||||
/*
|
||||
* The \\ messiness explained:
|
||||
* * Don't count ' or " as end-of-string if it's escaped (has backslash
|
||||
* in front of it)
|
||||
* * Unless... that backslash itself is escaped (another leading slash),
|
||||
* in which case it's no longer escaping the ' or "
|
||||
* * So there can be either no backslash, or an even number
|
||||
* * multiply all of that times 4, to account for the escaping that has
|
||||
* to be done to pass the backslash into the PHP string without it being
|
||||
* considered as escape-char (times 2) and to get it in the regex,
|
||||
* escaped (times 2)
|
||||
*/
|
||||
$this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will restore all extracted data (strings, regexes) that were
|
||||
* replaced with placeholder text in extract*(). The original content was
|
||||
* saved in $this->extracted.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function restoreExtractedData($content)
|
||||
{
|
||||
if (!$this->extracted) {
|
||||
// nothing was extracted, nothing to restore
|
||||
return $content;
|
||||
}
|
||||
|
||||
$content = strtr($content, $this->extracted);
|
||||
|
||||
$this->extracted = array();
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
3
lib/tests/bootstrap.php
Normal file
3
lib/tests/bootstrap.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
594
lib/tests/css/CSSTest.php
Normal file
594
lib/tests/css/CSSTest.php
Normal file
@ -0,0 +1,594 @@
|
||||
<?php
|
||||
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
/**
|
||||
* CSS minifier test case.
|
||||
*/
|
||||
class CSSTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var Minify\CSS
|
||||
*/
|
||||
private $minifier;
|
||||
|
||||
/**
|
||||
* Prepares the environment before running a test.
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// override save method, there's no point in writing the result out here
|
||||
$this->minifier = $this->getMockBuilder('\MatthiasMullie\Minify\CSS')
|
||||
->setMethods(array('save'))
|
||||
->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the environment after running a test.
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->minifier = null;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSS minifier rules, provided by dataProvider.
|
||||
*
|
||||
* @test
|
||||
* @dataProvider dataProvider
|
||||
*/
|
||||
public function minify($input, $expected)
|
||||
{
|
||||
$input = (array) $input;
|
||||
foreach ($input as $css) {
|
||||
$this->minifier->add($css);
|
||||
}
|
||||
$result = $this->minifier->minify();
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test conversion of relative paths, provided by dataProviderPaths.
|
||||
*
|
||||
* @test
|
||||
* @dataProvider dataProviderPaths
|
||||
*/
|
||||
public function convertRelativePath($source, $target, $expected)
|
||||
{
|
||||
$source = (array) $source;
|
||||
foreach ($source as $path => $css) {
|
||||
$this->minifier->add($css);
|
||||
|
||||
// $source also accepts an array where the key is a bogus path
|
||||
if (is_string($path)) {
|
||||
$object = new ReflectionObject($this->minifier);
|
||||
$property = $object->getProperty('data');
|
||||
$property->setAccessible(true);
|
||||
$data = $property->getValue($this->minifier);
|
||||
|
||||
// keep content, but make it appear from the given path
|
||||
$data[$path] = array_pop($data);
|
||||
$property->setValue($this->minifier, $data);
|
||||
$property->setAccessible(false);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->minifier->minify($target);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test minifier import configuration methods.
|
||||
*
|
||||
* @test
|
||||
*/
|
||||
public function setConfig()
|
||||
{
|
||||
$this->minifier->setMaxImportSize(10);
|
||||
$this->minifier->setImportExtensions(array('gif' => 'data:image/gif'));
|
||||
|
||||
$object = new ReflectionObject($this->minifier);
|
||||
|
||||
$property = $object->getProperty('maxImportSize');
|
||||
$property->setAccessible(true);
|
||||
$this->assertEquals($property->getValue($this->minifier), 10);
|
||||
|
||||
$property = $object->getProperty('importExtensions');
|
||||
$property->setAccessible(true);
|
||||
$this->assertEquals($property->getValue($this->minifier), array('gif' => 'data:image/gif'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [input, expected result]
|
||||
*/
|
||||
public function dataProvider()
|
||||
{
|
||||
$tests = array();
|
||||
|
||||
// try importing, with both @import syntax types & media queries
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/combine_imports/index.css',
|
||||
'body{color:red}',
|
||||
);
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/combine_imports/index2.css',
|
||||
'body{color:red}',
|
||||
);
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/combine_imports/index3.css',
|
||||
'body{color:red}body{color:red}',
|
||||
);
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/combine_imports/index4.css',
|
||||
'@media only screen{body{color:red}}@media only screen{body{color:red}}',
|
||||
);
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/combine_imports/index5.css',
|
||||
'body{color:red}body{color:red}',
|
||||
);
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/combine_imports/index6a.css',
|
||||
'body{color:red}',
|
||||
);
|
||||
|
||||
// shorthand hex color codes
|
||||
$tests[] = array(
|
||||
'color:#FF00FF;',
|
||||
'color:#F0F;',
|
||||
);
|
||||
|
||||
// import files
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/import_files/index.css',
|
||||
'body{background:url(data:image/png;base64,'.base64_encode(file_get_contents(__DIR__.'/sample/import_files/file.png')).')}',
|
||||
);
|
||||
|
||||
// strip comments
|
||||
$tests[] = array(
|
||||
'/* This is a CSS comment */',
|
||||
'',
|
||||
);
|
||||
|
||||
// strip whitespace
|
||||
$tests[] = array(
|
||||
'body { color: red; }',
|
||||
'body{color:red}',
|
||||
);
|
||||
|
||||
// whitespace inside strings shouldn't be replaced
|
||||
$tests[] = array(
|
||||
'content:"preserve whitespace"',
|
||||
'content:"preserve whitespace"',
|
||||
);
|
||||
|
||||
$tests[] = array(
|
||||
'html
|
||||
body {
|
||||
color: red;
|
||||
}',
|
||||
'html body{color:red}',
|
||||
);
|
||||
|
||||
$tests[] = array(
|
||||
<<<'JS'
|
||||
p * i , html
|
||||
/* remove spaces */
|
||||
|
||||
/* " comments have no escapes \*/
|
||||
body/* keep */ /* space */p,
|
||||
p [ remove ~= " spaces " ] :nth-child( 3 + 2n ) > b span i , div::after
|
||||
|
||||
{
|
||||
/* comment */
|
||||
content : " escapes \" allowed \\" ;
|
||||
content: " /* string */ " !important ;
|
||||
width: calc( 100% - 3em + 5px ) ;
|
||||
margin-top : 0;
|
||||
margin-bottom : 0;
|
||||
margin-left : 10px;
|
||||
margin-right : 10px;
|
||||
}
|
||||
JS
|
||||
,
|
||||
'p * i,html body p,p [remove~=" spaces "] :nth-child(3+2n)>b span i,div::after{content:" escapes \\" allowed \\\\";content:" /* string */ "!important;width:calc(100% - 3em + 5px);margin-top:0;margin-bottom:0;margin-left:10px;margin-right:10px}',
|
||||
);
|
||||
|
||||
/*
|
||||
* https://github.com/forkcms/forkcms/issues/387
|
||||
*
|
||||
* CSS backslash.
|
||||
* * Backslash escaped by backslash in CSS
|
||||
* * Double CSS backslashed escaped twice for in PHP string
|
||||
*/
|
||||
$tests[] = array(
|
||||
'.iconic.map-pin:before { content: "\\\\"; }',
|
||||
'.iconic.map-pin:before{content:"\\\\"}',
|
||||
);
|
||||
|
||||
// strip BOM
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/bom/bom.css',
|
||||
'body{color:red}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/22
|
||||
$tests[] = array(
|
||||
'p { background-position: -0px -64px; }',
|
||||
'p{background-position:0 -64px}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/23
|
||||
$tests[] = array(
|
||||
'ul.pagination {
|
||||
display: block;
|
||||
min-height: 1.5rem;
|
||||
margin-left: -0.3125rem;
|
||||
}',
|
||||
'ul.pagination{display:block;min-height:1.5rem;margin-left:-.3125rem}',
|
||||
);
|
||||
|
||||
// edge cases for stripping zeroes
|
||||
$tests[] = array(
|
||||
'p { margin: -0.0rem; }',
|
||||
'p{margin:0rem}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: -0.01rem; }',
|
||||
'p{margin:-.01rem}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: .0; }',
|
||||
'p{margin:0}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: .0%; }',
|
||||
'p{margin:0%}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 1.0; }',
|
||||
'p{margin:1}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 1.0px; }',
|
||||
'p{margin:1px}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 1.1; }',
|
||||
'p{margin:1.1}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 1.1em; }',
|
||||
'p{margin:1.1em}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 00px; }',
|
||||
'p{margin:0}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 0.1px; }',
|
||||
'p{margin:.1px}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 01.1px; }',
|
||||
'p{margin:1.1px}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p { margin: 0.060px; }',
|
||||
'p{margin:.06px}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'p.class00 { background-color: #000000; color: #000; }',
|
||||
'p.class00{background-color:#000;color:#000}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/24
|
||||
$tests[] = array(
|
||||
'.col-1-1 { width: 100.00%; }',
|
||||
'.col-1-1{width:100%}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/25
|
||||
$tests[] = array(
|
||||
'p { background-color: #000000; color: #000; }',
|
||||
'p{background-color:#000;color:#000}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/26
|
||||
$tests[] = array(
|
||||
'.hr > :first-child { width: 0.0001%; }',
|
||||
'.hr>:first-child{width:.0001%}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/28
|
||||
$tests[] = array(
|
||||
'@font-face { src: url(//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0); }',
|
||||
'@font-face{src:url(//netdna.bootstrapcdn.com/font-awesome/4.2.0/fonts/fontawesome-webfont.eot?v=4.2.0)}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/31
|
||||
$tests[] = array(
|
||||
'dfn,em,img{color:red}',
|
||||
'dfn,em,img{color:red}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/49
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/import_files/issue49.css',
|
||||
'.social-btn a[href*="facebook"]{background-image:url(data:image/png;base64,'.base64_encode(file_get_contents(__DIR__.'/sample/import_files/facebook.png')).')}'.
|
||||
'.social-btn a[href*="vimeo"]{background-image:url(data:image/png;base64,'.base64_encode(file_get_contents(__DIR__.'/sample/import_files/vimeo.png')).')}'.
|
||||
'.social-btn a[href*="instagram"]{background-image:url(data:image/png;base64,'.base64_encode(file_get_contents(__DIR__.'/sample/import_files/instagram.png')).')}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/68
|
||||
$tests[] = array(
|
||||
__DIR__.'/sample/external_imports/issue68.css',
|
||||
'@import url(http://localhost/file.css);body{background:green}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/67
|
||||
$tests[] = array(
|
||||
'body { }
|
||||
p { color: #fff; }',
|
||||
'p{color:#fff}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'body {}
|
||||
p { color: #fff; }
|
||||
h1 { }
|
||||
strong { color: red; }',
|
||||
'p{color:#fff}strong{color:red}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/74
|
||||
$tests[] = array(
|
||||
"@media only screen and (-webkit-min-device-pixel-ratio: 1.5),
|
||||
only screen and (min--moz-device-pixel-ratio: 1.5),
|
||||
only screen and (min-device-pixel-ratio: 1.5) {
|
||||
|
||||
#fancybox-loading,.fancybox-close,.fancybox-prev span,.fancybox-next span {
|
||||
background-image: url('/path/to/image.png');
|
||||
background-size: 44px 152px;
|
||||
}
|
||||
|
||||
#fancybox-loading div {
|
||||
background-image: url('/path/to/image.gif');
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
}",
|
||||
'@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-device-pixel-ratio:1.5){#fancybox-loading,.fancybox-close,.fancybox-prev span,.fancybox-next span{background-image:url(/path/to/image.png);background-size:44px 152px}#fancybox-loading div{background-image:url(/path/to/image.gif);background-size:24px 24px}}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/92
|
||||
$tests[] = array(
|
||||
'@media (min-width:320px) {
|
||||
/* smartphones, iPhone, portrait 480x320 phones */
|
||||
p {
|
||||
background-color: red;
|
||||
}
|
||||
}
|
||||
@media (min-width:1025px) {
|
||||
/* big landscape tablets, laptops, and desktops */
|
||||
/* LEFT EMPTY OF ANY SELECTORS */
|
||||
}
|
||||
@media (min-width:1281px) {
|
||||
/* hi-res laptops and desktops */
|
||||
p {
|
||||
background-color: blue;
|
||||
}
|
||||
}',
|
||||
'@media (min-width:320px){p{background-color:red}}@media (min-width:1281px){p{background-color:blue}}',
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [input, expected result]
|
||||
*/
|
||||
public function dataProviderPaths()
|
||||
{
|
||||
$tests = array();
|
||||
|
||||
$source = __DIR__.'/sample/convert_relative_path/source';
|
||||
$target = __DIR__.'/sample/convert_relative_path/target';
|
||||
|
||||
// external link
|
||||
$tests[] = array(
|
||||
$source.'/external.css',
|
||||
$target.'/external.css',
|
||||
file_get_contents($source.'/external.css'),
|
||||
);
|
||||
|
||||
// absolute path
|
||||
$tests[] = array(
|
||||
$source.'/absolute.css',
|
||||
$target.'/absolute.css',
|
||||
file_get_contents($source.'/absolute.css'),
|
||||
);
|
||||
|
||||
// relative paths
|
||||
$tests[] = array(
|
||||
$source.'/relative.css',
|
||||
$target.'/relative.css',
|
||||
'@import url(stylesheet.css);',
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/../source/relative.css',
|
||||
$target.'/target/relative.css',
|
||||
'@import url(../stylesheet.css);',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/29
|
||||
$tests[] = array(
|
||||
$source.'/issue29.css',
|
||||
$target.'/issue29.css',
|
||||
"@import url('http://myurl.de');",
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/38
|
||||
$tests[] = array(
|
||||
$source.'/relative.css',
|
||||
null, // no output file
|
||||
file_get_contents($source.'/relative.css'),
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/39
|
||||
$tests[] = array(
|
||||
$source.'/issue39.css',
|
||||
null, // no output file
|
||||
// relative paths should remain untouched
|
||||
"@font-face{font-family:'blackcat';src:url(../webfont/blackcat.eot);src:url(../webfont/blackcat.eot?#iefix) format('embedded-opentype'),url(../webfont/blackcat.svg#blackcat) format('svg'),url(../webfont/blackcat.woff) format('woff'),url(../webfont/blackcat.ttf) format('truetype');font-weight:normal;font-style:normal}",
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/issue39.css',
|
||||
$target.'/issue39.css',
|
||||
// relative paths should remain untouched
|
||||
"@font-face{font-family:'blackcat';src:url(../webfont/blackcat.eot);src:url(../webfont/blackcat.eot?#iefix) format('embedded-opentype'),url(../webfont/blackcat.svg#blackcat) format('svg'),url(../webfont/blackcat.woff) format('woff'),url(../webfont/blackcat.ttf) format('truetype');font-weight:normal;font-style:normal}",
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/issue39.css',
|
||||
$target.'/target/issue39.css',
|
||||
// relative paths should have changed
|
||||
"@font-face{font-family:'blackcat';src:url(../../webfont/blackcat.eot);src:url(../../webfont/blackcat.eot?#iefix) format('embedded-opentype'),url(../../webfont/blackcat.svg#blackcat) format('svg'),url(../../webfont/blackcat.woff) format('woff'),url(../../webfont/blackcat.ttf) format('truetype');font-weight:normal;font-style:normal}",
|
||||
);
|
||||
|
||||
// https://github.com/forkcms/forkcms/issues/1121
|
||||
$tests[] = array(
|
||||
$source.'/nested/nested.css',
|
||||
$target.'/nested.css',
|
||||
'@import url(stylesheet.css);',
|
||||
);
|
||||
|
||||
// https://github.com/forkcms/forkcms/issues/1186
|
||||
$tests[] = array(
|
||||
array(
|
||||
// key is a bogus path
|
||||
'/Users/mathias/Documents/— Projecten/PROJECT_NAAM/Web/src/Backend/Core/Layout/Css/screen.css' => '@import url("imports/typography.css");',
|
||||
),
|
||||
'/Users/mathias/Documents/— Projecten/PROJECT_NAAM/Web/src/Backend/Cache/MinifiedCss/some-hash.css',
|
||||
'@import url(../../Core/Layout/Css/imports/typography.css);',
|
||||
);
|
||||
|
||||
$sourceRelative = 'tests/css/sample/convert_relative_path/source';
|
||||
$targetRelative = 'tests/css/sample/convert_relative_path/target';
|
||||
|
||||
// from and/or to are relative links
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/relative.css',
|
||||
$target.'/relative.css',
|
||||
'@import url(stylesheet.css);',
|
||||
);
|
||||
// note: relative target only works if the file already exists: it has
|
||||
// to be able to realpath()
|
||||
$tests[] = array(
|
||||
$source.'/relative.css',
|
||||
$targetRelative.'/relative.css',
|
||||
'@import url(stylesheet.css);',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/relative.css',
|
||||
$targetRelative.'/relative.css',
|
||||
'@import url(stylesheet.css);',
|
||||
);
|
||||
|
||||
$source = __DIR__.'/sample/symlink';
|
||||
$target = __DIR__.'/sample/symlink/target';
|
||||
$sourceRelative = 'tests/css/sample/symlink';
|
||||
$targetRelative = 'tests/css/sample/symlink/target';
|
||||
|
||||
// import symlinked files: relative, absolute & mix
|
||||
$tests[] = array(
|
||||
$source.'/import_symlinked_file.css',
|
||||
$target.'/import_symlinked_file.css',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/import_symlinked_file.css',
|
||||
$targetRelative.'/import_symlinked_file.css',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/import_symlinked_file.css',
|
||||
$targetRelative.'/import_symlinked_file.css',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/import_symlinked_file.css',
|
||||
$target.'/import_symlinked_file.css',
|
||||
'',
|
||||
);
|
||||
|
||||
// move symlinked files: relative, absolute & mix
|
||||
$tests[] = array(
|
||||
$source.'/move_symlinked_file.css',
|
||||
$target.'/move_symlinked_file.css',
|
||||
'body{background-url:url(../assets/symlink.bmp)}',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/move_symlinked_file.css',
|
||||
$targetRelative.'/move_symlinked_file.css',
|
||||
'body{background-url:url(../assets/symlink.bmp)}',
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/move_symlinked_file.css',
|
||||
$targetRelative.'/move_symlinked_file.css',
|
||||
'body{background-url:url(../assets/symlink.bmp)}',
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/move_symlinked_file.css',
|
||||
$targetRelative.'/move_symlinked_file.css',
|
||||
'body{background-url:url(../assets/symlink.bmp)}',
|
||||
);
|
||||
|
||||
// import symlinked folders: relative, absolute & mix
|
||||
$tests[] = array(
|
||||
$source.'/import_symlinked_folder.css',
|
||||
$target.'/import_symlinked_folder.css',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/import_symlinked_folder.css',
|
||||
$targetRelative.'/import_symlinked_folder.css',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/import_symlinked_folder.css',
|
||||
$targetRelative.'/import_symlinked_folder.css',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/import_symlinked_folder.css',
|
||||
$target.'/import_symlinked_folder.css',
|
||||
'',
|
||||
);
|
||||
|
||||
// move symlinked folders: relative, absolute & mix
|
||||
$tests[] = array(
|
||||
$source.'/move_symlinked_folder.css',
|
||||
$target.'/move_symlinked_folder.css',
|
||||
'body{background-url:url(../assets_symlink/asset.bmp)}',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/move_symlinked_folder.css',
|
||||
$targetRelative.'/move_symlinked_folder.css',
|
||||
'body{background-url:url(../assets_symlink/asset.bmp)}',
|
||||
);
|
||||
$tests[] = array(
|
||||
$source.'/move_symlinked_folder.css',
|
||||
$targetRelative.'/move_symlinked_folder.css',
|
||||
'body{background-url:url(../assets_symlink/asset.bmp)}',
|
||||
);
|
||||
$tests[] = array(
|
||||
$sourceRelative.'/move_symlinked_folder.css',
|
||||
$target.'/move_symlinked_folder.css',
|
||||
'body{background-url:url(../assets_symlink/asset.bmp)}',
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
}
|
||||
3
lib/tests/css/sample/bom/bom.css
Normal file
3
lib/tests/css/sample/bom/bom.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
color: red;
|
||||
}
|
||||
3
lib/tests/css/sample/combine_imports/import.css
vendored
Normal file
3
lib/tests/css/sample/combine_imports/import.css
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
color: red;
|
||||
}
|
||||
1
lib/tests/css/sample/combine_imports/index.css
Normal file
1
lib/tests/css/sample/combine_imports/index.css
Normal file
@ -0,0 +1 @@
|
||||
@import url(import.css);
|
||||
1
lib/tests/css/sample/combine_imports/index2.css
Normal file
1
lib/tests/css/sample/combine_imports/index2.css
Normal file
@ -0,0 +1 @@
|
||||
@import 'import.css';
|
||||
2
lib/tests/css/sample/combine_imports/index3.css
Normal file
2
lib/tests/css/sample/combine_imports/index3.css
Normal file
@ -0,0 +1,2 @@
|
||||
@import url(import.css);
|
||||
@import 'import.css';
|
||||
2
lib/tests/css/sample/combine_imports/index4.css
Normal file
2
lib/tests/css/sample/combine_imports/index4.css
Normal file
@ -0,0 +1,2 @@
|
||||
@import url(import.css) only screen;
|
||||
@import 'import.css' only screen;
|
||||
2
lib/tests/css/sample/combine_imports/index5.css
Normal file
2
lib/tests/css/sample/combine_imports/index5.css
Normal file
@ -0,0 +1,2 @@
|
||||
@import url(import.css);
|
||||
@import 'import.css';
|
||||
1
lib/tests/css/sample/combine_imports/index6a.css
Normal file
1
lib/tests/css/sample/combine_imports/index6a.css
Normal file
@ -0,0 +1 @@
|
||||
@import url(index6b.css);
|
||||
1
lib/tests/css/sample/combine_imports/index6b.css
Normal file
1
lib/tests/css/sample/combine_imports/index6b.css
Normal file
@ -0,0 +1 @@
|
||||
@import url(import.css);
|
||||
@ -0,0 +1 @@
|
||||
@import url(/absolute/path/to/stylesheet.css);
|
||||
@ -0,0 +1 @@
|
||||
@import url(http://fonts.googleapis.com/css?family=Lato:100,300,400,700,900|Droid+Sans:400,700);
|
||||
@ -0,0 +1 @@
|
||||
@import url( 'http://myurl.de');
|
||||
@ -0,0 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'blackcat';
|
||||
src:url('../webfont/blackcat.eot');
|
||||
src:url('../webfont/blackcat.eot?#iefix') format('embedded-opentype'),
|
||||
url('../webfont/blackcat.svg#blackcat') format('svg'),
|
||||
url('../webfont/blackcat.woff') format('woff'),
|
||||
url('../webfont/blackcat.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
@import url(../relative.css);
|
||||
@ -0,0 +1 @@
|
||||
@import url(../target/stylesheet.css);
|
||||
3
lib/tests/css/sample/external_imports/file.css
Normal file
3
lib/tests/css/sample/external_imports/file.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
background: green;
|
||||
}
|
||||
2
lib/tests/css/sample/external_imports/issue68.css
Normal file
2
lib/tests/css/sample/external_imports/issue68.css
Normal file
@ -0,0 +1,2 @@
|
||||
@import "file.css";
|
||||
@import url(http://localhost/file.css);
|
||||
BIN
lib/tests/css/sample/import_files/facebook.png
Normal file
BIN
lib/tests/css/sample/import_files/facebook.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
BIN
lib/tests/css/sample/import_files/file.png
Normal file
BIN
lib/tests/css/sample/import_files/file.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
1
lib/tests/css/sample/import_files/index.css
Normal file
1
lib/tests/css/sample/import_files/index.css
Normal file
@ -0,0 +1 @@
|
||||
body { background: url(file.png); }
|
||||
BIN
lib/tests/css/sample/import_files/instagram.png
Normal file
BIN
lib/tests/css/sample/import_files/instagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
3
lib/tests/css/sample/import_files/issue49.css
Normal file
3
lib/tests/css/sample/import_files/issue49.css
Normal file
@ -0,0 +1,3 @@
|
||||
.social-btn a[href*="facebook"] { background-image: url(facebook.png) } /* encode skipped */
|
||||
.social-btn a[href*="vimeo"] { background-image: url(vimeo.png) }
|
||||
.social-btn a[href*="instagram"] { background-image: url(instagram.png) }
|
||||
BIN
lib/tests/css/sample/import_files/vimeo.png
Normal file
BIN
lib/tests/css/sample/import_files/vimeo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 B |
1
lib/tests/css/sample/symlink/assets/symlink.bmp
Normal file
1
lib/tests/css/sample/symlink/assets/symlink.bmp
Normal file
@ -0,0 +1 @@
|
||||
../other_path/assets/asset.bmp
|
||||
1
lib/tests/css/sample/symlink/assets_symlink
Normal file
1
lib/tests/css/sample/symlink/assets_symlink
Normal file
@ -0,0 +1 @@
|
||||
other_path/assets
|
||||
1
lib/tests/css/sample/symlink/import_symlinked_file.css
Normal file
1
lib/tests/css/sample/symlink/import_symlinked_file.css
Normal file
@ -0,0 +1 @@
|
||||
@import url(imports/symlink.css);
|
||||
1
lib/tests/css/sample/symlink/import_symlinked_folder.css
Normal file
1
lib/tests/css/sample/symlink/import_symlinked_folder.css
Normal file
@ -0,0 +1 @@
|
||||
@import url(imports_symlink/import.css);
|
||||
1
lib/tests/css/sample/symlink/imports/symlink.css
Normal file
1
lib/tests/css/sample/symlink/imports/symlink.css
Normal file
@ -0,0 +1 @@
|
||||
../other_path/imports/import.css
|
||||
1
lib/tests/css/sample/symlink/imports_symlink
Normal file
1
lib/tests/css/sample/symlink/imports_symlink
Normal file
@ -0,0 +1 @@
|
||||
other_path/imports
|
||||
1
lib/tests/css/sample/symlink/move_symlinked_file.css
Normal file
1
lib/tests/css/sample/symlink/move_symlinked_file.css
Normal file
@ -0,0 +1 @@
|
||||
body{background-url:url(assets/symlink.bmp)}
|
||||
1
lib/tests/css/sample/symlink/move_symlinked_folder.css
Normal file
1
lib/tests/css/sample/symlink/move_symlinked_folder.css
Normal file
@ -0,0 +1 @@
|
||||
body{background-url:url(assets_symlink/asset.bmp)}
|
||||
0
lib/tests/css/sample/symlink/other_path/imports/import.css
vendored
Normal file
0
lib/tests/css/sample/symlink/other_path/imports/import.css
vendored
Normal file
149
lib/tests/js/AbstractTest.php
Normal file
149
lib/tests/js/AbstractTest.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
use MatthiasMullie\Minify;
|
||||
use MatthiasMullie\Scrapbook\Adapters\MemoryStore;
|
||||
use MatthiasMullie\Scrapbook\Psr6\Pool;
|
||||
|
||||
/**
|
||||
* Tests common functions of abstract Minify class by using JS implementation.
|
||||
*/
|
||||
class CommonTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function construct()
|
||||
{
|
||||
$path1 = __DIR__.'/sample/source/script1.js';
|
||||
$path2 = __DIR__.'/sample/source/script2.js';
|
||||
$content1 = file_get_contents($path1);
|
||||
$content2 = file_get_contents($path2);
|
||||
|
||||
// 1 source in constructor
|
||||
$minifier = new Minify\JS($content1);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1, $result);
|
||||
|
||||
// multiple sources in constructor
|
||||
$minifier = new Minify\JS($content1, $content2);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1.';'.$content2, $result);
|
||||
|
||||
// file in constructor
|
||||
$minifier = new Minify\JS($path1);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1, $result);
|
||||
|
||||
// multiple files in constructor
|
||||
$minifier = new Minify\JS($path1, $path2);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1.';'.$content2, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$path1 = __DIR__.'/sample/source/script1.js';
|
||||
$path2 = __DIR__.'/sample/source/script2.js';
|
||||
$content1 = file_get_contents($path1);
|
||||
$content2 = file_get_contents($path2);
|
||||
|
||||
// 1 source in add
|
||||
$minifier = new Minify\JS();
|
||||
$minifier->add($content1);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1, $result);
|
||||
|
||||
// multiple sources in add
|
||||
$minifier = new Minify\JS();
|
||||
$minifier->add($content1);
|
||||
$minifier->add($content2);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1.';'.$content2, $result);
|
||||
|
||||
// file in add
|
||||
$minifier = new Minify\JS();
|
||||
$minifier->add($path1);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1, $result);
|
||||
|
||||
// multiple files in add
|
||||
$minifier = new Minify\JS();
|
||||
$minifier->add($path1);
|
||||
$minifier->add($path2);
|
||||
$result = $minifier->minify();
|
||||
|
||||
$this->assertEquals($content1.';'.$content2, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
// content greater than PHP_MAXPATHLEN
|
||||
// https://github.com/matthiasmullie/minify/issues/90
|
||||
$content = rtrim(str_repeat('var a="b";', 500), ';');
|
||||
|
||||
$minifier = new Minify\JS($content);
|
||||
|
||||
$this->assertEquals($minifier->minify(), $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$path = __DIR__.'/sample/source/script1.js';
|
||||
$content = file_get_contents($path);
|
||||
$savePath = __DIR__.'/sample/target/script1.js';
|
||||
|
||||
$minifier = new Minify\JS($path);
|
||||
$minifier->minify($savePath);
|
||||
|
||||
$this->assertEquals(file_get_contents($savePath), $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function gzip()
|
||||
{
|
||||
$path = __DIR__.'/sample/source/script1.js';
|
||||
$content = file_get_contents($path);
|
||||
$savePath = __DIR__.'/sample/target/script1.js.gz';
|
||||
|
||||
$minifier = new Minify\JS($path);
|
||||
$minifier->gzip($savePath, 9);
|
||||
|
||||
$this->assertEquals(file_get_contents($savePath), gzencode($content, 9, FORCE_GZIP));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cache()
|
||||
{
|
||||
$path = __DIR__.'/sample/source/script1.js';
|
||||
$content = file_get_contents($path);
|
||||
|
||||
$cache = new MemoryStore();
|
||||
$pool = new Pool($cache);
|
||||
$item = $pool->getItem('cache-script1');
|
||||
|
||||
$minifier = new Minify\JS($path);
|
||||
$item = $minifier->cache($item);
|
||||
|
||||
$this->assertEquals($item->get(), $content);
|
||||
}
|
||||
}
|
||||
670
lib/tests/js/JSTest.php
Normal file
670
lib/tests/js/JSTest.php
Normal file
@ -0,0 +1,670 @@
|
||||
<?php
|
||||
|
||||
use MatthiasMullie\Minify;
|
||||
|
||||
/**
|
||||
* JS minifier test case.
|
||||
*/
|
||||
class JSTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var Minify\JS
|
||||
*/
|
||||
private $minifier;
|
||||
|
||||
/**
|
||||
* Prepares the environment before running a test.
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// override save method, there's no point in writing the result out here
|
||||
$this->minifier = $this->getMockBuilder('\MatthiasMullie\Minify\JS')
|
||||
->setMethods(array('save'))
|
||||
->getMock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the environment after running a test.
|
||||
*/
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->minifier = null;
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test JS minifier rules, provided by dataProvider.
|
||||
*
|
||||
* @test
|
||||
* @dataProvider dataProvider
|
||||
*/
|
||||
public function minify($input, $expected)
|
||||
{
|
||||
$input = (array) $input;
|
||||
foreach ($input as $js) {
|
||||
$this->minifier->add($js);
|
||||
}
|
||||
$result = $this->minifier->minify();
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array [input, expected result]
|
||||
*/
|
||||
public function dataProvider()
|
||||
{
|
||||
$tests = array();
|
||||
|
||||
// escaped quotes should not terminate string
|
||||
$tests[] = array(
|
||||
'alert("Escaped quote which is same as string quotes: \"; should not match")',
|
||||
'alert("Escaped quote which is same as string quotes: \"; should not match")',
|
||||
);
|
||||
|
||||
// backtick string (allow string interpolation)
|
||||
$tests[] = array(
|
||||
'var str=`Hi, ${name}`',
|
||||
'var str=`Hi, ${name}`',
|
||||
);
|
||||
|
||||
// regex delimiters need to be treated as strings
|
||||
// (two forward slashes could look like a comment)
|
||||
$tests[] = array(
|
||||
'/abc\/def\//.test("abc")',
|
||||
'/abc\/def\//.test("abc")',
|
||||
);
|
||||
$tests[] = array(
|
||||
'var a = /abc\/def\//.test("abc")',
|
||||
'var a=/abc\/def\//.test("abc")',
|
||||
);
|
||||
|
||||
// don't confuse multiple slashes for regexes
|
||||
$tests[] = array(
|
||||
'a = b / c; d = e / f',
|
||||
'a=b/c;d=e/f',
|
||||
);
|
||||
|
||||
// mixture of quotes starting in comment/regex, to make sure strings are
|
||||
// matched correctly, not inside comment/regex.
|
||||
$tests[] = array(
|
||||
'/abc"def/.test("abc")',
|
||||
'/abc"def/.test("abc")',
|
||||
);
|
||||
$tests[] = array(
|
||||
'/* Bogus " */var test="test";',
|
||||
'var test="test"',
|
||||
);
|
||||
|
||||
// replace comments
|
||||
$tests[] = array(
|
||||
'/* This is a JS comment */',
|
||||
'',
|
||||
);
|
||||
|
||||
// make sure no ; is added in places it shouldn't
|
||||
$tests[] = array(
|
||||
'if(true){}else{}',
|
||||
'if(!0){}else{}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'do{i++}while(i<1)',
|
||||
'do{i++}while(i<1)',
|
||||
);
|
||||
|
||||
$tests[] = array(
|
||||
'if(true)statement;else statement',
|
||||
'if(!0)statement;else statement',
|
||||
);
|
||||
|
||||
$tests[] = array(
|
||||
'for ( i = 0; ; i++ ) statement',
|
||||
'for(i=0;;i++)statement',
|
||||
);
|
||||
$tests[] = array(
|
||||
'for (i = 0; (i < 10); i++) statement',
|
||||
'for(i=0;(i<10);i++)statement',
|
||||
);
|
||||
$tests[] = array(
|
||||
'alert("test");;alert("test2")',
|
||||
'alert("test");alert("test2")',
|
||||
);
|
||||
$tests[] = array(
|
||||
'-1
|
||||
+2',
|
||||
'-1+2',
|
||||
);
|
||||
$tests[] = array(
|
||||
'-1+
|
||||
2',
|
||||
'-1+2',
|
||||
);
|
||||
$tests[] = array(
|
||||
'alert("this is a test");',
|
||||
'alert("this is a test")',
|
||||
);
|
||||
|
||||
// test where newline should be preserved (for ASI) or semicolon added
|
||||
$tests[] = array(
|
||||
'function(){console.log("this is a test");}',
|
||||
'function(){console.log("this is a test")}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'alert("this is a test")
|
||||
alert("this is another test")',
|
||||
'alert("this is a test")
|
||||
alert("this is another test")',
|
||||
);
|
||||
$tests[] = array(
|
||||
'a=b+c
|
||||
d=e+f',
|
||||
'a=b+c
|
||||
d=e+f',
|
||||
);
|
||||
$tests[] = array(
|
||||
'a++
|
||||
|
||||
++b',
|
||||
'a++
|
||||
++b',
|
||||
);
|
||||
$tests[] = array(
|
||||
'!a
|
||||
!b',
|
||||
'!a
|
||||
!b',
|
||||
);
|
||||
$tests[] = array(
|
||||
// don't confuse with 'if'
|
||||
'digestif
|
||||
(true)
|
||||
statement',
|
||||
'digestif(!0)
|
||||
statement',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if
|
||||
(
|
||||
(
|
||||
true
|
||||
)
|
||||
&&
|
||||
(
|
||||
true
|
||||
)
|
||||
)
|
||||
statement',
|
||||
'if((!0)&&(!0))
|
||||
statement',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if
|
||||
(
|
||||
true
|
||||
)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
}',
|
||||
'if(!0){}
|
||||
else{}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'do
|
||||
{
|
||||
i++
|
||||
}
|
||||
while
|
||||
(
|
||||
i<1
|
||||
)',
|
||||
'do{i++}
|
||||
while(i<1)',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if ( true )
|
||||
statement
|
||||
else
|
||||
statement',
|
||||
'if(!0)
|
||||
statement
|
||||
else statement',
|
||||
);
|
||||
|
||||
// test if whitespace around keywords is properly collapsed
|
||||
$tests[] = array(
|
||||
'var
|
||||
variable
|
||||
=
|
||||
"value";',
|
||||
'var variable="value"',
|
||||
);
|
||||
$tests[] = array(
|
||||
'var variable = {
|
||||
test:
|
||||
{
|
||||
}
|
||||
}',
|
||||
'var variable={test:{}}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if ( true ) {
|
||||
} else {
|
||||
}',
|
||||
'if(!0){}else{}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'53 instanceof String',
|
||||
'53 instanceof String',
|
||||
);
|
||||
|
||||
// remove whitespace around operators
|
||||
$tests[] = array(
|
||||
'a = 1 + 2',
|
||||
'a=1+2',
|
||||
);
|
||||
$tests[] = array(
|
||||
'object . property',
|
||||
'object.property',
|
||||
);
|
||||
$tests[] = array(
|
||||
'object
|
||||
.property',
|
||||
'object.property',
|
||||
);
|
||||
$tests[] = array(
|
||||
'alert ( "this is a test" );',
|
||||
'alert("this is a test")',
|
||||
);
|
||||
|
||||
// mix of ++ and +: three consecutive +es will be interpreted as ++ +
|
||||
$tests[] = array(
|
||||
'a++ +b',
|
||||
'a++ +b',
|
||||
);
|
||||
$tests[] = array(
|
||||
'a+ ++b',
|
||||
'a+ ++b', // +++ would actually be allowed as well
|
||||
);
|
||||
|
||||
// SyntaxError: identifier starts immediately after numeric literal
|
||||
$tests[] = array(
|
||||
'42 .toString()',
|
||||
'42 .toString()',
|
||||
);
|
||||
|
||||
// add comment in between whitespace that needs to be stripped
|
||||
$tests[] = array(
|
||||
'object
|
||||
// haha, some comment, just to make things harder!
|
||||
.property',
|
||||
'object.property',
|
||||
);
|
||||
|
||||
// add comment in between whitespace that needs to be stripped
|
||||
$tests[] = array(
|
||||
'var test=true,test2=false',
|
||||
'var test=!0,test2=!1',
|
||||
);
|
||||
$tests[] = array(
|
||||
'var testtrue="testing if true as part of varname is ignored as it should"',
|
||||
'var testtrue="testing if true as part of varname is ignored as it should"',
|
||||
);
|
||||
|
||||
// random bits of code that tripped errors during development
|
||||
$tests[] = array(
|
||||
'
|
||||
// check if it isn\'t a text-element
|
||||
if(currentElement.attr(\'type\') != \'text\')
|
||||
{
|
||||
// remove the current one
|
||||
currentElement.remove();
|
||||
}
|
||||
|
||||
// already a text element
|
||||
else newElement = currentElement;
|
||||
',
|
||||
'if(currentElement.attr(\'type\')!=\'text\'){currentElement.remove()}
|
||||
else newElement=currentElement',
|
||||
);
|
||||
$tests[] = array(
|
||||
'var jsBackend =
|
||||
{
|
||||
debug: false,
|
||||
current: {}
|
||||
}',
|
||||
'var jsBackend={debug:!1,current:{}}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'var utils =
|
||||
{
|
||||
debug: false
|
||||
}
|
||||
utils.array =
|
||||
{
|
||||
}',
|
||||
'var utils={debug:!1}
|
||||
utils.array={}',
|
||||
);
|
||||
$tests[] = array(
|
||||
'rescape = /\'|\\\\/g,
|
||||
|
||||
// blablabla here was some more code but the point was that somewhere
|
||||
// down below, there would be a closing quote which would cause the
|
||||
// regex (confused for escaped closing tag) not to be recognized,
|
||||
// taking the opening single quote & looking for a string.
|
||||
// So here\'s <-- the closing quote
|
||||
runescape = \'blabla\'',
|
||||
'rescape=/\'|\\\\/g,runescape=\'blabla\'',
|
||||
);
|
||||
$tests[] = array(
|
||||
'var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/)',
|
||||
'var rsingleTag=(/^<(\w+)\s*\/?>(?:<\/\1>|)$/)',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if (this.sliding) return this.$element.one(\'slid.bs.carousel\', function () { that.to(pos) }) // yes, "slid"
|
||||
if (activeIndex == pos) return this.pause().cycle()',
|
||||
'if(this.sliding)return this.$element.one(\'slid.bs.carousel\',function(){that.to(pos)})
|
||||
if(activeIndex==pos)return this.pause().cycle()',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if (e.which == 38 && index > 0) index-- // up
|
||||
if (e.which == 40 && index < $items.length - 1) index++ // down',
|
||||
'if(e.which==38&&index>0)index--
|
||||
if(e.which==40&&index<$items.length-1)index++',
|
||||
);
|
||||
|
||||
// replace associative array key references by property notation
|
||||
$tests[] = array(
|
||||
'array["key"][\'key2\']',
|
||||
'array.key.key2',
|
||||
);
|
||||
$tests[] = array(
|
||||
'array[ "key" ][ \'key2\' ]',
|
||||
'array.key.key2',
|
||||
);
|
||||
$tests[] = array(
|
||||
'array["a","b","c"]',
|
||||
'array["a","b","c"]',
|
||||
);
|
||||
//
|
||||
$tests[] = array(
|
||||
"['loader']",
|
||||
"['loader']",
|
||||
);
|
||||
$tests[] = array(
|
||||
'array["dont-replace"][\'key2\']',
|
||||
'array["dont-replace"].key2',
|
||||
);
|
||||
|
||||
// shorten bools
|
||||
$tests[] = array(
|
||||
'while(true){break}',
|
||||
'for(;;){break}',
|
||||
);
|
||||
// make sure we don't get "missing while after do-loop body"
|
||||
$tests[] = array(
|
||||
'do{break}while(true)',
|
||||
'do{break}while(!0)',
|
||||
);
|
||||
$tests[] = array(
|
||||
"do break\nwhile(true)",
|
||||
"do break\nwhile(!0)",
|
||||
);
|
||||
$tests[] = array(
|
||||
"do{break}while(true){alert('test')}",
|
||||
"do{break}while(!0){alert('test')}",
|
||||
);
|
||||
$tests[] = array(
|
||||
"do break\nwhile(true){alert('test')}",
|
||||
"do break\nwhile(!0){alert('test')}",
|
||||
);
|
||||
// nested do-while & while
|
||||
$tests[] = array(
|
||||
"do{while(true){break}break}while(true){alert('test')}",
|
||||
"do{for(;;){break}break}while(!0){alert('test')}",
|
||||
);
|
||||
$tests[] = array(
|
||||
"do{while(true){break}break}while(true){alert('test')}while(true){break}",
|
||||
"do{for(;;){break}break}while(!0){alert('test')}for(;;){break}",
|
||||
);
|
||||
$tests[] = array(
|
||||
"do{while(true){break}break}while(true){alert('test')}while(true){break}do{while(true){break}break}while(true){alert('test')}while(true){break}",
|
||||
"do{for(;;){break}break}while(!0){alert('test')}for(;;){break}do{for(;;){break}break}while(!0){alert('test')}for(;;){break}",
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/10
|
||||
$tests[] = array(
|
||||
'// first mutation patch
|
||||
// second mutation patch
|
||||
// third mutation patch
|
||||
// fourth mutation patch',
|
||||
'',
|
||||
);
|
||||
$tests[] = array(
|
||||
'/////////////////////////
|
||||
// first mutation patch
|
||||
// second mutation patch
|
||||
// third mutation patch
|
||||
// fourth mutation patch
|
||||
/////////////////////////',
|
||||
'',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/14
|
||||
$tests[] = array(
|
||||
'function foo (a, b)
|
||||
{
|
||||
return a / b;
|
||||
}
|
||||
function foo (a, b)
|
||||
{
|
||||
return a / b;
|
||||
}',
|
||||
'function foo(a,b){return a/b}
|
||||
function foo(a,b){return a/b}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/15
|
||||
$tests[] = array(
|
||||
'if ( !data.success )
|
||||
deferred.reject(); else
|
||||
deferred.resolve(data);',
|
||||
'if(!data.success)
|
||||
deferred.reject();else deferred.resolve(data)',
|
||||
);
|
||||
$tests[] = array(
|
||||
"if ( typeof jQuery === 'undefined' )
|
||||
throw new Error('.editManager.js: jQuery is required and must be loaded first');",
|
||||
"if(typeof jQuery==='undefined')
|
||||
throw new Error('.editManager.js: jQuery is required and must be loaded first')",
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/27
|
||||
$tests[] = array(
|
||||
'$.expr[":"]',
|
||||
'$.expr[":"]',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/31
|
||||
$tests[] = array(
|
||||
"$(_this).attr('src',this.src).trigger('adapt',['loader'])",
|
||||
"$(_this).attr('src',this.src).trigger('adapt',['loader'])",
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/33
|
||||
$tests[] = array(
|
||||
'$.fn.alert = Plugin
|
||||
$.fn.alert.Constructor = Alert',
|
||||
'$.fn.alert=Plugin
|
||||
$.fn.alert.Constructor=Alert',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/34
|
||||
$tests[] = array(
|
||||
'a.replace("\\\\","");hi="This is a string"',
|
||||
'a.replace("\\\\","");hi="This is a string"',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/35
|
||||
$tests[] = array(
|
||||
array(
|
||||
'// script that ends with comment',
|
||||
'var test=1',
|
||||
),
|
||||
'var test=1',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/37
|
||||
$tests[] = array(
|
||||
'function () { ;;;;;;;; }',
|
||||
'function(){}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/40
|
||||
$tests[] = array(
|
||||
'for(v=1,_=b;;){}',
|
||||
'for(v=1,_=b;;){}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/41
|
||||
$tests[] = array(
|
||||
"conf.zoomHoverIcons['default']",
|
||||
"conf.zoomHoverIcons['default']",
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/42
|
||||
$tests[] = array(
|
||||
'for(i=1;i<2;i++);',
|
||||
'for(i=1;i<2;i++);',
|
||||
);
|
||||
$tests[] = array(
|
||||
'if(1){for(i=1;i<2;i++);}',
|
||||
'if(1){for(i=1;i<2;i++);}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/43
|
||||
$tests[] = array(
|
||||
'{"key":"3","key2":"value","key3":"3"}',
|
||||
'{"key":"3","key2":"value","key3":"3"}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/44
|
||||
$tests[] = array(
|
||||
'return ["x"]',
|
||||
'return["x"]',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/50
|
||||
$tests[] = array(
|
||||
'do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(true)}',
|
||||
'do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(!0)}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/53
|
||||
$tests[] = array(
|
||||
'a.validator.addMethod("accept", function (b, c, d) {
|
||||
var e, f, g = "string" == typeof d ?
|
||||
d.replace(/\s/g, "").replace(/,/g, "|") :
|
||||
"image/*", h = this.optional(c);
|
||||
if (h)return h;
|
||||
if ("file" === a(c).attr("type") && (g = g.replace(/\*/g, ".*"), c.files && c.files.length))
|
||||
for (e = 0; e < c.files.length; e++)
|
||||
if (f = c.files[e], !f.type.match(new RegExp(".?(" + g + ")$", "i")))
|
||||
return !1;
|
||||
return !0
|
||||
}',
|
||||
'a.validator.addMethod("accept",function(b,c,d){var e,f,g="string"==typeof d?d.replace(/\s/g,"").replace(/,/g,"|"):"image/*",h=this.optional(c);if(h)return h;if("file"===a(c).attr("type")&&(g=g.replace(/\*/g,".*"),c.files&&c.files.length))
|
||||
for(e=0;e<c.files.length;e++)
|
||||
if(f=c.files[e],!f.type.match(new RegExp(".?("+g+")$","i")))
|
||||
return !1;return !0}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/54
|
||||
$tests[] = array(
|
||||
'function a() {
|
||||
if (true)
|
||||
return
|
||||
if (false)
|
||||
return
|
||||
}',
|
||||
'function a(){if(!0)
|
||||
return
|
||||
if(!1)
|
||||
return}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/56
|
||||
$tests[] = array(
|
||||
'var timeRegex = /^([2][0-3]|[01]?[0-9])(:[0-5][0-9])?$/
|
||||
if (start_time.match(timeRegex) == null) {}',
|
||||
'var timeRegex=/^([2][0-3]|[01]?[0-9])(:[0-5][0-9])?$/
|
||||
if(start_time.match(timeRegex)==null){}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/58
|
||||
// stripped of redundant code to expose problem case
|
||||
$tests[] = array(
|
||||
<<<'BUG'
|
||||
function inspect() {
|
||||
escapedString.replace(/abc/g, '\\\'');
|
||||
}
|
||||
function isJSON() {
|
||||
str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
|
||||
}
|
||||
BUG
|
||||
,
|
||||
<<<'BUG'
|
||||
function inspect(){escapedString.replace(/abc/g,'\\\'')}
|
||||
function isJSON(){str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']')}
|
||||
BUG
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/59
|
||||
$tests[] = array(
|
||||
'isPath:function(e) {
|
||||
return /\//.test(e);
|
||||
}',
|
||||
'isPath:function(e){return/\//.test(e)}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/64
|
||||
$tests[] = array(
|
||||
' var d3_nsPrefix = {
|
||||
svg: "http://www.w3.org/2000/svg",
|
||||
xhtml: "http://www.w3.org/1999/xhtml",
|
||||
xlink: "http://www.w3.org/1999/xlink",
|
||||
xml: "http://www.w3.org/XML/1998/namespace",
|
||||
xmlns: "http://www.w3.org/2000/xmlns/"
|
||||
};',
|
||||
'var d3_nsPrefix={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/66
|
||||
$tests[] = array(
|
||||
"$(coming.wrap).bind('onReset', function () {
|
||||
try {
|
||||
$(this).find('iframe').hide().attr('src', '//about:blank').end().empty();
|
||||
} catch (e) {}
|
||||
});",
|
||||
"$(coming.wrap).bind('onReset',function(){try{\$(this).find('iframe').hide().attr('src','//about:blank').end().empty()}catch(e){}})",
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/89
|
||||
$tests[] = array(
|
||||
'for(;;ja||(ja=true)){}',
|
||||
'for(;;ja||(ja=!0)){}',
|
||||
);
|
||||
|
||||
// https://github.com/matthiasmullie/minify/issues/91
|
||||
$tests[] = array(
|
||||
'if(true){if(true)console.log("test")else;}',
|
||||
'if(!0){if(!0)console.log("test")}',
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
}
|
||||
1
lib/tests/js/sample/source/script1.js
Normal file
1
lib/tests/js/sample/source/script1.js
Normal file
@ -0,0 +1 @@
|
||||
var test=1
|
||||
1
lib/tests/js/sample/source/script2.js
Normal file
1
lib/tests/js/sample/source/script2.js
Normal file
@ -0,0 +1 @@
|
||||
var test=2
|
||||
Loading…
x
Reference in New Issue
Block a user