recommited fixed lib

This commit is contained in:
Ulf Gebhardt 2016-01-19 21:43:56 +01:00
parent e6a99daa6d
commit 30f29c3607
58 changed files with 3426 additions and 0 deletions

9
lib/.gitattributes vendored Normal file
View 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
View File

@ -0,0 +1,10 @@
/build
/docs
/vendor
/.idea
/.project
/.cache
/.buildpath
/.settings
.*.swp
.DS_Store

18
lib/.travis.yml Normal file
View 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
View 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
View File

@ -0,0 +1,140 @@
# Minify
[![Build status](https://api.travis-ci.org/matthiasmullie/minify.svg?branch=master)](https://travis-ci.org/matthiasmullie/minify)
[![Code coverage](http://img.shields.io/codecov/c/github/matthiasmullie/minify.svg)](https://codecov.io/github/matthiasmullie/minify)
[![Code quality](http://img.shields.io/scrutinizer/g/matthiasmullie/minify.svg)](https://scrutinizer-ci.com/g/matthiasmullie/minify)
[![Latest version](http://img.shields.io/packagist/v/matthiasmullie/minify.svg)](https://packagist.org/packages/matthiasmullie/minify)
[![Downloads total](http://img.shields.io/packagist/dt/matthiasmullie/minify.svg)](https://packagist.org/packages/matthiasmullie/minify)
[![License](http://img.shields.io/packagist/l/matthiasmullie/minify.svg)](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
View 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
View 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
View 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"
]
}

View File

@ -0,0 +1,7 @@
in
public
extends
private
protected
implements
instanceof

View 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

View 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

View File

@ -0,0 +1,45 @@
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
.
[
]
?
:
,
;
(
)
{
}

View File

@ -0,0 +1,43 @@
+
-
*
/
%
=
+=
-=
*=
/=
%=
<<=
>>=
>>>=
&=
^=
|=
&
|
^
~
<<
>>
>>>
==
===
!=
!==
>
<
>=
<=
&&
||
!
.
[
?
:
,
;
(
{

5
lib/makefile Normal file
View 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
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

382
lib/src/Minify.php Normal file
View 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
View File

@ -0,0 +1,3 @@
<?php
require __DIR__.'/../vendor/autoload.php';

594
lib/tests/css/CSSTest.php Normal file
View 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;
}
}

View File

@ -0,0 +1,3 @@
body {
color: red;
}

View File

@ -0,0 +1,3 @@
body {
color: red;
}

View File

@ -0,0 +1 @@
@import url(import.css);

View File

@ -0,0 +1 @@
@import 'import.css';

View File

@ -0,0 +1,2 @@
@import url(import.css);
@import 'import.css';

View File

@ -0,0 +1,2 @@
@import url(import.css) only screen;
@import 'import.css' only screen;

View File

@ -0,0 +1,2 @@
@import url(import.css);
@import 'import.css';

View File

@ -0,0 +1 @@
@import url(index6b.css);

View File

@ -0,0 +1 @@
@import url(import.css);

View File

@ -0,0 +1 @@
@import url(/absolute/path/to/stylesheet.css);

View File

@ -0,0 +1 @@
@import url(http://fonts.googleapis.com/css?family=Lato:100,300,400,700,900|Droid+Sans:400,700);

View File

@ -0,0 +1 @@
@import url( 'http://myurl.de');

View File

@ -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;
}

View File

@ -0,0 +1 @@
@import url(../relative.css);

View File

@ -0,0 +1 @@
@import url(../target/stylesheet.css);

View File

@ -0,0 +1,3 @@
body {
background: green;
}

View File

@ -0,0 +1,2 @@
@import "file.css";
@import url(http://localhost/file.css);

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View File

@ -0,0 +1 @@
body { background: url(file.png); }

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View 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) }

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

View File

@ -0,0 +1 @@
../other_path/assets/asset.bmp

View File

@ -0,0 +1 @@
other_path/assets

View File

@ -0,0 +1 @@
@import url(imports/symlink.css);

View File

@ -0,0 +1 @@
@import url(imports_symlink/import.css);

View File

@ -0,0 +1 @@
../other_path/imports/import.css

View File

@ -0,0 +1 @@
other_path/imports

View File

@ -0,0 +1 @@
body{background-url:url(assets/symlink.bmp)}

View File

@ -0,0 +1 @@
body{background-url:url(assets_symlink/asset.bmp)}

View 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
View 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;
}
}

View File

@ -0,0 +1 @@
var test=1

View File

@ -0,0 +1 @@
var test=2