├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Eusonlito │ └── LaravelPacker │ │ ├── Exceptions │ │ ├── DirNotExistException.php │ │ ├── DirNotWritableException.php │ │ ├── FileNotExistException.php │ │ ├── FileNotWritableException.php │ │ └── InvalidArgumentException.php │ │ ├── Facade.php │ │ ├── Packer.php │ │ ├── PackerServiceProvider.php │ │ ├── Processors │ │ ├── CSS │ │ │ ├── Colors.php │ │ │ ├── Minify.php │ │ │ └── Utils.php │ │ └── JS │ │ │ └── Minify.php │ │ └── Providers │ │ ├── CSS.php │ │ ├── IMG.php │ │ ├── JS.php │ │ ├── ProviderBase.php │ │ └── ProviderInterface.php ├── config │ └── config.php └── images │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ └── 8.jpg └── tests ├── Base.php ├── CSSTest.php ├── IMGTest.php ├── JSTest.php └── resources ├── image-1.png ├── image-2.jpg ├── scripts-1.js ├── scripts-2.js ├── styles-1.css ├── styles-2.css └── styles-3.css /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | .DS_Store 4 | *.sh 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | - 7.3 6 | - 7.4 7 | 8 | before_script: 9 | - composer install --dev --prefer-source --no-interaction 10 | 11 | script: vendor/bin/phpunit 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cees van Egmond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel >= 5 Packer 2 | 3 | [![Build Status](https://travis-ci.org/eusonlito/laravel-Packer.svg?branch=master)](https://travis-ci.org/eusonlito/laravel-Packer) 4 | [![Latest Stable Version](https://poser.pugx.org/laravel/packer/v/stable.png)](https://packagist.org/packages/laravel/packer) 5 | [![Total Downloads](https://poser.pugx.org/laravel/packer/downloads.png)](https://packagist.org/packages/laravel/packer) 6 | [![License](https://poser.pugx.org/laravel/packer/license.png)](https://packagist.org/packages/laravel/packer) 7 | 8 | Inspired by: https://github.com/ceesvanegmond/minify 9 | 10 | With this package you can pack and minify your existing css and javascript files. This process can be a little tough, this package simplies this process and automates it. 11 | 12 | Also, you can resize/crop images to adapt thumbnails into your layouts. 13 | 14 | If you want a Laravel <= 4.2 compatible version, please use `v4.2` branch. 15 | 16 | ## Installation 17 | 18 | Begin by installing this package through Composer. 19 | 20 | ```js 21 | { 22 | "require": { 23 | "eusonlito/laravel-packer": "master-dev" 24 | } 25 | } 26 | ``` 27 | 28 | ### Laravel installation 29 | 30 | ```php 31 | 32 | // config/app.php 33 | 34 | 'providers' => [ 35 | '...', 36 | 'Eusonlito\LaravelPacker\PackerServiceProvider', 37 | ]; 38 | 39 | 'aliases' => [ 40 | '...', 41 | 'Packer' => 'Eusonlito\LaravelPacker\Facade', 42 | ]; 43 | ``` 44 | 45 | Publish the config file: 46 | 47 | ``` 48 | php artisan vendor:publish --provider="Eusonlito\LaravelPacker\PackerServiceProvider" 49 | ``` 50 | 51 | Now you have a ```Packer``` facade available. 52 | 53 | #### CSS 54 | 55 | ```php 56 | // resources/views/hello.blade.php 57 | 58 | 59 | 60 | // Pack a simple file 61 | {!! Packer::css('/css/main.css', '/storage/cache/css/main.css') !!} 62 | 63 | // Pack a simple file using cache_folder option as storage folder to packed file 64 | {!! Packer::css('/css/main.css', 'css/main.css') !!} 65 | 66 | // Packing multiple files 67 | {!! Packer::css(['/css/main.css', '/css/bootstrap.css'], '/storage/cache/css/styles.css') !!} 68 | 69 | // Packing multiple files using cache_folder option as storage folder to packed file 70 | {!! Packer::css(['/css/main.css', '/css/bootstrap.css'], 'css/styles.css') !!} 71 | 72 | // Packing multiple files with autonaming based 73 | {!! Packer::css(['/css/main.css', '/css/bootstrap.css'], '/storage/cache/css/') !!} 74 | 75 | // pack and combine all css files in given folder 76 | {!! Packer::cssDir('/css/', '/storage/cache/css/all.css') !!} 77 | 78 | // pack and combine all css files in given folder using cache_folder option as storage folder to packed file 79 | {!! Packer::cssDir('/css/', 'css/all.css') !!} 80 | 81 | // Packing multiple folders 82 | {!! Packer::cssDir(['/css/', '/theme/'], '/storage/cache/css/all.css') !!} 83 | 84 | // Packing multiple folders with recursive search 85 | {!! Packer::cssDir(['/css/', '/theme/'], '/storage/cache/css/all.css', true) !!} 86 | 87 | // Packing multiple folders with recursive search and autonaming 88 | {!! Packer::cssDir(['/css/', '/theme/'], '/storage/cache/css/', true) !!} 89 | 90 | // Packing multiple folders with recursive search and autonaming using cache_folder option as storage folder to packed file 91 | {!! Packer::cssDir(['/css/', '/theme/'], 'css/', true) !!} 92 | 93 | 94 | ``` 95 | 96 | CSS `url()` values will be converted to absolute path to avoid file references problems. 97 | 98 | #### Javascript 99 | 100 | ```php 101 | // resources/views/hello.blade.php 102 | 103 | 104 | 105 | ... 106 | // Pack a simple file 107 | {!! Packer::js('/js/main.js', '/storage/cache/js/main.js') !!} 108 | 109 | // Pack a simple file using cache_folder option as storage folder to packed file 110 | {!! Packer::js('/js/main.js', 'js/main.js') !!} 111 | 112 | // Packing multiple files 113 | {!! Packer::js(['/js/main.js', '/js/bootstrap.js'], '/storage/cache/js/styles.js') !!} 114 | 115 | // Packing multiple files using cache_folder option as storage folder to packed file 116 | {!! Packer::js(['/js/main.js', '/js/bootstrap.js'], 'js/styles.js') !!} 117 | 118 | // Packing multiple files with autonaming based 119 | {!! Packer::js(['/js/main.js', '/js/bootstrap.js'], '/storage/cache/js/') !!} 120 | 121 | // pack and combine all js files in given folder 122 | {!! Packer::jsDir('/js/', '/storage/cache/js/all.js') !!} 123 | 124 | // pack and combine all js files in given folder using cache_folder option as storage folder to packed file 125 | {!! Packer::jsDir('/js/', 'js/all.js') !!} 126 | 127 | // Packing multiple folders 128 | {!! Packer::jsDir(['/js/', '/theme/'], '/storage/cache/js/all.js') !!} 129 | 130 | // Packing multiple folders with recursive search 131 | {!! Packer::jsDir(['/js/', '/theme/'], '/storage/cache/js/all.js', true) !!} 132 | 133 | // Packing multiple folders with recursive search and autonaming 134 | {!! Packer::jsDir(['/js/', '/theme/'], '/storage/cache/js/', true) !!} 135 | 136 | // Packing multiple folders with recursive search and autonaming using cache_folder option as storage folder to packed file 137 | {!! Packer::jsDir(['/js/', '/theme/'], 'js/', true) !!} 138 | 139 | 140 | ``` 141 | 142 | #### Images 143 | All transform options availables at https://github.com/oscarotero/imageCow 144 | 145 | ```php 146 | // resources/views/hello.blade.php 147 | 148 | 149 | 150 | ... 151 | // Set width size to 500px using cache_folder default parameter (from settings) 152 | 153 | 154 | // Crop image to 200px square with custom cache folder (full path) 155 | 156 | 157 | // Crop image to 200px square center middle with custom cache folder (using cache_folder as base) 158 | 159 | 160 | // Crop image to 200px square center top with custom cache folder (using cache_folder as base) 161 | 162 | 163 | 164 | 165 | ``` 166 | 167 | ### Config 168 | 169 | ```php 170 | return array( 171 | 172 | /* 173 | |-------------------------------------------------------------------------- 174 | | Current environment 175 | |-------------------------------------------------------------------------- 176 | | 177 | | Set the current server environment. Leave empty to laravel autodetect 178 | | 179 | */ 180 | 181 | 'environment' => '', 182 | 183 | /* 184 | |-------------------------------------------------------------------------- 185 | | App environments to not pack 186 | |-------------------------------------------------------------------------- 187 | | 188 | | These environments will not be minified and all individual files are 189 | | returned 190 | | 191 | */ 192 | 193 | 'ignore_environments' => ['local'], 194 | 195 | /* 196 | |-------------------------------------------------------------------------- 197 | | Public accessible path 198 | |-------------------------------------------------------------------------- 199 | | 200 | | Set absolute folder path to public view from web. If you are using 201 | | laravel, this value will be set with public_path() function 202 | | 203 | */ 204 | 205 | 'public_path' => realpath(getenv('DOCUMENT_ROOT')), 206 | 207 | /* 208 | |-------------------------------------------------------------------------- 209 | | Asset absolute location 210 | |-------------------------------------------------------------------------- 211 | | 212 | | Set absolute URL location to asset folder. Many times will be same as 213 | | public_path but using absolute URL. If you are using laravel, this value 214 | | will be set with asset() function 215 | | 216 | */ 217 | 218 | 'asset' => 'http://'.getenv('SERVER_NAME').'/', 219 | 220 | /* 221 | |-------------------------------------------------------------------------- 222 | | Base folder to store packed files 223 | |-------------------------------------------------------------------------- 224 | | 225 | | If you are using relative paths to second paramenter in css and js 226 | | commands, this files will be created with this folder as base. 227 | | 228 | | This folder in relative to 'public_path' value 229 | | 230 | */ 231 | 232 | 'cache_folder' => '/storage/cache/', 233 | 234 | /* 235 | |-------------------------------------------------------------------------- 236 | | Check if some file to pack have a recent timestamp 237 | |-------------------------------------------------------------------------- 238 | | 239 | | Compare current packed file with all files to pack. If exists one more 240 | | recent than packed file, will be packed again with a new autogenerated 241 | | name. 242 | | 243 | */ 244 | 245 | 'check_timestamps' => true, 246 | 247 | /* 248 | |-------------------------------------------------------------------------- 249 | | Check if you want minify css files or only pack them together 250 | |-------------------------------------------------------------------------- 251 | | 252 | | You can check this option if you want to join and minify all css files or 253 | | only join files 254 | | 255 | */ 256 | 257 | 'css_minify' => true, 258 | 259 | /* 260 | |-------------------------------------------------------------------------- 261 | | Check if you want minify js files or only pack them together 262 | |-------------------------------------------------------------------------- 263 | | 264 | | You can check this option if you want to join and minify all js files or 265 | | only join files 266 | | 267 | */ 268 | 269 | 'js_minify' => true, 270 | 271 | /* 272 | |-------------------------------------------------------------------------- 273 | | Use fake images stored in src/images/ when original image does not exists 274 | |-------------------------------------------------------------------------- 275 | | 276 | | You can use fake images in your developments to avoid not existing 277 | | original images problems. Fake images are stored in src/images/ and used 278 | | with a rand 279 | | 280 | */ 281 | 282 | 'images_fake' => true 283 | ); 284 | ``` 285 | 286 | If you set the `'check_timestamps'` option, a timestamp value will be added to final filename. 287 | 288 | ### Using Packer outside Laravel 289 | 290 | ```php 291 | require (__DIR__.'/vendor/autoload.php'); 292 | 293 | // Check default settings 294 | $config = require (__DIR__.'/src/config/config.php'); 295 | 296 | $Packer = new Eusonlito\LaravelPacker\Packer($config); 297 | 298 | echo $Packer->css([ 299 | '/resources/css/styles-1.css', 300 | '/resources/css/styles-2.css' 301 | ], 'css/styles.css')->render(); 302 | 303 | echo $Packer->js('/resources/js/scripts.js', 'js/scripts.js')->render(); 304 | 305 | echo $Packer->js([ 306 | '/resources/js/scripts-1.js', 307 | '/resources/js/scripts-2.js' 308 | ], 'js/')->render(); 309 | ``` 310 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eusonlito/laravel-packer", 3 | "keywords": ["packer", "minify", "laravel"], 4 | "description": "A package for pack css and javascript files", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Lito", 9 | "email": "lito@eordes.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5", 14 | "imagecow/imagecow": "^2.4" 15 | }, 16 | "require-dev": { 17 | "phpunit/phpunit": "^7.5", 18 | "mikey179/vfsstream": "^1.6" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Eusonlito\\LaravelPacker\\": "src/Eusonlito/LaravelPacker/" 23 | }, 24 | "classmap": [ 25 | "src/Eusonlito/LaravelPacker/Processors/" 26 | ] 27 | }, 28 | "minimum-stability": "stable", 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "Eusonlito\\LaravelPacker\\PackerServiceProvider" 33 | ], 34 | "aliases": { 35 | "Packer": "Eusonlito\\LaravelPacker\\Facade" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Exceptions/DirNotExistException.php: -------------------------------------------------------------------------------- 1 | false, 46 | 'previous' => false 47 | ]; 48 | 49 | /** 50 | * @var array 51 | */ 52 | private $config = []; 53 | 54 | /** 55 | * @var object 56 | */ 57 | private $provider; 58 | 59 | /** 60 | * @var object; 61 | */ 62 | private static $instance; 63 | 64 | /** 65 | * @param array $config 66 | * @return object 67 | */ 68 | public static function getInstance(array $config) 69 | { 70 | return static::$instance ?: (static::$instance = new self($config)); 71 | } 72 | 73 | /** 74 | * @param array $config 75 | * @throws Exceptions\InvalidArgument 76 | * @return this 77 | */ 78 | public function __construct(array $config) 79 | { 80 | return $this->setConfig($config); 81 | } 82 | 83 | /** 84 | * @param array $config 85 | * @throws Exceptions\InvalidArgument 86 | * @throws Exceptions\DirNotExist 87 | * @return this 88 | */ 89 | public function setConfig(array $config) 90 | { 91 | $config = array_merge($this->config, $config); 92 | 93 | if (!isset($config['ignore_environments']) || !is_array($config['ignore_environments'])) { 94 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'ignore_environments')); 95 | } 96 | 97 | if (!isset($config['cache_folder'])) { 98 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'cache_folder')); 99 | } 100 | 101 | if (!isset($config['check_timestamps'])) { 102 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'check_timestamps')); 103 | } 104 | 105 | if (!isset($config['css_minify'])) { 106 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'css_minify')); 107 | } 108 | 109 | if (!isset($config['js_minify'])) { 110 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'js_minify')); 111 | } 112 | 113 | if (!isset($config['environment'])) { 114 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'environment')); 115 | } 116 | 117 | if (!isset($config['asset'])) { 118 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'asset')); 119 | } 120 | 121 | if (!isset($config['public_path'])) { 122 | throw new Exceptions\InvalidArgumentException(sprintf('Missing option %s', 'public_path')); 123 | } 124 | 125 | if (!is_dir($config['public_path'])) { 126 | throw new Exceptions\DirNotExistException(sprintf('Folder %s not exists', $config['public_path'])); 127 | } 128 | 129 | $this->config = $config; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * @param mixed $files 136 | * @param string $name 137 | * @param array $attributes 138 | * @return this 139 | */ 140 | public function js($files, $name, array $attributes = []) 141 | { 142 | $this->provider = new Js([ 143 | 'asset' => $this->config['asset'], 144 | 'minify' => $this->config['js_minify'], 145 | 'attributes' => $attributes 146 | ]); 147 | 148 | return $this->load('js', $files, $name); 149 | } 150 | 151 | /** 152 | * @param mixed $dir 153 | * @param string $name 154 | * @param boolean $recursive 155 | * @param array $attributes 156 | * @return this 157 | */ 158 | public function jsDir($dir, $name, $recursive = false, array $attributes = []) 159 | { 160 | $this->provider = new JS([ 161 | 'asset' => $this->config['asset'], 162 | 'minify' => $this->config['js_minify'], 163 | 'attributes' => $attributes 164 | ]); 165 | 166 | return $this->load('js', $this->scanDir('js', $dir, $recursive), $name); 167 | } 168 | 169 | /** 170 | * @param mixed $files 171 | * @param string $name 172 | * @param array $attributes 173 | * @return this 174 | */ 175 | public function css($files, $name, array $attributes = []) 176 | { 177 | $this->provider = new CSS([ 178 | 'asset' => $this->config['asset'], 179 | 'minify' => $this->config['css_minify'], 180 | 'attributes' => $attributes 181 | ]); 182 | 183 | return $this->load('css', $files, $name); 184 | } 185 | 186 | /** 187 | * @param mixed $dir 188 | * @param string $name 189 | * @param boolean $recursive 190 | * @param array $attributes 191 | * @return this 192 | */ 193 | public function cssDir($dir, $name, $recursive = false, array $attributes = []) 194 | { 195 | $this->provider = new CSS([ 196 | 'asset' => $this->config['asset'], 197 | 'minify' => $this->config['css_minify'], 198 | 'attributes' => $attributes 199 | ]); 200 | 201 | return $this->load('css', $this->scanDir('css', $dir, $recursive), $name); 202 | } 203 | 204 | /** 205 | * @param string $file 206 | * @param string $transform 207 | * @param string $new 208 | * @param array $attributes 209 | * @throws Exceptions\InvalidArgument 210 | * @return this 211 | */ 212 | public function img($file, $transform, $new = '', array $attributes = []) 213 | { 214 | if (!is_string($file)) { 215 | throw new Exceptions\InvalidArgumentException('img function only supports strings'); 216 | } 217 | 218 | $this->provider = new IMG([ 219 | 'asset' => $this->config['asset'], 220 | 'fake' => $this->config['images_fake'], 221 | 'transform' => $transform, 222 | 'quality' => (isset($this->config['quality']) ? $this->config['quality'] : null), 223 | 'attributes' => $attributes 224 | ]); 225 | 226 | if ($this->isRemote($file)) { 227 | $file = $this->downloadRemote($file); 228 | } 229 | 230 | if (!$this->provider->check($this->path('public', $file))) { 231 | $this->file = false; 232 | 233 | return $this; 234 | } 235 | 236 | $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); 237 | $name = $new ?: $this->getImgName($file, $transform); 238 | 239 | $this->force['previous'] = $this->force['current']; 240 | $this->force['current'] = true; 241 | 242 | return $this->load($ext, $file, $name); 243 | } 244 | 245 | private function getImgName($file, $transform) 246 | { 247 | if (preg_match('/^[^0-9]+([0-9]+),([0-9]+).*$/', $transform)) { 248 | $transform = preg_replace('/^[^0-9]+([0-9]+),([0-9]+).*$/', '$1x$2', $transform);; 249 | } elseif (preg_match('/^[^,]+,([0-9]+).*$/', $transform)) { 250 | $transform = preg_replace('/^[^,]+,([0-9]+).*$/', '$1', $transform);; 251 | } else { 252 | $transform = preg_replace('/^([^,]+).*$/', '$1', $transform); 253 | } 254 | 255 | $name = preg_replace('/[^a-z0-9\-\_\.]/', '', strtolower(basename($file))); 256 | 257 | return 'images/'.substr(md5($file.$transform), 0, 3).'/'.$transform.'_'.$name; 258 | } 259 | 260 | /** 261 | * @param string $ext 262 | * @param string $dir 263 | * @param boolean $recursive 264 | * @throws Exceptions\DirNotExist 265 | * @return array 266 | */ 267 | private function scanDir($ext, $dir, $recursive = false) 268 | { 269 | $files = []; 270 | 271 | if (is_array($dir)) { 272 | foreach ($dir as $each) { 273 | $files = array_merge($files, $this->scanDir('css', $each, $recursive)); 274 | } 275 | 276 | return $files; 277 | } 278 | 279 | $dir = $this->path('public', $dir); 280 | 281 | if (!is_dir($dir)) { 282 | throw new Exceptions\DirNotExistException(sprintf('Folder %s not exists', $dir)); 283 | } 284 | 285 | if ($recursive) { 286 | $Iterator = new RecursiveDirectoryIterator($dir); 287 | $Iterator = new RegexIterator(new RecursiveIteratorIterator($Iterator), '/\.'.$ext.'$/i', RegexIterator::MATCH); 288 | } else { 289 | $Iterator = new DirectoryIterator($dir); 290 | $Iterator = new RegexIterator(new IteratorIterator($Iterator), '/\.'.$ext.'$/i', RegexIterator::MATCH); 291 | } 292 | 293 | $public = $this->path('public'); 294 | 295 | foreach ($Iterator as $file) { 296 | $files[] = str_replace($public, '', $file->getPathname()); 297 | } 298 | 299 | return $files; 300 | } 301 | 302 | /** 303 | * @param string $type 304 | * @param mixed $file 305 | * @param string $name 306 | * @return this 307 | */ 308 | public function load($type, $files, $name) 309 | { 310 | $this->files = $this->downloadRemotes($files); 311 | 312 | if (strpos($name, '/') !== 0) { 313 | $name = $this->path('', $this->config['cache_folder'].'/'.$name); 314 | } 315 | 316 | if (preg_match('/\.'.$type.'$/i', $name)) { 317 | $this->storage = dirname($name).'/'; 318 | $this->name = basename($name); 319 | } else { 320 | $this->storage = $this->path('', $name.'/'); 321 | $this->name = md5(implode('', $this->files)).'.'.$type; 322 | } 323 | 324 | if ($this->files && !$this->isLocal() && ($this->config['check_timestamps'] === true)) { 325 | $this->name = $this->checkNewer($this->files, $this->name); 326 | } 327 | 328 | $this->file = $this->path('public', $this->storage.$this->name); 329 | 330 | return $this->process(); 331 | } 332 | 333 | /** 334 | * @param array $files 335 | * @param string $name 336 | * @return string 337 | */ 338 | protected function checkNewer($files, $name) 339 | { 340 | $filemtime = function ($file) { 341 | if (is_file($file = $this->path('public', $file))) { 342 | return filemtime($file); 343 | } 344 | }; 345 | 346 | $this->newer = max(array_map($filemtime, $files)); 347 | 348 | if (empty($this->newer)) { 349 | $this->newer = md5(implode($files)); 350 | } 351 | 352 | return $this->newer.'-'.$name; 353 | } 354 | 355 | /** 356 | * @param string $name 357 | * @param string $location 358 | * @throws Exceptions\InvalidArgument 359 | * @return string 360 | */ 361 | public function path($name, $location = '') 362 | { 363 | if ($this->isRemote($location)) { 364 | return $location; 365 | } 366 | 367 | if ($name === '') { 368 | $path = ''; 369 | } elseif ($name === 'public') { 370 | $path = $this->config['public_path']; 371 | } else { 372 | throw new Exceptions\InvalidArgumentException(sprintf('This path does not exists %s', $name)); 373 | } 374 | 375 | return preg_replace('#(^|[^:])//+#', '$1/', $path.'/'.$location); 376 | } 377 | 378 | /** 379 | * @throws Exceptions\FileNotWritable 380 | * @return this 381 | */ 382 | private function process() 383 | { 384 | if ($this->useCache()) { 385 | return $this; 386 | } 387 | 388 | $this->checkDir(dirname($this->file)); 389 | 390 | $tmp = tempnam(sys_get_temp_dir(), basename($this->file)); 391 | 392 | if (!($fp = @fopen($tmp, 'c'))) { 393 | throw new Exceptions\FileNotWritableException(sprintf('File %s can not be created', $this->file)); 394 | } 395 | 396 | foreach ($this->files as $file) { 397 | fwrite($fp, $this->provider->pack($this->path('public', $file), $file)); 398 | } 399 | 400 | fclose($fp); 401 | 402 | copy($tmp, $this->file); 403 | 404 | unlink($tmp); 405 | 406 | chmod($this->file, 0644); 407 | 408 | return $this; 409 | } 410 | 411 | /** 412 | * @return boolean 413 | */ 414 | private function useCache() 415 | { 416 | if ($this->isLocal()) { 417 | return true; 418 | } if (!is_file($this->file)) { 419 | return false; 420 | } if ($this->config['check_timestamps'] === false) { 421 | return true; 422 | } 423 | 424 | return ($this->newer < filemtime($this->file)); 425 | } 426 | 427 | /** 428 | * @param string $dir 429 | * @throws Exceptions\DirNotWritable 430 | * @return boolean 431 | */ 432 | private function checkDir($dir) 433 | { 434 | clearstatcache(true, $dir); 435 | 436 | if (!is_dir($dir) && !@mkdir($dir, 0755, true) && !is_dir($dir)) { 437 | throw new Exceptions\DirNotWritableException(sprintf('Folder %s can not be created', $dir)); 438 | } 439 | } 440 | 441 | /** 442 | * @return string 443 | */ 444 | public function render() 445 | { 446 | if ($this->isLocal()) { 447 | $list = $this->files; 448 | } else { 449 | $list = $this->storage.$this->name; 450 | } 451 | 452 | $this->files = []; 453 | $this->storage = ''; 454 | $this->name = ''; 455 | 456 | $this->force['current'] = $this->force['previous']; 457 | 458 | return $this->provider->tag($list); 459 | } 460 | 461 | /** 462 | * @return boolean 463 | */ 464 | protected function isLocal() 465 | { 466 | return ($this->force['current'] === false) && in_array($this->config['environment'], $this->config['ignore_environments'], true); 467 | } 468 | 469 | protected function isRemote($file) 470 | { 471 | return strpos($file, 'http') === 0; 472 | } 473 | 474 | /** 475 | * @param mixed $files 476 | * @return array 477 | */ 478 | protected function downloadRemotes($files) 479 | { 480 | $files = (array)$files; 481 | 482 | foreach ($files as &$file) { 483 | if ($this->isRemote($file)) { 484 | $file = $this->downloadRemote($file); 485 | } 486 | } 487 | 488 | return array_filter($files); 489 | } 490 | 491 | /** 492 | * @param string $url 493 | * @return string|null 494 | */ 495 | protected function downloadRemote($url) 496 | { 497 | $uri = explode('?', $url)[0]; 498 | 499 | if (!pathinfo($uri, PATHINFO_EXTENSION)) { 500 | return null; 501 | } 502 | 503 | $name = md5($url).'-'.basename($uri); 504 | 505 | $path = $this->storage.$this->config['cache_folder'].$name; 506 | $file = $this->path('public', $path); 507 | 508 | if (is_file($file)) { 509 | return $path; 510 | } 511 | 512 | if (!($contents = @file_get_contents($url))) { 513 | return null; 514 | } 515 | 516 | $this->checkDir(dirname($file)); 517 | 518 | file_put_contents($file, $contents); 519 | 520 | return $path; 521 | } 522 | 523 | /** 524 | * @return boolean 525 | */ 526 | public function getFilePublic() 527 | { 528 | return $this->storage.$this->name; 529 | } 530 | 531 | /** 532 | * @return boolean 533 | */ 534 | public function getFilePath() 535 | { 536 | return $this->file; 537 | } 538 | 539 | /** 540 | * @return string 541 | */ 542 | public function __toString() 543 | { 544 | return $this->render(); 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/PackerServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 23 | __DIR__.'/../../config/config.php' => config_path('packer.php') 24 | ]); 25 | } 26 | 27 | /** 28 | * Register the service provider. 29 | * 30 | * @return void 31 | */ 32 | public function register() 33 | { 34 | $this->app->singleton('packer', function($app) { 35 | return new Packer($this->config()); 36 | }); 37 | } 38 | 39 | /** 40 | * Get the services provided by the provider. 41 | * 42 | * @return array 43 | */ 44 | public function provides() 45 | { 46 | return ['packer']; 47 | } 48 | 49 | /** 50 | * Get the base settings from config file 51 | * 52 | * @return array 53 | */ 54 | public function config() 55 | { 56 | $config = config('packer'); 57 | 58 | if (empty($config['environment'])) { 59 | $config['environment'] = app()->environment(); 60 | } 61 | 62 | if (empty($config['public_path'])) { 63 | $config['public_path'] = public_path(); 64 | } 65 | 66 | if (empty($config['asset'])) { 67 | $config['asset'] = asset(''); 68 | } 69 | 70 | return $config; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Processors/CSS/Colors.php: -------------------------------------------------------------------------------- 1 | 'azure', 12 | '#f5f5dc' => 'beige', 13 | '#ffe4c4' => 'bisque', 14 | '#a52a2a' => 'brown', 15 | '#ff7f50' => 'coral', 16 | '#ffd700' => 'gold', 17 | '#808080' => 'gray', 18 | '#008000' => 'green', 19 | '#4b0082' => 'indigo', 20 | '#fffff0' => 'ivory', 21 | '#f0e68c' => 'khaki', 22 | '#faf0e6' => 'linen', 23 | '#800000' => 'maroon', 24 | '#000080' => 'navy', 25 | '#fdf5e6' => 'oldlace', 26 | '#808000' => 'olive', 27 | '#ffa500' => 'orange', 28 | '#da70d6' => 'orchid', 29 | '#cd853f' => 'peru', 30 | '#ffc0cb' => 'pink', 31 | '#dda0dd' => 'plum', 32 | '#800080' => 'purple', 33 | '#f00' => 'red', 34 | '#fa8072' => 'salmon', 35 | '#a0522d' => 'sienna', 36 | '#c0c0c0' => 'silver', 37 | '#fffafa' => 'snow', 38 | '#d2b48c' => 'tan', 39 | '#008080' => 'teal', 40 | '#ff6347' => 'tomato', 41 | '#ee82ee' => 'violet', 42 | '#f5deb3' => 'wheat' 43 | ); 44 | } 45 | 46 | public static function getNamedToHexMap() 47 | { 48 | // Named colors longer than hex counterpart 49 | return array( 50 | 'aliceblue' => '#f0f8ff', 51 | 'antiquewhite' => '#faebd7', 52 | 'aquamarine' => '#7fffd4', 53 | 'black' => '#000', 54 | 'blanchedalmond' => '#ffebcd', 55 | 'blueviolet' => '#8a2be2', 56 | 'burlywood' => '#deb887', 57 | 'cadetblue' => '#5f9ea0', 58 | 'chartreuse' => '#7fff00', 59 | 'chocolate' => '#d2691e', 60 | 'cornflowerblue' => '#6495ed', 61 | 'cornsilk' => '#fff8dc', 62 | 'darkblue' => '#00008b', 63 | 'darkcyan' => '#008b8b', 64 | 'darkgoldenrod' => '#b8860b', 65 | 'darkgray' => '#a9a9a9', 66 | 'darkgreen' => '#006400', 67 | 'darkgrey' => '#a9a9a9', 68 | 'darkkhaki' => '#bdb76b', 69 | 'darkmagenta' => '#8b008b', 70 | 'darkolivegreen' => '#556b2f', 71 | 'darkorange' => '#ff8c00', 72 | 'darkorchid' => '#9932cc', 73 | 'darksalmon' => '#e9967a', 74 | 'darkseagreen' => '#8fbc8f', 75 | 'darkslateblue' => '#483d8b', 76 | 'darkslategray' => '#2f4f4f', 77 | 'darkslategrey' => '#2f4f4f', 78 | 'darkturquoise' => '#00ced1', 79 | 'darkviolet' => '#9400d3', 80 | 'deeppink' => '#ff1493', 81 | 'deepskyblue' => '#00bfff', 82 | 'dodgerblue' => '#1e90ff', 83 | 'firebrick' => '#b22222', 84 | 'floralwhite' => '#fffaf0', 85 | 'forestgreen' => '#228b22', 86 | 'fuchsia' => '#f0f', 87 | 'gainsboro' => '#dcdcdc', 88 | 'ghostwhite' => '#f8f8ff', 89 | 'goldenrod' => '#daa520', 90 | 'greenyellow' => '#adff2f', 91 | 'honeydew' => '#f0fff0', 92 | 'indianred' => '#cd5c5c', 93 | 'lavender' => '#e6e6fa', 94 | 'lavenderblush' => '#fff0f5', 95 | 'lawngreen' => '#7cfc00', 96 | 'lemonchiffon' => '#fffacd', 97 | 'lightblue' => '#add8e6', 98 | 'lightcoral' => '#f08080', 99 | 'lightcyan' => '#e0ffff', 100 | 'lightgoldenrodyellow' => '#fafad2', 101 | 'lightgray' => '#d3d3d3', 102 | 'lightgreen' => '#90ee90', 103 | 'lightgrey' => '#d3d3d3', 104 | 'lightpink' => '#ffb6c1', 105 | 'lightsalmon' => '#ffa07a', 106 | 'lightseagreen' => '#20b2aa', 107 | 'lightskyblue' => '#87cefa', 108 | 'lightslategray' => '#778899', 109 | 'lightslategrey' => '#778899', 110 | 'lightsteelblue' => '#b0c4de', 111 | 'lightyellow' => '#ffffe0', 112 | 'limegreen' => '#32cd32', 113 | 'mediumaquamarine' => '#66cdaa', 114 | 'mediumblue' => '#0000cd', 115 | 'mediumorchid' => '#ba55d3', 116 | 'mediumpurple' => '#9370db', 117 | 'mediumseagreen' => '#3cb371', 118 | 'mediumslateblue' => '#7b68ee', 119 | 'mediumspringgreen' => '#00fa9a', 120 | 'mediumturquoise' => '#48d1cc', 121 | 'mediumvioletred' => '#c71585', 122 | 'midnightblue' => '#191970', 123 | 'mintcream' => '#f5fffa', 124 | 'mistyrose' => '#ffe4e1', 125 | 'moccasin' => '#ffe4b5', 126 | 'navajowhite' => '#ffdead', 127 | 'olivedrab' => '#6b8e23', 128 | 'orangered' => '#ff4500', 129 | 'palegoldenrod' => '#eee8aa', 130 | 'palegreen' => '#98fb98', 131 | 'paleturquoise' => '#afeeee', 132 | 'palevioletred' => '#db7093', 133 | 'papayawhip' => '#ffefd5', 134 | 'peachpuff' => '#ffdab9', 135 | 'powderblue' => '#b0e0e6', 136 | 'rebeccapurple' => '#663399', 137 | 'rosybrown' => '#bc8f8f', 138 | 'royalblue' => '#4169e1', 139 | 'saddlebrown' => '#8b4513', 140 | 'sandybrown' => '#f4a460', 141 | 'seagreen' => '#2e8b57', 142 | 'seashell' => '#fff5ee', 143 | 'slateblue' => '#6a5acd', 144 | 'slategray' => '#708090', 145 | 'slategrey' => '#708090', 146 | 'springgreen' => '#00ff7f', 147 | 'steelblue' => '#4682b4', 148 | 'turquoise' => '#40e0d0', 149 | 'white' => '#fff', 150 | 'whitesmoke' => '#f5f5f5', 151 | 'yellow' => '#ff0', 152 | 'yellowgreen' => '#9acd32' 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Processors/CSS/Minify.php: -------------------------------------------------------------------------------- 1 | raisePhpLimits = (bool) $raisePhpLimits; 72 | $this->memoryLimit = 128 * 1048576; // 128MB in bytes 73 | $this->pcreBacktrackLimit = 1000 * 1000; 74 | $this->pcreRecursionLimit = 500 * 1000; 75 | $this->hexToNamedColorsMap = Colors::getHexToNamedMap(); 76 | $this->namedToHexColorsMap = Colors::getNamedToHexMap(); 77 | $this->namedToHexColorsRegex = sprintf( 78 | '/([:,( ])(%s)( |,|\)|;|$)/Si', 79 | implode('|', array_keys($this->namedToHexColorsMap)) 80 | ); 81 | $this->numRegex = sprintf('-?\d*\.?\d+%s?', $this->unitsGroupRegex); 82 | $this->setShortenZeroValuesRegexes(); 83 | } 84 | 85 | /** 86 | * Parses & minifies the given input CSS string 87 | * @param string $css 88 | * @return string 89 | */ 90 | public function run($css = '') 91 | { 92 | if (empty($css) || !is_string($css)) { 93 | return ''; 94 | } 95 | 96 | $this->resetRunProperties(); 97 | 98 | if ($this->raisePhpLimits) { 99 | $this->doRaisePhpLimits(); 100 | } 101 | 102 | return $this->minify($css); 103 | } 104 | 105 | /** 106 | * Sets whether to keep or remove sourcemap special comment. 107 | * Sourcemap comments are removed by default. 108 | * @param bool $keepSourceMapComment 109 | */ 110 | public function keepSourceMapComment($keepSourceMapComment = true) 111 | { 112 | $this->keepSourceMapComment = (bool) $keepSourceMapComment; 113 | } 114 | 115 | /** 116 | * Sets whether to keep or remove important comments. 117 | * Important comments outside of a declaration block are kept by default. 118 | * @param bool $removeImportantComments 119 | */ 120 | public function removeImportantComments($removeImportantComments = true) 121 | { 122 | $this->keepImportantComments = !(bool) $removeImportantComments; 123 | } 124 | 125 | /** 126 | * Sets the approximate column after which long lines will be splitted in the output 127 | * with a linebreak. 128 | * @param int $position 129 | */ 130 | public function setLineBreakPosition($position) 131 | { 132 | $this->linebreakPosition = (int) $position; 133 | } 134 | 135 | /** 136 | * Sets the memory limit for this script 137 | * @param int|string $limit 138 | */ 139 | public function setMemoryLimit($limit) 140 | { 141 | $this->memoryLimit = Utils::normalizeInt($limit); 142 | } 143 | 144 | /** 145 | * Sets the maximum execution time for this script 146 | * @param int|string $seconds 147 | */ 148 | public function setMaxExecutionTime($seconds) 149 | { 150 | $this->maxExecutionTime = (int) $seconds; 151 | } 152 | 153 | /** 154 | * Sets the PCRE backtrack limit for this script 155 | * @param int $limit 156 | */ 157 | public function setPcreBacktrackLimit($limit) 158 | { 159 | $this->pcreBacktrackLimit = (int) $limit; 160 | } 161 | 162 | /** 163 | * Sets the PCRE recursion limit for this script 164 | * @param int $limit 165 | */ 166 | public function setPcreRecursionLimit($limit) 167 | { 168 | $this->pcreRecursionLimit = (int) $limit; 169 | } 170 | 171 | /** 172 | * Builds regular expressions needed for shortening zero values 173 | */ 174 | private function setShortenZeroValuesRegexes() 175 | { 176 | $zeroRegex = '0'. $this->unitsGroupRegex; 177 | $numOrPosRegex = '('. $this->numRegex .'|top|left|bottom|right|center) '; 178 | $oneZeroSafeProperties = array( 179 | '(?:line-)?height', 180 | '(?:(?:min|max)-)?width', 181 | 'top', 182 | 'left', 183 | 'background-position', 184 | 'bottom', 185 | 'right', 186 | 'border(?:-(?:top|left|bottom|right))?(?:-width)?', 187 | 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', 188 | 'column-(?:gap|width)', 189 | 'margin(?:-(?:top|left|bottom|right))?', 190 | 'outline-width', 191 | 'padding(?:-(?:top|left|bottom|right))?' 192 | ); 193 | 194 | // First zero regex 195 | $regex = '/(^|;)('. implode('|', $oneZeroSafeProperties) .'):%s/Si'; 196 | $this->shortenOneZeroesRegex = sprintf($regex, $zeroRegex); 197 | 198 | // Multiple zeroes regexes 199 | $regex = '/(^|;)(margin|padding|border-(?:width|radius)|background-position):%s/Si'; 200 | $this->shortenTwoZeroesRegex = sprintf($regex, $numOrPosRegex . $zeroRegex); 201 | $this->shortenThreeZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $zeroRegex); 202 | $this->shortenFourZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $numOrPosRegex . $zeroRegex); 203 | } 204 | 205 | /** 206 | * Resets properties whose value may change between runs 207 | */ 208 | private function resetRunProperties() 209 | { 210 | $this->comments = array(); 211 | $this->ruleBodies = array(); 212 | $this->preservedTokens = array(); 213 | } 214 | 215 | /** 216 | * Tries to configure PHP to use at least the suggested minimum settings 217 | * @return void 218 | */ 219 | private function doRaisePhpLimits() 220 | { 221 | $phpLimits = array( 222 | 'memory_limit' => $this->memoryLimit, 223 | 'max_execution_time' => $this->maxExecutionTime, 224 | 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, 225 | 'pcre.recursion_limit' => $this->pcreRecursionLimit 226 | ); 227 | 228 | // If current settings are higher respect them. 229 | foreach ($phpLimits as $name => $suggested) { 230 | $current = Utils::normalizeInt(ini_get($name)); 231 | 232 | if ($current >= $suggested) { 233 | continue; 234 | } 235 | 236 | // memoryLimit exception: allow -1 for "no memory limit". 237 | if ($name === 'memory_limit' && $current === -1) { 238 | continue; 239 | } 240 | 241 | // maxExecutionTime exception: allow 0 for "no memory limit". 242 | if ($name === 'max_execution_time' && $current === 0) { 243 | continue; 244 | } 245 | 246 | ini_set($name, $suggested); 247 | } 248 | } 249 | 250 | /** 251 | * Registers a preserved token 252 | * @param string $token 253 | * @return string The token ID string 254 | */ 255 | private function registerPreservedToken($token) 256 | { 257 | $tokenId = sprintf(self::PRESERVED_TOKEN, count($this->preservedTokens)); 258 | $this->preservedTokens[$tokenId] = $token; 259 | return $tokenId; 260 | } 261 | 262 | /** 263 | * Registers a candidate comment token 264 | * @param string $comment 265 | * @return string The comment token ID string 266 | */ 267 | private function registerCommentToken($comment) 268 | { 269 | $tokenId = sprintf(self::COMMENT_TOKEN, count($this->comments)); 270 | $this->comments[$tokenId] = $comment; 271 | return $tokenId; 272 | } 273 | 274 | /** 275 | * Registers a rule body token 276 | * @param string $body the minified rule body 277 | * @return string The rule body token ID string 278 | */ 279 | private function registerRuleBodyToken($body) 280 | { 281 | if (empty($body)) { 282 | return ''; 283 | } 284 | 285 | $tokenId = sprintf(self::RULE_BODY_TOKEN, count($this->ruleBodies)); 286 | $this->ruleBodies[$tokenId] = $body; 287 | return $tokenId; 288 | } 289 | 290 | /** 291 | * Parses & minifies the given input CSS string 292 | * @param string $css 293 | * @return string 294 | */ 295 | private function minify($css) 296 | { 297 | // Process data urls 298 | $css = $this->processDataUrls($css); 299 | 300 | // Process comments 301 | $css = preg_replace_callback( 302 | '/(?processComments($css); 341 | 342 | // Process rule bodies 343 | $css = $this->processRuleBodies($css); 344 | 345 | // Process at-rules and selectors 346 | $css = $this->processAtRulesAndSelectors($css); 347 | 348 | // Restore preserved rule bodies before splitting 349 | $css = strtr($css, $this->ruleBodies); 350 | 351 | // Split long lines in output if required 352 | $css = $this->processLongLineSplitting($css); 353 | 354 | // Restore preserved comments and strings 355 | $css = strtr($css, $this->preservedTokens); 356 | 357 | return trim($css); 358 | } 359 | 360 | /** 361 | * Searches & replaces all data urls with tokens before we start compressing, 362 | * to avoid performance issues running some of the subsequent regexes against large string chunks. 363 | * @param string $css 364 | * @return string 365 | */ 366 | private function processDataUrls($css) 367 | { 368 | $ret = ''; 369 | $searchOffset = $substrOffset = 0; 370 | 371 | // Since we need to account for non-base64 data urls, we need to handle 372 | // ' and ) being part of the data string. 373 | while (preg_match('/url\(\s*(["\']?)data:/Si', $css, $m, PREG_OFFSET_CAPTURE, $searchOffset)) { 374 | $matchStartIndex = $m[0][1]; 375 | $dataStartIndex = $matchStartIndex + 4; // url( length 376 | $searchOffset = $matchStartIndex + strlen($m[0][0]); 377 | $terminator = $m[1][0]; // ', " or empty (not quoted) 378 | $terminatorRegex = '/(?registerPreservedToken(trim($token)) .')'; 394 | // No end terminator found, re-add the whole match. Should we throw/warn here? 395 | } else { 396 | $ret .= substr($css, $matchStartIndex, $searchOffset - $matchStartIndex); 397 | } 398 | 399 | $substrOffset = $searchOffset; 400 | } 401 | 402 | $ret .= substr($css, $substrOffset); 403 | 404 | return $ret; 405 | } 406 | 407 | /** 408 | * Registers all comments found as candidates to be preserved. 409 | * @param array $matches 410 | * @return string 411 | */ 412 | private function processCommentsCallback($matches) 413 | { 414 | return '/*'. $this->registerCommentToken($matches[1]) .'*/'; 415 | } 416 | 417 | /** 418 | * Preserves old IE Matrix string definition 419 | * @param array $matches 420 | * @return string 421 | */ 422 | private function processOldIeSpecificMatrixDefinitionCallback($matches) 423 | { 424 | return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $this->registerPreservedToken($matches[1]) .')'; 425 | } 426 | 427 | /** 428 | * Preserves strings found 429 | * @param array $matches 430 | * @return string 431 | */ 432 | private function processStringsCallback($matches) 433 | { 434 | $match = $matches[0]; 435 | $quote = substr($match, 0, 1); 436 | $match = substr($match, 1, -1); 437 | 438 | // maybe the string contains a comment-like substring? 439 | // one, maybe more? put'em back then 440 | if (strpos($match, self::COMMENT_TOKEN_START) !== false) { 441 | $match = strtr($match, $this->comments); 442 | } 443 | 444 | // minify alpha opacity in filter strings 445 | $match = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $match); 446 | 447 | return $quote . $this->registerPreservedToken($match) . $quote; 448 | } 449 | 450 | /** 451 | * Searches & replaces all import at-rule unquoted urls with tokens so URI reserved characters such as a semicolon 452 | * may be used safely in a URL. 453 | * @param array $matches 454 | * @return string 455 | */ 456 | private function processImportUnquotedUrlAtRulesCallback($matches) 457 | { 458 | return '@import url('. $this->registerPreservedToken($matches[1]) .')'. $matches[2]; 459 | } 460 | 461 | /** 462 | * Preserves or removes comments found. 463 | * @param string $css 464 | * @return string 465 | */ 466 | private function processComments($css) 467 | { 468 | foreach ($this->comments as $commentId => $comment) { 469 | $commentIdString = '/*'. $commentId .'*/'; 470 | 471 | // ! in the first position of the comment means preserve 472 | // so push to the preserved tokens keeping the ! 473 | if ($this->keepImportantComments && strpos($comment, '!') === 0) { 474 | $preservedTokenId = $this->registerPreservedToken($comment); 475 | // Put new lines before and after /*! important comments 476 | $css = str_replace($commentIdString, "\n/*$preservedTokenId*/\n", $css); 477 | continue; 478 | } 479 | 480 | // # sourceMappingURL= in the first position of the comment means sourcemap 481 | // so push to the preserved tokens if {$this->keepSourceMapComment} is truthy. 482 | if ($this->keepSourceMapComment && strpos($comment, '# sourceMappingURL=') === 0) { 483 | $preservedTokenId = $this->registerPreservedToken($comment); 484 | // Add new line before the sourcemap comment 485 | $css = str_replace($commentIdString, "\n/*$preservedTokenId*/", $css); 486 | continue; 487 | } 488 | 489 | // Keep empty comments after child selectors (IE7 hack) 490 | // e.g. html >/**/ body 491 | if (strlen($comment) === 0 && strpos($css, '>/*'.$commentId) !== false) { 492 | $css = str_replace($commentId, $this->registerPreservedToken(''), $css); 493 | continue; 494 | } 495 | 496 | // in all other cases kill the comment 497 | $css = str_replace($commentIdString, '', $css); 498 | } 499 | 500 | // Normalize whitespace again 501 | $css = preg_replace('/ +/S', ' ', $css); 502 | 503 | return $css; 504 | } 505 | 506 | /** 507 | * Finds, minifies & preserves all rule bodies. 508 | * @param string $css the whole stylesheet. 509 | * @return string 510 | */ 511 | private function processRuleBodies($css) 512 | { 513 | $ret = ''; 514 | $searchOffset = $substrOffset = 0; 515 | 516 | while (($blockStartPos = strpos($css, '{', $searchOffset)) !== false) { 517 | $blockEndPos = strpos($css, '}', $blockStartPos); 518 | $nextBlockStartPos = strpos($css, '{', $blockStartPos + 1); 519 | $ret .= substr($css, $substrOffset, $blockStartPos - $substrOffset); 520 | 521 | if ($nextBlockStartPos !== false && $nextBlockStartPos < $blockEndPos) { 522 | $ret .= substr($css, $blockStartPos, $nextBlockStartPos - $blockStartPos); 523 | $searchOffset = $nextBlockStartPos; 524 | } else { 525 | $ruleBody = substr($css, $blockStartPos + 1, $blockEndPos - $blockStartPos - 1); 526 | $ruleBodyToken = $this->registerRuleBodyToken($this->processRuleBody($ruleBody)); 527 | $ret .= '{'. $ruleBodyToken .'}'; 528 | $searchOffset = $blockEndPos + 1; 529 | } 530 | 531 | $substrOffset = $searchOffset; 532 | } 533 | 534 | $ret .= substr($css, $substrOffset); 535 | 536 | return $ret; 537 | } 538 | 539 | /** 540 | * Compresses non-group rule bodies. 541 | * @param string $body The rule body without curly braces 542 | * @return string 543 | */ 544 | private function processRuleBody($body) 545 | { 546 | $body = trim($body); 547 | 548 | // Remove spaces before the things that should not have spaces before them. 549 | $body = preg_replace('/ ([:=,)*\/;\n])/S', '$1', $body); 550 | 551 | // Remove the spaces after the things that should not have spaces after them. 552 | $body = preg_replace('/([:=,(*\/!;\n]) /S', '$1', $body); 553 | 554 | // Replace multiple semi-colons in a row by a single one 555 | $body = preg_replace('/;;+/S', ';', $body); 556 | 557 | // Remove semicolon before closing brace except when: 558 | // - The last property is prefixed with a `*` (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers. 559 | if (!preg_match('/\*[a-z0-9-]+:[^;]+;$/Si', $body)) { 560 | $body = rtrim($body, ';'); 561 | } 562 | 563 | // Remove important comments inside a rule body (because they make no sense here). 564 | if (strpos($body, '/*') !== false) { 565 | $body = preg_replace('/\n?\/\*[A-Z0-9_]+\*\/\n?/S', '', $body); 566 | } 567 | 568 | // Empty rule body? Exit :) 569 | if (empty($body)) { 570 | return ''; 571 | } 572 | 573 | // Shorten font-weight values 574 | $body = preg_replace( 575 | array('/(font-weight:)bold\b/Si', '/(font-weight:)normal\b/Si'), 576 | array('${1}700', '${1}400'), 577 | $body 578 | ); 579 | 580 | // Shorten background property 581 | $body = preg_replace('/(background:)(?:none|transparent)( !|;|$)/Si', '${1}0 0$2', $body); 582 | 583 | // Shorten opacity IE filter 584 | $body = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $body); 585 | 586 | // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space) 587 | // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space) 588 | // This makes it more likely that it'll get further compressed in the next step. 589 | $body = preg_replace_callback( 590 | '/(rgb|hsl)\(([0-9,.% -]+)\)(.|$)/Si', 591 | array($this, 'shortenHslAndRgbToHexCallback'), 592 | $body 593 | ); 594 | 595 | // Shorten colors from #AABBCC to #ABC or shorter color name: 596 | // - Look for hex colors which don't have a "=" in front of them (to avoid MSIE filters) 597 | $body = preg_replace_callback( 598 | '/(? #fff. 604 | // Run at least 2 times to cover most cases 605 | $body = preg_replace_callback( 606 | array($this->namedToHexColorsRegex, $this->namedToHexColorsRegex), 607 | array($this, 'shortenNamedColorsCallback'), 608 | $body 609 | ); 610 | 611 | // Replace positive sign from numbers before the leading space is removed. 612 | // +1.2em to 1.2em, +.8px to .8px, +2% to 2% 613 | $body = preg_replace('/([ :,(])\+(\.?\d+)/S', '$1$2', $body); 614 | 615 | // shorten ms to s 616 | $body = preg_replace_callback('/([ :,(])(-?)(\d{3,})ms/Si', function ($matches) { 617 | return $matches[1] . $matches[2] . ((int) $matches[3] / 1000) .'s'; 618 | }, $body); 619 | 620 | // Remove leading zeros from integer and float numbers. 621 | // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05 622 | $body = preg_replace('/([ :,(])(-?)0+([1-9]?\.?\d+)/S', '$1$2$3', $body); 623 | 624 | // Remove trailing zeros from float numbers. 625 | // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px 626 | $body = preg_replace('/([ :,(])(-?\d?\.\d+?)0+([^\d])/S', '$1$2$3', $body); 627 | 628 | // Remove trailing .0 -> -9.0 to -9 629 | $body = preg_replace('/([ :,(])(-?\d+)\.0([^\d])/S', '$1$2$3', $body); 630 | 631 | // Replace 0 length numbers with 0 632 | $body = preg_replace('/([ :,(])-?\.?0+([^\d])/S', '${1}0$2', $body); 633 | 634 | // Shorten zero values for safe properties only 635 | $body = preg_replace( 636 | array( 637 | $this->shortenOneZeroesRegex, 638 | $this->shortenTwoZeroesRegex, 639 | $this->shortenThreeZeroesRegex, 640 | $this->shortenFourZeroesRegex 641 | ), 642 | array( 643 | '$1$2:0', 644 | '$1$2:$3 0', 645 | '$1$2:$3 $4 0', 646 | '$1$2:$3 $4 $5 0' 647 | ), 648 | $body 649 | ); 650 | 651 | // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. 652 | $body = preg_replace('/(background-position):0(?: 0){2,3}( !|;|$)/Si', '$1:0 0$2', $body); 653 | 654 | // Shorten suitable shorthand properties with repeated values 655 | $body = preg_replace( 656 | array( 657 | '/(margin|padding|border-(?:width|radius)):('.$this->numRegex.')(?: \2)+( !|;|$)/Si', 658 | '/(border-(?:style|color)):([#a-z0-9]+)(?: \2)+( !|;|$)/Si' 659 | ), 660 | '$1:$2$3', 661 | $body 662 | ); 663 | $body = preg_replace( 664 | array( 665 | '/(margin|padding|border-(?:width|radius)):'. 666 | '('.$this->numRegex.') ('.$this->numRegex.') \2 \3( !|;|$)/Si', 667 | '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) \2 \3( !|;|$)/Si' 668 | ), 669 | '$1:$2 $3$4', 670 | $body 671 | ); 672 | $body = preg_replace( 673 | array( 674 | '/(margin|padding|border-(?:width|radius)):'. 675 | '('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') \3( !|;|$)/Si', 676 | '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) ([#a-z0-9]+) \3( !|;|$)/Si' 677 | ), 678 | '$1:$2 $3 $4$5', 679 | $body 680 | ); 681 | 682 | // Lowercase some common functions that can be values 683 | $body = preg_replace_callback( 684 | '/(?:attr|blur|brightness|circle|contrast|cubic-bezier|drop-shadow|ellipse|from|grayscale|'. 685 | 'hsla?|hue-rotate|inset|invert|local|minmax|opacity|perspective|polygon|rgba?|rect|repeat|saturate|sepia|'. 686 | 'steps|to|url|var|-webkit-gradient|'. 687 | '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|(?:repeating-)?(?:linear|radial)-gradient))\(/Si', 688 | array($this, 'strtolowerCallback'), 689 | $body 690 | ); 691 | 692 | // Lowercase all uppercase properties 693 | $body = preg_replace_callback('/(?:^|;)[A-Z-]+:/S', array($this, 'strtolowerCallback'), $body); 694 | 695 | return $body; 696 | } 697 | 698 | /** 699 | * Compresses At-rules and selectors. 700 | * @param string $css the whole stylesheet with rule bodies tokenized. 701 | * @return string 702 | */ 703 | private function processAtRulesAndSelectors($css) 704 | { 705 | $charset = ''; 706 | $imports = ''; 707 | $namespaces = ''; 708 | 709 | // Remove spaces before the things that should not have spaces before them. 710 | $css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css); 711 | 712 | // Remove the spaces after the things that should not have spaces after them. 713 | $css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css); 714 | 715 | // Shorten shortable double colon (CSS3) pseudo-elements to single colon (CSS2) 716 | $css = preg_replace('/::(before|after|first-(?:line|letter))(\{|,)/Si', ':$1$2', $css); 717 | 718 | // Retain space for special IE6 cases 719 | $css = preg_replace_callback('/:first-(line|letter)(\{|,)/Si', function ($matches) { 720 | return ':first-'. strtolower($matches[1]) .' '. $matches[2]; 721 | }, $css); 722 | 723 | // Find a fraction that may used in some @media queries such as: (min-aspect-ratio: 1/1) 724 | // Add token to add the "/" back in later 725 | $css = preg_replace('/\(([a-z-]+):([0-9]+)\/([0-9]+)\)/Si', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); 726 | 727 | // Remove empty rule blocks up to 2 levels deep. 728 | $css = preg_replace(array_fill(0, 2, '/(\{)[^{};\/\n]+\{\}/S'), '$1', $css); 729 | $css = preg_replace('/[^{};\/\n]+\{\}/S', '', $css); 730 | 731 | // Two important comments next to each other? Remove extra newline. 732 | if ($this->keepImportantComments) { 733 | $css = str_replace("\n\n", "\n", $css); 734 | } 735 | 736 | // Restore fraction 737 | $css = str_replace(self::QUERY_FRACTION, '/', $css); 738 | 739 | // Lowercase some popular @directives 740 | $css = preg_replace_callback( 741 | '/(?charsetRegex, $css, $matches)) { 766 | // Keep the first @charset at-rule found 767 | $charset = $matches[0]; 768 | // Delete all @charset at-rules 769 | $css = preg_replace($this->charsetRegex, '', $css); 770 | } 771 | 772 | // @import handling 773 | $css = preg_replace_callback($this->importRegex, function ($matches) use (&$imports) { 774 | // Keep all @import at-rules found for later 775 | $imports .= $matches[0]; 776 | // Delete all @import at-rules 777 | return ''; 778 | }, $css); 779 | 780 | // @namespace handling 781 | $css = preg_replace_callback($this->namespaceRegex, function ($matches) use (&$namespaces) { 782 | // Keep all @namespace at-rules found for later 783 | $namespaces .= $matches[0]; 784 | // Delete all @namespace at-rules 785 | return ''; 786 | }, $css); 787 | 788 | // Order critical at-rules: 789 | // 1. @charset first 790 | // 2. @imports below @charset 791 | // 3. @namespaces below @imports 792 | $css = $charset . $imports . $namespaces . $css; 793 | 794 | return $css; 795 | } 796 | 797 | /** 798 | * Splits long lines after a specific column. 799 | * 800 | * Some source control tools don't like it when files containing lines longer 801 | * than, say 8000 characters, are checked in. The linebreak option is used in 802 | * that case to split long lines after a specific column. 803 | * 804 | * @param string $css the whole stylesheet. 805 | * @return string 806 | */ 807 | private function processLongLineSplitting($css) 808 | { 809 | if ($this->linebreakPosition > 0) { 810 | $l = strlen($css); 811 | $offset = $this->linebreakPosition; 812 | while (preg_match('/(?linebreakPosition; 816 | $l += 1; 817 | if ($offset > $l) { 818 | break; 819 | } 820 | } 821 | } 822 | 823 | return $css; 824 | } 825 | 826 | /** 827 | * Converts hsl() & rgb() colors to HEX format. 828 | * @param $matches 829 | * @return string 830 | */ 831 | private function shortenHslAndRgbToHexCallback($matches) 832 | { 833 | $type = $matches[1]; 834 | $values = explode(',', $matches[2]); 835 | $terminator = $matches[3]; 836 | 837 | if ($type === 'hsl') { 838 | $values = Utils::hslToRgb($values); 839 | } 840 | 841 | $hexColors = Utils::rgbToHex($values); 842 | 843 | // Restore space after rgb() or hsl() function in some cases such as: 844 | // background-image: linear-gradient(to bottom, rgb(210,180,140) 10%, rgb(255,0,0) 90%); 845 | if (!empty($terminator) && !preg_match('/[ ,);]/S', $terminator)) { 846 | $terminator = ' '. $terminator; 847 | } 848 | 849 | return '#'. implode('', $hexColors) . $terminator; 850 | } 851 | 852 | /** 853 | * Compresses HEX color values of the form #AABBCC to #ABC or short color name. 854 | * @param $matches 855 | * @return string 856 | */ 857 | private function shortenHexColorsCallback($matches) 858 | { 859 | $hex = $matches[1]; 860 | 861 | // Shorten suitable 6 chars HEX colors 862 | if (strlen($hex) === 6 && preg_match('/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/Si', $hex, $m)) { 863 | $hex = $m[1] . $m[2] . $m[3]; 864 | } 865 | 866 | // Lowercase 867 | $hex = '#'. strtolower($hex); 868 | 869 | // Replace Hex colors with shorter color names 870 | $color = array_key_exists($hex, $this->hexToNamedColorsMap) ? $this->hexToNamedColorsMap[$hex] : $hex; 871 | 872 | return $color . $matches[2]; 873 | } 874 | 875 | /** 876 | * Shortens all named colors with a shorter HEX counterpart for a set of safe properties 877 | * e.g. white -> #fff 878 | * @param array $matches 879 | * @return string 880 | */ 881 | private function shortenNamedColorsCallback($matches) 882 | { 883 | return $matches[1] . $this->namedToHexColorsMap[strtolower($matches[2])] . $matches[3]; 884 | } 885 | 886 | /** 887 | * Makes a string lowercase 888 | * @param array $matches 889 | * @return string 890 | */ 891 | private function strtolowerCallback($matches) 892 | { 893 | return strtolower($matches[0]); 894 | } 895 | } 896 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Processors/CSS/Utils.php: -------------------------------------------------------------------------------- 1 | 1 ? $vh - 1 : $vh); 68 | 69 | if ($vh * 6 < 1) { 70 | return $v1 + ($v2 - $v1) * 6 * $vh; 71 | } 72 | 73 | if ($vh * 2 < 1) { 74 | return $v2; 75 | } 76 | 77 | if ($vh * 3 < 2) { 78 | return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; 79 | } 80 | 81 | return $v1; 82 | } 83 | 84 | /** 85 | * Convert strings like "64M" or "30" to int values 86 | * @param mixed $size 87 | * @return int 88 | */ 89 | public static function normalizeInt($size) 90 | { 91 | if (is_string($size)) { 92 | $letter = substr($size, -1); 93 | $size = intval($size); 94 | switch ($letter) { 95 | case 'M': 96 | case 'm': 97 | return (int) $size * 1048576; 98 | case 'K': 99 | case 'k': 100 | return (int) $size * 1024; 101 | case 'G': 102 | case 'g': 103 | return (int) $size * 1073741824; 104 | } 105 | } 106 | return (int) $size; 107 | } 108 | 109 | /** 110 | * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 111 | * @param $rgbPercentage 112 | * @return int 113 | */ 114 | public static function rgbPercentageToRgbInteger($rgbPercentage) 115 | { 116 | if (strpos($rgbPercentage, '%') !== false) { 117 | $rgbPercentage = self::roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); 118 | } 119 | 120 | return intval($rgbPercentage, 10); 121 | } 122 | 123 | /** 124 | * Converts a RGB color into a HEX color 125 | * @param array $rgbColors 126 | * @return array 127 | */ 128 | public static function rgbToHex($rgbColors) 129 | { 130 | $hexColors = array(); 131 | 132 | // Values outside the sRGB color space should be clipped (0-255) 133 | for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { 134 | $hexColors[$i] = sprintf("%02x", self::clampNumberSrgb(self::rgbPercentageToRgbInteger($rgbColors[$i]))); 135 | } 136 | 137 | return $hexColors; 138 | } 139 | 140 | /** 141 | * Rounds a number to its closest integer 142 | * @param $n 143 | * @return int 144 | */ 145 | public static function roundNumber($n) 146 | { 147 | return intval(round(floatval($n)), 10); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Processors/JS/Minify.php: -------------------------------------------------------------------------------- 1 | 11 | * $minifiedJs = JSMin::minify($js); 12 | * 13 | * 14 | * This is a modified port of jsmin.c. Improvements: 15 | * 16 | * Does not choke on some regexp literals containing quote characters. E.g. /'/ 17 | * 18 | * Spaces are preserved after some add/sub operators, so they are not mistakenly 19 | * converted to post-inc/dec. E.g. a + ++b -> a+ ++b 20 | * 21 | * Preserves multi-line comments that begin with /*! 22 | * 23 | * PHP 5 or higher is required. 24 | * 25 | * Permission is hereby granted to use this version of the library under the 26 | * same terms as jsmin.c, which has the following license: 27 | * 28 | * -- 29 | * Copyright (c) 2002 Douglas Crockford (www.crockford.com) 30 | * 31 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 32 | * this software and associated documentation files (the "Software"), to deal in 33 | * the Software without restriction, including without limitation the rights to 34 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 35 | * of the Software, and to permit persons to whom the Software is furnished to do 36 | * so, subject to the following conditions: 37 | * 38 | * The above copyright notice and this permission notice shall be included in all 39 | * copies or substantial portions of the Software. 40 | * 41 | * The Software shall be used for Good, not Evil. 42 | * 43 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | * SOFTWARE. 50 | * -- 51 | * 52 | * @package JSMin 53 | * @author Ryan Grove (PHP port) 54 | * @author Steve Clay (modifications + cleanup) 55 | * @author Andrea Giammarchi (spaceBeforeRegExp) 56 | * @copyright 2002 Douglas Crockford (jsmin.c) 57 | * @copyright 2008 Ryan Grove (PHP port) 58 | * @license http://opensource.org/licenses/mit-license.php MIT License 59 | * @link http://code.google.com/p/jsmin-php/ 60 | */ 61 | 62 | class Minify 63 | { 64 | const ORD_LF = 10; 65 | const ORD_SPACE = 32; 66 | const ACTION_KEEP_A = 1; 67 | const ACTION_DELETE_A = 2; 68 | const ACTION_DELETE_A_B = 3; 69 | 70 | protected $a = "\n"; 71 | protected $b = ''; 72 | protected $input = ''; 73 | protected $inputIndex = 0; 74 | protected $inputLength = 0; 75 | protected $lookAhead = null; 76 | protected $output = ''; 77 | protected $lastByteOut = ''; 78 | protected $keptComment = ''; 79 | protected $keptComments = false; 80 | 81 | /** 82 | * Minify Javascript. 83 | * 84 | * @param string $js Javascript to be minified 85 | * 86 | * @return string 87 | */ 88 | public static function minify($js) 89 | { 90 | return (new self($js))->min(); 91 | } 92 | 93 | /** 94 | * @param string $input 95 | */ 96 | public function __construct($input) 97 | { 98 | $this->input = $input; 99 | } 100 | 101 | /** 102 | * Perform minification, return result 103 | * 104 | * @return string 105 | */ 106 | public function min() 107 | { 108 | if ($this->output !== '') { // min already run 109 | 110 | return $this->output; 111 | } 112 | 113 | $mbIntEnc = null; 114 | if (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload') & 2)) { 115 | $mbIntEnc = mb_internal_encoding(); 116 | mb_internal_encoding('8bit'); 117 | } 118 | $this->input = str_replace("\r\n", "\n", $this->input); 119 | $this->inputLength = strlen($this->input); 120 | 121 | $this->action(self::ACTION_DELETE_A_B); 122 | 123 | while ($this->a !== null) { 124 | // determine next command 125 | $command = self::ACTION_KEEP_A; // default 126 | if ($this->a === ' ') { 127 | if (($this->lastByteOut === '+' || $this->lastByteOut === '-') 128 | && ($this->b === $this->lastByteOut)) { 129 | // Don't delete this space. If we do, the addition/subtraction 130 | // could be parsed as a post-increment 131 | } elseif (! $this->isAlphaNum($this->b)) { 132 | $command = self::ACTION_DELETE_A; 133 | } 134 | } elseif ($this->a === "\n") { 135 | if ($this->b === ' ') { 136 | $command = self::ACTION_DELETE_A_B; 137 | 138 | // in case of mbstring.func_overload & 2, must check for null b, 139 | // otherwise mb_strpos will give WARNING 140 | } elseif ($this->b === null 141 | || (false === strpos('{[(+-!~', $this->b) 142 | && ! $this->isAlphaNum($this->b))) { 143 | $command = self::ACTION_DELETE_A; 144 | } 145 | } elseif (! $this->isAlphaNum($this->a)) { 146 | if ($this->b === ' ' 147 | || ($this->b === "\n" 148 | && (false === strpos('}])+-"\'', $this->a)))) { 149 | $command = self::ACTION_DELETE_A_B; 150 | } 151 | } 152 | $this->action($command); 153 | } 154 | $this->output = trim($this->output); 155 | 156 | if ($mbIntEnc !== null) { 157 | mb_internal_encoding($mbIntEnc); 158 | } 159 | 160 | return $this->output; 161 | } 162 | 163 | /** 164 | * ACTION_KEEP_A = Output A. Copy B to A. Get the next B. 165 | * ACTION_DELETE_A = Copy B to A. Get the next B. 166 | * ACTION_DELETE_A_B = Get the next B. 167 | * 168 | * @param int $command 169 | * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException 170 | */ 171 | protected function action($command) 172 | { 173 | // make sure we don't compress "a + ++b" to "a+++b", etc. 174 | if ($command === self::ACTION_DELETE_A_B 175 | && $this->b === ' ' 176 | && ($this->a === '+' || $this->a === '-')) { 177 | // Note: we're at an addition/substraction operator; the inputIndex 178 | // will certainly be a valid index 179 | if ($this->input[$this->inputIndex] === $this->a) { 180 | // This is "+ +" or "- -". Don't delete the space. 181 | $command = self::ACTION_KEEP_A; 182 | } 183 | } 184 | 185 | switch ($command) { 186 | case self::ACTION_KEEP_A: // 1 187 | $this->output .= $this->a; 188 | 189 | if ($this->keptComment) { 190 | $this->output = rtrim($this->output, "\n"); 191 | $this->output .= $this->keptComment; 192 | $this->keptComment = ''; 193 | } 194 | 195 | $this->lastByteOut = $this->a; 196 | 197 | // fallthrough intentional 198 | case self::ACTION_DELETE_A: // 2 199 | $this->a = $this->b; 200 | if ($this->a === "'" || $this->a === '"') { // string literal 201 | $str = $this->a; // in case needed for exception 202 | for (;;) { 203 | $this->output .= $this->a; 204 | $this->lastByteOut = $this->a; 205 | 206 | $this->a = $this->get(); 207 | if ($this->a === $this->b) { // end quote 208 | break; 209 | } 210 | if ($this->isEOF($this->a)) { 211 | $byte = $this->inputIndex - 1; 212 | throw new JSMin_UnterminatedStringException( 213 | "JSMin: Unterminated String at byte {$byte}: {$str}"); 214 | } 215 | $str .= $this->a; 216 | if ($this->a === '\\') { 217 | $this->output .= $this->a; 218 | $this->lastByteOut = $this->a; 219 | 220 | $this->a = $this->get(); 221 | $str .= $this->a; 222 | } 223 | } 224 | } 225 | 226 | // fallthrough intentional 227 | case self::ACTION_DELETE_A_B: // 3 228 | $this->b = $this->next(); 229 | if ($this->b === '/' && $this->isRegexpLiteral()) { 230 | $this->output .= $this->a . $this->b; 231 | $pattern = '/'; // keep entire pattern in case we need to report it in the exception 232 | for (;;) { 233 | $this->a = $this->get(); 234 | $pattern .= $this->a; 235 | if ($this->a === '[') { 236 | for (;;) { 237 | $this->output .= $this->a; 238 | $this->a = $this->get(); 239 | $pattern .= $this->a; 240 | if ($this->a === ']') { 241 | break; 242 | } 243 | if ($this->a === '\\') { 244 | $this->output .= $this->a; 245 | $this->a = $this->get(); 246 | $pattern .= $this->a; 247 | } 248 | if ($this->isEOF($this->a)) { 249 | throw new JSMin_UnterminatedRegExpException( 250 | "JSMin: Unterminated set in RegExp at byte " 251 | . $this->inputIndex .": {$pattern}"); 252 | } 253 | } 254 | } 255 | 256 | if ($this->a === '/') { // end pattern 257 | break; // while (true) 258 | } elseif ($this->a === '\\') { 259 | $this->output .= $this->a; 260 | $this->a = $this->get(); 261 | $pattern .= $this->a; 262 | } elseif ($this->isEOF($this->a)) { 263 | $byte = $this->inputIndex - 1; 264 | throw new JSMin_UnterminatedRegExpException( 265 | "JSMin: Unterminated RegExp at byte {$byte}: {$pattern}"); 266 | } 267 | $this->output .= $this->a; 268 | $this->lastByteOut = $this->a; 269 | } 270 | $this->b = $this->next(); 271 | } 272 | // end case ACTION_DELETE_A_B 273 | } 274 | } 275 | 276 | /** 277 | * @return bool 278 | */ 279 | protected function isRegexpLiteral() 280 | { 281 | if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { 282 | // we obviously aren't dividing 283 | return true; 284 | } 285 | 286 | // we have to check for a preceding keyword, and we don't need to pattern 287 | // match over the whole output. 288 | $recentOutput = substr($this->output, -10); 289 | 290 | // check if return/typeof directly precede a pattern without a space 291 | foreach (array('return', 'typeof') as $keyword) { 292 | if ($this->a !== substr($keyword, -1)) { 293 | // certainly wasn't keyword 294 | continue; 295 | } 296 | if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) { 297 | if ($m[1] === '' || !$this->isAlphaNum($m[1])) { 298 | return true; 299 | } 300 | } 301 | } 302 | 303 | // check all keywords 304 | if ($this->a === ' ' || $this->a === "\n") { 305 | if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) { 306 | if ($m[1] === '' || !$this->isAlphaNum($m[1])) { 307 | return true; 308 | } 309 | } 310 | } 311 | 312 | return false; 313 | } 314 | 315 | /** 316 | * Return the next character from stdin. Watch out for lookahead. If the character is a control character, 317 | * translate it to a space or linefeed. 318 | * 319 | * @return string 320 | */ 321 | protected function get() 322 | { 323 | $c = $this->lookAhead; 324 | $this->lookAhead = null; 325 | if ($c === null) { 326 | // getc(stdin) 327 | if ($this->inputIndex < $this->inputLength) { 328 | $c = $this->input[$this->inputIndex]; 329 | $this->inputIndex += 1; 330 | } else { 331 | $c = null; 332 | } 333 | } 334 | if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { 335 | return $c; 336 | } 337 | if ($c === "\r") { 338 | return "\n"; 339 | } 340 | 341 | return ' '; 342 | } 343 | 344 | /** 345 | * Does $a indicate end of input? 346 | * 347 | * @param string $a 348 | * @return bool 349 | */ 350 | protected function isEOF($a) 351 | { 352 | return ord($a) <= self::ORD_LF; 353 | } 354 | 355 | /** 356 | * Get next char (without getting it). If is ctrl character, translate to a space or newline. 357 | * 358 | * @return string 359 | */ 360 | protected function peek() 361 | { 362 | $this->lookAhead = $this->get(); 363 | 364 | return $this->lookAhead; 365 | } 366 | 367 | /** 368 | * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. 369 | * 370 | * @param string $c 371 | * 372 | * @return bool 373 | */ 374 | protected function isAlphaNum($c) 375 | { 376 | return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126); 377 | } 378 | 379 | /** 380 | * Consume a single line comment from input (possibly retaining it) 381 | */ 382 | protected function consumeSingleLineComment() 383 | { 384 | $comment = ''; 385 | while (true) { 386 | $get = $this->get(); 387 | $comment .= $get; 388 | if (ord($get) <= self::ORD_LF) { // end of line reached 389 | // if IE conditional comment 390 | if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { 391 | $this->keptComment .= "/{$comment}"; 392 | } 393 | 394 | return; 395 | } 396 | } 397 | } 398 | 399 | /** 400 | * Consume a multiple line comment from input (possibly retaining it) 401 | * 402 | * @throws JSMin_UnterminatedCommentException 403 | */ 404 | protected function consumeMultipleLineComment() 405 | { 406 | $this->get(); 407 | $comment = ''; 408 | for (;;) { 409 | $get = $this->get(); 410 | if ($get === '*') { 411 | if ($this->peek() === '/') { // end of comment reached 412 | $this->get(); 413 | if ($this->keptComments && (0 === strpos($comment, '!'))) { 414 | // preserved by YUI Compressor 415 | if (empty($this->keptComment)) { 416 | // don't prepend a newline if two comments right after one another 417 | $this->keptComment = "\n"; 418 | } 419 | $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; 420 | } elseif (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { 421 | // IE conditional 422 | $this->keptComment .= "/*{$comment}*/"; 423 | } 424 | 425 | return; 426 | } 427 | } elseif ($get === null) { 428 | throw new JSMin_UnterminatedCommentException( 429 | "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); 430 | } 431 | $comment .= $get; 432 | } 433 | } 434 | 435 | /** 436 | * Get the next character, skipping over comments. Some comments may be preserved. 437 | * 438 | * @return string 439 | */ 440 | protected function next() 441 | { 442 | $get = $this->get(); 443 | if ($get === '/') { 444 | switch ($this->peek()) { 445 | case '/': 446 | $this->consumeSingleLineComment(); 447 | $get = "\n"; 448 | break; 449 | case '*': 450 | $this->consumeMultipleLineComment(); 451 | $get = ' '; 452 | break; 453 | } 454 | } 455 | 456 | return $get; 457 | } 458 | } 459 | 460 | class JSMin_UnterminatedStringException extends Exception {} 461 | class JSMin_UnterminatedCommentException extends Exception {} 462 | class JSMin_UnterminatedRegExpException extends Exception {} 463 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Providers/CSS.php: -------------------------------------------------------------------------------- 1 | contents($file); 20 | 21 | return preg_replace('/(url\([\'"]?)/', '$1'.$this->settings['asset'].dirname($public).'/', $contents); 22 | } 23 | 24 | /** 25 | * @param string $file 26 | * @return string 27 | */ 28 | protected function contents($file) 29 | { 30 | $contents = file_get_contents($file); 31 | 32 | if (empty($this->settings['minify'])) { 33 | return $contents; 34 | } 35 | 36 | $minify = new Minify(); 37 | $minify->removeImportantComments(true); 38 | 39 | return $minify->run($contents); 40 | } 41 | 42 | /** 43 | * @param mixed $file 44 | * @return string 45 | */ 46 | public function tag($file) 47 | { 48 | if (is_array($file)) { 49 | return $this->tags($file); 50 | } 51 | 52 | $attributes = $this->settings['attributes']; 53 | $attributes['href'] = $this->path($this->settings['asset'].$file); 54 | $attributes['rel'] = 'stylesheet'; 55 | 56 | return 'attributes($attributes).' />'.PHP_EOL; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Providers/IMG.php: -------------------------------------------------------------------------------- 1 | isImage($file) && is_file($file)) ? $file : $this->fake(); 27 | } 28 | 29 | /** 30 | * @return string 31 | */ 32 | public function fake() 33 | { 34 | if (empty($this->settings['fake'])) { 35 | return false; 36 | } 37 | 38 | return realpath(__DIR__.'/../../../images/'.rand(1, 8).'.jpg'); 39 | } 40 | 41 | /** 42 | * @param string $file 43 | * @param string $public 44 | * @return string 45 | */ 46 | public function pack($file, $public) 47 | { 48 | if (!($file = $this->check($file))) { 49 | return; 50 | } 51 | 52 | $image = Image::fromFile($file); 53 | 54 | if ($this->settings['quality'] && !strstr($this->settings['transform'], 'quality,')) { 55 | $image->quality($this->settings['quality']); 56 | } 57 | 58 | return $image->transform($this->settings['transform'])->getString(); 59 | } 60 | 61 | /** 62 | * @param string $file 63 | * @return string 64 | */ 65 | public function tag($file) 66 | { 67 | if (empty($file)) { 68 | return ''; 69 | } 70 | 71 | $attributes = $this->settings['attributes']; 72 | $file = is_array($file) ? $file[0] : $file; 73 | 74 | if (empty($attributes)) { 75 | return $this->path($this->settings['asset'].$file); 76 | } 77 | 78 | $attributes['src'] = $this->path($this->settings['asset'].$file); 79 | 80 | return 'attributes($attributes).' />'.PHP_EOL; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Providers/JS.php: -------------------------------------------------------------------------------- 1 | settings['minify']) { 22 | $contents = (new Minify($contents))->min(); 23 | } 24 | 25 | return ';'.$contents; 26 | } 27 | 28 | /** 29 | * @param mixed $file 30 | * @return string 31 | */ 32 | public function tag($file) 33 | { 34 | if (is_array($file)) { 35 | return $this->tags($file); 36 | } 37 | 38 | $attributes = $this->settings['attributes']; 39 | $attributes['src'] = $this->path($this->settings['asset'].$file); 40 | 41 | return ''.PHP_EOL; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Providers/ProviderBase.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 15 | 16 | if (empty($this->settings['attributes'])) { 17 | $this->settings['attributes'] = []; 18 | } 19 | } 20 | 21 | /** 22 | * @param array $files 23 | * @return string 24 | */ 25 | public function tags($files) 26 | { 27 | $html = ''; 28 | 29 | foreach ($files as $file) { 30 | $html .= $this->tag($file); 31 | } 32 | 33 | return $html; 34 | } 35 | 36 | /** 37 | * @param array $attributes 38 | * @return string 39 | */ 40 | protected function attributes(array $attributes) 41 | { 42 | $html = ''; 43 | 44 | foreach ($attributes as $key => $value) { 45 | $html .= $key.'="'.htmlspecialchars($value).'" '; 46 | } 47 | 48 | return trim($html); 49 | } 50 | 51 | /** 52 | * @param string $path 53 | * @return string 54 | */ 55 | protected function path($path) 56 | { 57 | return preg_replace('#(^|[^:])//+#', '$1/', $path); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Eusonlito/LaravelPacker/Providers/ProviderInterface.php: -------------------------------------------------------------------------------- 1 | '', 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | App environments to not pack 19 | |-------------------------------------------------------------------------- 20 | | 21 | | These environments will not be minified and all individual files are 22 | | returned 23 | | 24 | */ 25 | 26 | 'ignore_environments' => ['local'], 27 | 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | Public accessible path 31 | |-------------------------------------------------------------------------- 32 | | 33 | | Set absolute folder path to public view from web. If you are using 34 | | laravel, set this value to null and will be set with public_path() 35 | | function 36 | | 37 | */ 38 | 39 | 'public_path' => realpath(getenv('DOCUMENT_ROOT')), 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Asset absolute location 44 | |-------------------------------------------------------------------------- 45 | | 46 | | Set absolute URL location to asset folder. Many times will be same as 47 | | public_path but using absolute URL. If you are using laravel, set this 48 | | value to null and will be set with asset() function 49 | | 50 | */ 51 | 52 | 'asset' => 'http://'.getenv('SERVER_NAME').'/', 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Cache folder to store packed files 57 | |-------------------------------------------------------------------------- 58 | | 59 | | If you are using relative paths to second paramenter in css and js 60 | | commands, this files will be created with this folder as base. 61 | | 62 | | This folder in relative to 'public_path' value 63 | | 64 | */ 65 | 66 | 'cache_folder' => '/storage/cache/', 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Check if some file to pack have a recent timestamp 71 | |-------------------------------------------------------------------------- 72 | | 73 | | Compare current packed file with all files to pack. If exists one more 74 | | recent than packed file, will be packed again with a new autogenerated 75 | | name. 76 | | 77 | */ 78 | 79 | 'check_timestamps' => true, 80 | 81 | /* 82 | |-------------------------------------------------------------------------- 83 | | Check if you want minify css files or only pack them together 84 | |-------------------------------------------------------------------------- 85 | | 86 | | You can check this option if you want to join and minify all css files or 87 | | only join files 88 | | 89 | */ 90 | 91 | 'css_minify' => true, 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Check if you want minify js files or only pack them together 96 | |-------------------------------------------------------------------------- 97 | | 98 | | You can check this option if you want to join and minify all js files or 99 | | only join files 100 | | 101 | */ 102 | 103 | 'js_minify' => true, 104 | 105 | /* 106 | |-------------------------------------------------------------------------- 107 | | Use fake images stored in src/images/ when original image does not exists 108 | |-------------------------------------------------------------------------- 109 | | 110 | | You can use fake images in your developments to avoid not existing 111 | | original images problems. Fake images are stored in src/images/ and used 112 | | with a rand 113 | | 114 | */ 115 | 116 | 'images_fake' => true, 117 | 118 | /* 119 | |-------------------------------------------------------------------------- 120 | | Set resized images quality 121 | |-------------------------------------------------------------------------- 122 | | 123 | | Valid values are from 0 to 100 124 | | 125 | */ 126 | 127 | 'quality' => 85 128 | ); 129 | -------------------------------------------------------------------------------- /src/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/1.jpg -------------------------------------------------------------------------------- /src/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/2.jpg -------------------------------------------------------------------------------- /src/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/3.jpg -------------------------------------------------------------------------------- /src/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/4.jpg -------------------------------------------------------------------------------- /src/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/5.jpg -------------------------------------------------------------------------------- /src/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/6.jpg -------------------------------------------------------------------------------- /src/images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/7.jpg -------------------------------------------------------------------------------- /src/images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/src/images/8.jpg -------------------------------------------------------------------------------- /tests/Base.php: -------------------------------------------------------------------------------- 1 | at($fs); 26 | 27 | $js = fs::newDirectory('js')->at($resources); 28 | $css = fs::newDirectory('css')->at($resources); 29 | $img = fs::newDirectory('img')->at($resources); 30 | 31 | foreach (glob(__DIR__.'/resources/*') as $file) { 32 | $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); 33 | $ext = in_array($ext, ['css', 'js'], true) ? $ext : 'img'; 34 | 35 | $new = fs::newFile(basename($file)) 36 | ->setContent(file_get_contents($file)) 37 | ->at($$ext); 38 | } 39 | 40 | $config['public_path'] = $fs->url(); 41 | 42 | $this->cache = $config['public_path'].$config['cache_folder']; 43 | 44 | $this->Packer = new Packer($config); 45 | } 46 | 47 | public function checkContents($file, array $tests) 48 | { 49 | $file = file_get_contents($file); 50 | 51 | foreach ($tests as $test) { 52 | $this->assertTrue(strstr($file, $test) ? true : false); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/CSSTest.php: -------------------------------------------------------------------------------- 1 | Packer->css('/resources/css/styles-1.css', 'css/styles-1.css')->getFilePath(); 9 | 10 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 11 | 12 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 13 | 14 | unlink($file); 15 | 16 | $file = $this->cache.'/css/styles-1.css'; 17 | 18 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 19 | } 20 | 21 | public function testPackOneDefaultAbsolute() 22 | { 23 | $file = $this->Packer->css('/resources/css/styles-1.css', '/cache/css/styles-1.css')->getFilePath(); 24 | 25 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 26 | 27 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 28 | 29 | unlink($file); 30 | 31 | $file = $this->cache.'/css/styles-1.css'; 32 | 33 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 34 | } 35 | 36 | public function testPackOneNoTimestampRelative() 37 | { 38 | $this->Packer->setConfig(['check_timestamps' => false]); 39 | 40 | $this->Packer->css('/resources/css/styles-1.css', 'css/styles-1.css'); 41 | 42 | $file = $this->cache.'/css/styles-1.css'; 43 | 44 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 45 | 46 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 47 | 48 | unlink($file); 49 | 50 | $this->Packer->setConfig(['check_timestamps' => true]); 51 | } 52 | 53 | public function testPackOneNoTimestampAbsolute() 54 | { 55 | $this->Packer->setConfig(['check_timestamps' => false]); 56 | 57 | $this->Packer->css('/resources/css/styles-1.css', '/cache/css/styles-1.css'); 58 | 59 | $file = $this->cache.'/css/styles-1.css'; 60 | 61 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 62 | 63 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 64 | 65 | unlink($file); 66 | 67 | $this->Packer->setConfig(['check_timestamps' => true]); 68 | } 69 | 70 | public function testPackOneAutonameRelative() 71 | { 72 | $file = $this->Packer->css('/resources/css/styles-1.css', 'css/')->getFilePath(); 73 | 74 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 75 | 76 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 77 | 78 | unlink($file); 79 | } 80 | 81 | public function testPackOneAutonameAbsolute() 82 | { 83 | $file = $this->Packer->css('/resources/css/styles-1.css', '/cache/css/')->getFilePath(); 84 | 85 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 86 | 87 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 88 | 89 | unlink($file); 90 | } 91 | 92 | /** TESTS WITH MULTIPLE FILES **/ 93 | 94 | public function testPackMultipleDefaultRelative() 95 | { 96 | $file = $this->Packer->css([ 97 | '/resources/css/styles-1.css', 98 | '/resources/css/styles-2.css' 99 | ], 'css/styles.css')->getFilePath(); 100 | 101 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 102 | 103 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 104 | 105 | unlink($file); 106 | 107 | $file = $this->cache.'/css/styles.css'; 108 | 109 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 110 | } 111 | 112 | public function testPackMultipleIssuesDefaultRelative() 113 | { 114 | $file = $this->Packer->css([ 115 | '/resources/css/styles-1.css', 116 | '/resources/css/styles-2.css', 117 | '/resources/css/styles-3.css' 118 | ], 'css/styles.css')->getFilePath(); 119 | 120 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 121 | 122 | $this->checkContents($file, [ 123 | '_TEST_INI_FILE1', '_TEST_END_FILE1', 124 | '_TEST_INI_FILE2', '_TEST_END_FILE2', 125 | '_TEST_INI_FILE3', '_TEST_END_FILE3' 126 | ]); 127 | 128 | unlink($file); 129 | 130 | $file = $this->cache.'/css/styles.css'; 131 | 132 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 133 | } 134 | 135 | public function testPackMultipleDefaultAbsolute() 136 | { 137 | $file = $this->Packer->css([ 138 | '/resources/css/styles-1.css', 139 | '/resources/css/styles-2.css' 140 | ], '/cache/css/styles.css')->getFilePath(); 141 | 142 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 143 | 144 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 145 | 146 | unlink($file); 147 | 148 | $file = $this->cache.'/css/styles.css'; 149 | 150 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 151 | } 152 | 153 | public function testPackMultipleNoTimestampRelative() 154 | { 155 | $this->Packer->setConfig(['check_timestamps' => false]); 156 | 157 | $file = $this->Packer->css([ 158 | '/resources/css/styles-1.css', 159 | '/resources/css/styles-2.css' 160 | ], 'css/styles.css'); 161 | 162 | $file = $this->cache.'/css/styles.css'; 163 | 164 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 165 | 166 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 167 | 168 | unlink($file); 169 | 170 | $this->Packer->setConfig(['check_timestamps' => true]); 171 | } 172 | 173 | public function testPackMultipleNoTimestampAbsolute() 174 | { 175 | $this->Packer->setConfig(['check_timestamps' => false]); 176 | 177 | $file = $this->Packer->css([ 178 | '/resources/css/styles-1.css', 179 | '/resources/css/styles-2.css' 180 | ], '/cache/css/styles.css'); 181 | 182 | $file = $this->cache.'/css/styles.css'; 183 | 184 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 185 | 186 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 187 | 188 | unlink($file); 189 | 190 | $this->Packer->setConfig(['check_timestamps' => true]); 191 | } 192 | 193 | public function testPackMultipleAutonameRelative() 194 | { 195 | $file = $this->Packer->css([ 196 | '/resources/css/styles-1.css', 197 | '/resources/css/styles-2.css' 198 | ], 'css/')->getFilePath(); 199 | 200 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 201 | 202 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 203 | 204 | unlink($file); 205 | } 206 | 207 | public function testPackMultipleAutonameAbsolute() 208 | { 209 | $file = $this->Packer->css([ 210 | '/resources/css/styles-1.css', 211 | '/resources/css/styles-2.css' 212 | ], '/cache/css/')->getFilePath(); 213 | 214 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 215 | 216 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 217 | 218 | unlink($file); 219 | } 220 | 221 | public function testPackMultipleLocal() 222 | { 223 | $this->Packer->setConfig(['environment' => 'local']); 224 | 225 | $packed = $this->Packer->css([ 226 | '/resources/css/styles-1.css', 227 | '/resources/css/styles-2.css' 228 | ], 'css/'); 229 | 230 | $file = $packed->getFilePath(); 231 | 232 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 233 | 234 | $this->assertTrue(substr_count($packed->render(), 'rel="stylesheet"') === 2, 'Local environment get 2 tags to original files'); 235 | 236 | $this->Packer->setConfig(['environment' => 'testing']); 237 | } 238 | 239 | /** TESTS DIRECTORY **/ 240 | 241 | public function testPackDirectoryDefaultRelative() 242 | { 243 | $file = $this->Packer->cssDir('/resources/css/', 'css/all.css')->getFilePath(); 244 | 245 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 246 | 247 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 248 | 249 | unlink($file); 250 | 251 | $file = $this->cache.'/css/all.css'; 252 | 253 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 254 | } 255 | 256 | public function testPackDirectoryDefaultAbsolute() 257 | { 258 | $file = $this->Packer->cssDir('/resources/css/', '/cache/css/all.css')->getFilePath(); 259 | 260 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 261 | 262 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 263 | 264 | unlink($file); 265 | 266 | $file = $this->cache.'/css/all.css'; 267 | 268 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 269 | } 270 | 271 | public function testPackDirectoryNoTimestampRelative() 272 | { 273 | $this->Packer->setConfig(['check_timestamps' => false]); 274 | 275 | $this->Packer->cssDir('/resources/css/', 'css/all.css'); 276 | 277 | $file = $this->cache.'/css/all.css'; 278 | 279 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 280 | 281 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 282 | 283 | unlink($file); 284 | 285 | $this->Packer->setConfig(['check_timestamps' => true]); 286 | } 287 | 288 | public function testPackDirectoryNoTimestampAbsolute() 289 | { 290 | $this->Packer->setConfig(['check_timestamps' => false]); 291 | 292 | $this->Packer->cssDir('/resources/css/', '/cache/css/all.css'); 293 | 294 | $file = $this->cache.'/css/all.css'; 295 | 296 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 297 | 298 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 299 | 300 | unlink($file); 301 | 302 | $this->Packer->setConfig(['check_timestamps' => true]); 303 | } 304 | 305 | public function testPackDirectoryAutonameRelative() 306 | { 307 | $file = $this->Packer->cssDir('/resources/css/', 'css/')->getFilePath(); 308 | 309 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 310 | 311 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 312 | 313 | unlink($file); 314 | } 315 | 316 | public function testPackDirectoryAutonameAbsolute() 317 | { 318 | $file = $this->Packer->cssDir('/resources/css/', '/cache/css/')->getFilePath(); 319 | 320 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 321 | 322 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 323 | 324 | unlink($file); 325 | } 326 | 327 | public function testPackDirectoryAutonameAbsoluteRecursive() 328 | { 329 | $file = $this->Packer->cssDir('/resources/', '/cache/css/', true)->getFilePath(); 330 | 331 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 332 | 333 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 334 | 335 | unlink($file); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /tests/IMGTest.php: -------------------------------------------------------------------------------- 1 | Packer->setConfig(['images_fake' => false]); 11 | 12 | $file = $this->Packer->img('/resources/img/image-1.bmp', 'resizeCrop,400,400', 'img/image-1.bmp')->getFilePath(); 13 | 14 | $this->assertTrue($file === false); 15 | 16 | $this->Packer->setConfig(['images_fake' => true]); 17 | } 18 | 19 | /** 20 | * @expectedException Eusonlito\LaravelPacker\Exceptions\InvalidArgumentException 21 | */ 22 | public function testPackMultipeException() 23 | { 24 | $file = null; 25 | 26 | $file = $this->Packer->img([ 27 | '/resources/img/image-1.png', 28 | '/resources/img/image-1.png' 29 | ], 'resizeCrop,400,400', 'img/image-1.jpg')->getFilePath(); 30 | 31 | $this->assertTrue($file === null); 32 | } 33 | 34 | public function testPackOneDefaultRelative() 35 | { 36 | $file = $this->Packer->img('/resources/img/image-1.png', 'resizeCrop,400,400', 'img/image-1.png')->getFilePath(); 37 | 38 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 39 | 40 | $oldfile = $this->Packer->path('public', '/resources/img/image-1.png'); 41 | 42 | $this->assertFalse(file_get_contents($oldfile) === file_get_contents($file)); 43 | 44 | unlink($file); 45 | } 46 | 47 | public function testPackOneDefaultAbsolute() 48 | { 49 | $file = $this->Packer->img('/resources/img/image-1.png', 'resizeCrop,400,400', '/cache/img/image-1.png')->getFilePath(); 50 | 51 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 52 | 53 | $oldfile = $this->Packer->path('public', '/resources/img/image-1.png'); 54 | 55 | $this->assertFalse(file_get_contents($oldfile) === file_get_contents($file)); 56 | 57 | unlink($file); 58 | } 59 | 60 | public function testPackOneNoTimestampRelative() 61 | { 62 | $this->Packer->setConfig([ 63 | 'check_timestamps' => false 64 | ]); 65 | 66 | $file = $this->Packer->img('/resources/img/image-1.png', 'resizeCrop,400,400', '/cache/img/image-1.png')->getFilePath(); 67 | 68 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 69 | 70 | $oldfile = $this->Packer->path('public', '/resources/img/image-1.png'); 71 | 72 | $this->assertFalse(file_get_contents($oldfile) === file_get_contents($file)); 73 | 74 | $file = $this->cache.'/img/image-1.png'; 75 | 76 | $this->assertFileExists($file, sprintf('File %s exists', $file)); 77 | 78 | $this->Packer->setConfig([ 79 | 'check_timestamps' => true 80 | ]); 81 | } 82 | 83 | public function testPackOneNoTimestampRelativeMissing() 84 | { 85 | $this->Packer->setConfig([ 86 | 'check_timestamps' => false, 87 | 'images_fake' => false 88 | ]); 89 | 90 | $file = $this->Packer->img('/resources/img/NOTEXISTS.png', 'resizeCrop,400,400', '/cache/img/NOTEXISTS.png')->getFilePath(); 91 | 92 | $this->assertFalse($file); 93 | 94 | $file = $this->cache.'/img/NOTEXISTS.png'; 95 | 96 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 97 | 98 | $this->Packer->setConfig([ 99 | 'check_timestamps' => true, 100 | 'images_fake' => true 101 | ]); 102 | } 103 | 104 | public function testPackOneNoTimestampRelativeFake() 105 | { 106 | $this->Packer->setConfig([ 107 | 'check_timestamps' => false 108 | ]); 109 | 110 | $oldfile = $this->Packer->path('public', '/resources/img/NOTEXISTS.png'); 111 | 112 | $this->assertFileNotExists($oldfile, sprintf('File %s not exists', $oldfile)); 113 | 114 | $file = $this->Packer->img('/resources/img/NOTEXISTS.png', 'resizeCrop,400,400', '/cache/img/image-FAKE.png')->getFilePath(); 115 | 116 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 117 | 118 | $file = $this->cache.'/img/image-FAKE.png'; 119 | 120 | $this->assertFileExists($file, sprintf('File %s was created successfully from Fake', $file)); 121 | 122 | $this->assertTrue(filesize($file) > 0); 123 | 124 | $this->Packer->setConfig([ 125 | 'check_timestamps' => true 126 | ]); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/JSTest.php: -------------------------------------------------------------------------------- 1 | Packer->js('/resources/js/scripts-1.js', 'js/scripts-1.js')->getFilePath(); 9 | 10 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 11 | 12 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 13 | 14 | unlink($file); 15 | 16 | $file = $this->cache.'/js/scripts-1.js'; 17 | 18 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 19 | } 20 | 21 | public function testPackOneDefaultAbsolute() 22 | { 23 | $file = $this->Packer->js('/resources/js/scripts-1.js', '/cache/js/scripts-1.js')->getFilePath(); 24 | 25 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 26 | 27 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 28 | 29 | unlink($file); 30 | 31 | $file = $this->cache.'/js/scripts-1.js'; 32 | 33 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 34 | } 35 | 36 | public function testPackOneNoTimestampRelative() 37 | { 38 | $this->Packer->setConfig(['check_timestamps' => false]); 39 | 40 | $this->Packer->js('/resources/js/scripts-1.js', 'js/scripts-1.js'); 41 | 42 | $file = $this->cache.'/js/scripts-1.js'; 43 | 44 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 45 | 46 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 47 | 48 | unlink($file); 49 | 50 | $this->Packer->setConfig(['check_timestamps' => true]); 51 | } 52 | 53 | public function testPackOneNoTimestampAbsolute() 54 | { 55 | $this->Packer->setConfig(['check_timestamps' => false]); 56 | 57 | $this->Packer->js('/resources/js/scripts-1.js', '/cache/js/scripts-1.js'); 58 | 59 | $file = $this->cache.'/js/scripts-1.js'; 60 | 61 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 62 | 63 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 64 | 65 | unlink($file); 66 | 67 | $this->Packer->setConfig(['check_timestamps' => true]); 68 | } 69 | 70 | public function testPackOneAutonameRelative() 71 | { 72 | $file = $this->Packer->js('/resources/js/scripts-1.js', 'js/')->getFilePath(); 73 | 74 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 75 | 76 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 77 | 78 | unlink($file); 79 | } 80 | 81 | public function testPackOneAutonameAbsolute() 82 | { 83 | $file = $this->Packer->js('/resources/js/scripts-1.js', '/cache/js/')->getFilePath(); 84 | 85 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 86 | 87 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1']); 88 | 89 | unlink($file); 90 | } 91 | 92 | /** TESTS WITH MULTIPLE FILES **/ 93 | 94 | public function testPackMultipleDefaultRelative() 95 | { 96 | $file = $this->Packer->js([ 97 | '/resources/js/scripts-1.js', 98 | '/resources/js/scripts-2.js' 99 | ], 'js/scripts.js')->getFilePath(); 100 | 101 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 102 | 103 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 104 | 105 | unlink($file); 106 | 107 | $file = $this->cache.'/js/scripts.js'; 108 | 109 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 110 | } 111 | 112 | public function testPackMultipleDefaultAbsolute() 113 | { 114 | $file = $this->Packer->js([ 115 | '/resources/js/scripts-1.js', 116 | '/resources/js/scripts-2.js' 117 | ], '/cache/js/scripts.js')->getFilePath(); 118 | 119 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 120 | 121 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 122 | 123 | unlink($file); 124 | 125 | $file = $this->cache.'/js/scripts.js'; 126 | 127 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 128 | } 129 | 130 | public function testPackMultipleNoTimestampRelative() 131 | { 132 | $this->Packer->setConfig(['check_timestamps' => false]); 133 | 134 | $file = $this->Packer->js([ 135 | '/resources/js/scripts-1.js', 136 | '/resources/js/scripts-2.js' 137 | ], 'js/scripts.js'); 138 | 139 | $file = $this->cache.'/js/scripts.js'; 140 | 141 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 142 | 143 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 144 | 145 | unlink($file); 146 | 147 | $this->Packer->setConfig(['check_timestamps' => true]); 148 | } 149 | 150 | public function testPackMultipleNoTimestampAbsolute() 151 | { 152 | $this->Packer->setConfig(['check_timestamps' => false]); 153 | 154 | $file = $this->Packer->js([ 155 | '/resources/js/scripts-1.js', 156 | '/resources/js/scripts-2.js' 157 | ], '/cache/js/scripts.js'); 158 | 159 | $file = $this->cache.'/js/scripts.js'; 160 | 161 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 162 | 163 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 164 | 165 | unlink($file); 166 | 167 | $this->Packer->setConfig(['check_timestamps' => true]); 168 | } 169 | 170 | public function testPackMultipleAutonameRelative() 171 | { 172 | $file = $this->Packer->js([ 173 | '/resources/js/scripts-1.js', 174 | '/resources/js/scripts-2.js' 175 | ], 'js/')->getFilePath(); 176 | 177 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 178 | 179 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 180 | 181 | unlink($file); 182 | } 183 | 184 | public function testPackMultipleAutonameAbsolute() 185 | { 186 | $file = $this->Packer->js([ 187 | '/resources/js/scripts-1.js', 188 | '/resources/js/scripts-2.js' 189 | ], '/cache/js/')->getFilePath(); 190 | 191 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 192 | 193 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 194 | 195 | unlink($file); 196 | } 197 | 198 | public function testPackMultipleLocal() 199 | { 200 | $this->Packer->setConfig(['environment' => 'local']); 201 | 202 | $packed = $this->Packer->js([ 203 | '/resources/js/scripts-1.js', 204 | '/resources/js/scripts-2.js' 205 | ], 'js/'); 206 | 207 | $file = $packed->getFilePath(); 208 | 209 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 210 | 211 | $this->assertTrue(substr_count($packed->render(), '') === 2, 'Local environment get 2 tags to original files'); 212 | 213 | $this->Packer->setConfig(['environment' => 'testing']); 214 | } 215 | 216 | /** TESTS DIRECTORY **/ 217 | 218 | public function testPackDirectoryDefaultRelative() 219 | { 220 | $file = $this->Packer->jsDir('/resources/js/', 'js/all.js')->getFilePath(); 221 | 222 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 223 | 224 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 225 | 226 | unlink($file); 227 | 228 | $file = $this->cache.'/js/all.js'; 229 | 230 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 231 | } 232 | 233 | public function testPackDirectoryDefaultAbsolute() 234 | { 235 | $file = $this->Packer->jsDir('/resources/js/', '/cache/js/all.js')->getFilePath(); 236 | 237 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 238 | 239 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 240 | 241 | unlink($file); 242 | 243 | $file = $this->cache.'/js/all.js'; 244 | 245 | $this->assertFileNotExists($file, sprintf('File %s not exists', $file)); 246 | } 247 | 248 | public function testPackDirectoryNoTimestampRelative() 249 | { 250 | $this->Packer->setConfig(['check_timestamps' => false]); 251 | 252 | $this->Packer->jsDir('/resources/js/', 'js/all.js'); 253 | 254 | $file = $this->cache.'/js/all.js'; 255 | 256 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 257 | 258 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 259 | 260 | unlink($file); 261 | 262 | $this->Packer->setConfig(['check_timestamps' => true]); 263 | } 264 | 265 | public function testPackDirectoryNoTimestampAbsolute() 266 | { 267 | $this->Packer->setConfig(['check_timestamps' => false]); 268 | 269 | $this->Packer->jsDir('/resources/js/', '/cache/js/all.js'); 270 | 271 | $file = $this->cache.'/js/all.js'; 272 | 273 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 274 | 275 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 276 | 277 | unlink($file); 278 | 279 | $this->Packer->setConfig(['check_timestamps' => true]); 280 | } 281 | 282 | public function testPackDirectoryAutonameRelative() 283 | { 284 | $file = $this->Packer->jsDir('/resources/js/', 'js/')->getFilePath(); 285 | 286 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 287 | 288 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 289 | 290 | unlink($file); 291 | } 292 | 293 | public function testPackDirectoryAutonameAbsolute() 294 | { 295 | $file = $this->Packer->jsDir('/resources/js/', '/cache/js/')->getFilePath(); 296 | 297 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 298 | 299 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 300 | 301 | unlink($file); 302 | } 303 | 304 | public function testPackDirectoryAutonameAbsoluteRecursive() 305 | { 306 | $file = $this->Packer->jsDir('/resources/', '/cache/js/', true)->getFilePath(); 307 | 308 | $this->assertFileExists($file, sprintf('File %s was created successfully', $file)); 309 | 310 | $this->checkContents($file, ['_TEST_INI_FILE1', '_TEST_END_FILE1', '_TEST_INI_FILE2', '_TEST_END_FILE2']); 311 | 312 | unlink($file); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /tests/resources/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/tests/resources/image-1.png -------------------------------------------------------------------------------- /tests/resources/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eusonlito/laravel-Packer/7a4d8baa0ca2fc6c4df63a6b44a56323fdb8f023/tests/resources/image-2.jpg -------------------------------------------------------------------------------- /tests/resources/styles-2.css: -------------------------------------------------------------------------------- 1 | html._TEST_INI_FILE2 {font-size: 1em;} 2 | 3 | /*! 4 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 5 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 6 | */ 7 | /* FONT PATH 8 | * -------------------------- */ 9 | @font-face { 10 | font-family: 'FontAwesome'; 11 | src: url('../fonts/fontawesome-webfont.eot?v=4.0.3'); 12 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | .fa { 17 | display: inline-block; 18 | font-family: FontAwesome; 19 | font-style: normal; 20 | font-weight: normal; 21 | line-height: 1; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | /* makes the font 33% larger relative to the icon container */ 26 | .fa-lg { 27 | font-size: 1.3333333333333333em; 28 | line-height: 0.75em; 29 | vertical-align: -15%; 30 | } 31 | .fa-2x { 32 | font-size: 2em; 33 | } 34 | .fa-3x { 35 | font-size: 3em; 36 | } 37 | .fa-4x { 38 | font-size: 4em; 39 | } 40 | .fa-5x { 41 | font-size: 5em; 42 | } 43 | .fa-fw { 44 | width: 1.2857142857142858em; 45 | text-align: center; 46 | } 47 | .fa-ul { 48 | padding-left: 0; 49 | margin-left: 2.142857142857143em; 50 | list-style-type: none; 51 | } 52 | .fa-ul > li { 53 | position: relative; 54 | } 55 | .fa-li { 56 | position: absolute; 57 | left: -2.142857142857143em; 58 | width: 2.142857142857143em; 59 | top: 0.14285714285714285em; 60 | text-align: center; 61 | } 62 | .fa-li.fa-lg { 63 | left: -1.8571428571428572em; 64 | } 65 | .fa-border { 66 | padding: .2em .25em .15em; 67 | border: solid 0.08em #eeeeee; 68 | border-radius: .1em; 69 | } 70 | .pull-right { 71 | float: right; 72 | } 73 | .pull-left { 74 | float: left; 75 | } 76 | .fa.pull-left { 77 | margin-right: .3em; 78 | } 79 | .fa.pull-right { 80 | margin-left: .3em; 81 | } 82 | .fa-spin { 83 | -webkit-animation: spin 2s infinite linear; 84 | -moz-animation: spin 2s infinite linear; 85 | -o-animation: spin 2s infinite linear; 86 | animation: spin 2s infinite linear; 87 | } 88 | @-moz-keyframes spin { 89 | 0% { 90 | -moz-transform: rotate(0deg); 91 | } 92 | 100% { 93 | -moz-transform: rotate(359deg); 94 | } 95 | } 96 | @-webkit-keyframes spin { 97 | 0% { 98 | -webkit-transform: rotate(0deg); 99 | } 100 | 100% { 101 | -webkit-transform: rotate(359deg); 102 | } 103 | } 104 | @-o-keyframes spin { 105 | 0% { 106 | -o-transform: rotate(0deg); 107 | } 108 | 100% { 109 | -o-transform: rotate(359deg); 110 | } 111 | } 112 | @-ms-keyframes spin { 113 | 0% { 114 | -ms-transform: rotate(0deg); 115 | } 116 | 100% { 117 | -ms-transform: rotate(359deg); 118 | } 119 | } 120 | @keyframes spin { 121 | 0% { 122 | transform: rotate(0deg); 123 | } 124 | 100% { 125 | transform: rotate(359deg); 126 | } 127 | } 128 | .fa-rotate-90 { 129 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 130 | -webkit-transform: rotate(90deg); 131 | -moz-transform: rotate(90deg); 132 | -ms-transform: rotate(90deg); 133 | -o-transform: rotate(90deg); 134 | transform: rotate(90deg); 135 | } 136 | .fa-rotate-180 { 137 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 138 | -webkit-transform: rotate(180deg); 139 | -moz-transform: rotate(180deg); 140 | -ms-transform: rotate(180deg); 141 | -o-transform: rotate(180deg); 142 | transform: rotate(180deg); 143 | } 144 | .fa-rotate-270 { 145 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 146 | -webkit-transform: rotate(270deg); 147 | -moz-transform: rotate(270deg); 148 | -ms-transform: rotate(270deg); 149 | -o-transform: rotate(270deg); 150 | transform: rotate(270deg); 151 | } 152 | .fa-flip-horizontal { 153 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 154 | -webkit-transform: scale(-1, 1); 155 | -moz-transform: scale(-1, 1); 156 | -ms-transform: scale(-1, 1); 157 | -o-transform: scale(-1, 1); 158 | transform: scale(-1, 1); 159 | } 160 | .fa-flip-vertical { 161 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 162 | -webkit-transform: scale(1, -1); 163 | -moz-transform: scale(1, -1); 164 | -ms-transform: scale(1, -1); 165 | -o-transform: scale(1, -1); 166 | transform: scale(1, -1); 167 | } 168 | .fa-stack { 169 | position: relative; 170 | display: inline-block; 171 | width: 2em; 172 | height: 2em; 173 | line-height: 2em; 174 | vertical-align: middle; 175 | } 176 | .fa-stack-1x, 177 | .fa-stack-2x { 178 | position: absolute; 179 | left: 0; 180 | width: 100%; 181 | text-align: center; 182 | } 183 | .fa-stack-1x { 184 | line-height: inherit; 185 | } 186 | .fa-stack-2x { 187 | font-size: 2em; 188 | } 189 | .fa-inverse { 190 | color: #ffffff; 191 | } 192 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 193 | readers do not read off random characters that represent icons */ 194 | .fa-glass:before { 195 | content: "\f000"; 196 | } 197 | .fa-music:before { 198 | content: "\f001"; 199 | } 200 | .fa-search:before { 201 | content: "\f002"; 202 | } 203 | .fa-envelope-o:before { 204 | content: "\f003"; 205 | } 206 | .fa-heart:before { 207 | content: "\f004"; 208 | } 209 | .fa-star:before { 210 | content: "\f005"; 211 | } 212 | .fa-star-o:before { 213 | content: "\f006"; 214 | } 215 | .fa-user:before { 216 | content: "\f007"; 217 | } 218 | .fa-film:before { 219 | content: "\f008"; 220 | } 221 | .fa-th-large:before { 222 | content: "\f009"; 223 | } 224 | .fa-th:before { 225 | content: "\f00a"; 226 | } 227 | .fa-th-list:before { 228 | content: "\f00b"; 229 | } 230 | .fa-check:before { 231 | content: "\f00c"; 232 | } 233 | .fa-times:before { 234 | content: "\f00d"; 235 | } 236 | .fa-search-plus:before { 237 | content: "\f00e"; 238 | } 239 | .fa-search-minus:before { 240 | content: "\f010"; 241 | } 242 | .fa-power-off:before { 243 | content: "\f011"; 244 | } 245 | .fa-signal:before { 246 | content: "\f012"; 247 | } 248 | .fa-gear:before, 249 | .fa-cog:before { 250 | content: "\f013"; 251 | } 252 | .fa-trash-o:before { 253 | content: "\f014"; 254 | } 255 | .fa-home:before { 256 | content: "\f015"; 257 | } 258 | .fa-file-o:before { 259 | content: "\f016"; 260 | } 261 | .fa-clock-o:before { 262 | content: "\f017"; 263 | } 264 | .fa-road:before { 265 | content: "\f018"; 266 | } 267 | .fa-download:before { 268 | content: "\f019"; 269 | } 270 | .fa-arrow-circle-o-down:before { 271 | content: "\f01a"; 272 | } 273 | .fa-arrow-circle-o-up:before { 274 | content: "\f01b"; 275 | } 276 | .fa-inbox:before { 277 | content: "\f01c"; 278 | } 279 | .fa-play-circle-o:before { 280 | content: "\f01d"; 281 | } 282 | .fa-rotate-right:before, 283 | .fa-repeat:before { 284 | content: "\f01e"; 285 | } 286 | .fa-refresh:before { 287 | content: "\f021"; 288 | } 289 | .fa-list-alt:before { 290 | content: "\f022"; 291 | } 292 | .fa-lock:before { 293 | content: "\f023"; 294 | } 295 | .fa-flag:before { 296 | content: "\f024"; 297 | } 298 | .fa-headphones:before { 299 | content: "\f025"; 300 | } 301 | .fa-volume-off:before { 302 | content: "\f026"; 303 | } 304 | .fa-volume-down:before { 305 | content: "\f027"; 306 | } 307 | .fa-volume-up:before { 308 | content: "\f028"; 309 | } 310 | .fa-qrcode:before { 311 | content: "\f029"; 312 | } 313 | .fa-barcode:before { 314 | content: "\f02a"; 315 | } 316 | .fa-tag:before { 317 | content: "\f02b"; 318 | } 319 | .fa-tags:before { 320 | content: "\f02c"; 321 | } 322 | .fa-book:before { 323 | content: "\f02d"; 324 | } 325 | .fa-bookmark:before { 326 | content: "\f02e"; 327 | } 328 | .fa-print:before { 329 | content: "\f02f"; 330 | } 331 | .fa-camera:before { 332 | content: "\f030"; 333 | } 334 | .fa-font:before { 335 | content: "\f031"; 336 | } 337 | .fa-bold:before { 338 | content: "\f032"; 339 | } 340 | .fa-italic:before { 341 | content: "\f033"; 342 | } 343 | .fa-text-height:before { 344 | content: "\f034"; 345 | } 346 | .fa-text-width:before { 347 | content: "\f035"; 348 | } 349 | .fa-align-left:before { 350 | content: "\f036"; 351 | } 352 | .fa-align-center:before { 353 | content: "\f037"; 354 | } 355 | .fa-align-right:before { 356 | content: "\f038"; 357 | } 358 | .fa-align-justify:before { 359 | content: "\f039"; 360 | } 361 | .fa-list:before { 362 | content: "\f03a"; 363 | } 364 | .fa-dedent:before, 365 | .fa-outdent:before { 366 | content: "\f03b"; 367 | } 368 | .fa-indent:before { 369 | content: "\f03c"; 370 | } 371 | .fa-video-camera:before { 372 | content: "\f03d"; 373 | } 374 | .fa-picture-o:before { 375 | content: "\f03e"; 376 | } 377 | .fa-pencil:before { 378 | content: "\f040"; 379 | } 380 | .fa-map-marker:before { 381 | content: "\f041"; 382 | } 383 | .fa-adjust:before { 384 | content: "\f042"; 385 | } 386 | .fa-tint:before { 387 | content: "\f043"; 388 | } 389 | .fa-edit:before, 390 | .fa-pencil-square-o:before { 391 | content: "\f044"; 392 | } 393 | .fa-share-square-o:before { 394 | content: "\f045"; 395 | } 396 | .fa-check-square-o:before { 397 | content: "\f046"; 398 | } 399 | .fa-arrows:before { 400 | content: "\f047"; 401 | } 402 | .fa-step-backward:before { 403 | content: "\f048"; 404 | } 405 | .fa-fast-backward:before { 406 | content: "\f049"; 407 | } 408 | .fa-backward:before { 409 | content: "\f04a"; 410 | } 411 | .fa-play:before { 412 | content: "\f04b"; 413 | } 414 | .fa-pause:before { 415 | content: "\f04c"; 416 | } 417 | .fa-stop:before { 418 | content: "\f04d"; 419 | } 420 | .fa-forward:before { 421 | content: "\f04e"; 422 | } 423 | .fa-fast-forward:before { 424 | content: "\f050"; 425 | } 426 | .fa-step-forward:before { 427 | content: "\f051"; 428 | } 429 | .fa-eject:before { 430 | content: "\f052"; 431 | } 432 | .fa-chevron-left:before { 433 | content: "\f053"; 434 | } 435 | .fa-chevron-right:before { 436 | content: "\f054"; 437 | } 438 | .fa-plus-circle:before { 439 | content: "\f055"; 440 | } 441 | .fa-minus-circle:before { 442 | content: "\f056"; 443 | } 444 | .fa-times-circle:before { 445 | content: "\f057"; 446 | } 447 | .fa-check-circle:before { 448 | content: "\f058"; 449 | } 450 | .fa-question-circle:before { 451 | content: "\f059"; 452 | } 453 | .fa-info-circle:before { 454 | content: "\f05a"; 455 | } 456 | .fa-crosshairs:before { 457 | content: "\f05b"; 458 | } 459 | .fa-times-circle-o:before { 460 | content: "\f05c"; 461 | } 462 | .fa-check-circle-o:before { 463 | content: "\f05d"; 464 | } 465 | .fa-ban:before { 466 | content: "\f05e"; 467 | } 468 | .fa-arrow-left:before { 469 | content: "\f060"; 470 | } 471 | .fa-arrow-right:before { 472 | content: "\f061"; 473 | } 474 | .fa-arrow-up:before { 475 | content: "\f062"; 476 | } 477 | .fa-arrow-down:before { 478 | content: "\f063"; 479 | } 480 | .fa-mail-forward:before, 481 | .fa-share:before { 482 | content: "\f064"; 483 | } 484 | .fa-expand:before { 485 | content: "\f065"; 486 | } 487 | .fa-compress:before { 488 | content: "\f066"; 489 | } 490 | .fa-plus:before { 491 | content: "\f067"; 492 | } 493 | .fa-minus:before { 494 | content: "\f068"; 495 | } 496 | .fa-asterisk:before { 497 | content: "\f069"; 498 | } 499 | .fa-exclamation-circle:before { 500 | content: "\f06a"; 501 | } 502 | .fa-gift:before { 503 | content: "\f06b"; 504 | } 505 | .fa-leaf:before { 506 | content: "\f06c"; 507 | } 508 | .fa-fire:before { 509 | content: "\f06d"; 510 | } 511 | .fa-eye:before { 512 | content: "\f06e"; 513 | } 514 | .fa-eye-slash:before { 515 | content: "\f070"; 516 | } 517 | .fa-warning:before, 518 | .fa-exclamation-triangle:before { 519 | content: "\f071"; 520 | } 521 | .fa-plane:before { 522 | content: "\f072"; 523 | } 524 | .fa-calendar:before { 525 | content: "\f073"; 526 | } 527 | .fa-random:before { 528 | content: "\f074"; 529 | } 530 | .fa-comment:before { 531 | content: "\f075"; 532 | } 533 | .fa-magnet:before { 534 | content: "\f076"; 535 | } 536 | .fa-chevron-up:before { 537 | content: "\f077"; 538 | } 539 | .fa-chevron-down:before { 540 | content: "\f078"; 541 | } 542 | .fa-retweet:before { 543 | content: "\f079"; 544 | } 545 | .fa-shopping-cart:before { 546 | content: "\f07a"; 547 | } 548 | .fa-folder:before { 549 | content: "\f07b"; 550 | } 551 | .fa-folder-open:before { 552 | content: "\f07c"; 553 | } 554 | .fa-arrows-v:before { 555 | content: "\f07d"; 556 | } 557 | .fa-arrows-h:before { 558 | content: "\f07e"; 559 | } 560 | .fa-bar-chart-o:before { 561 | content: "\f080"; 562 | } 563 | .fa-twitter-square:before { 564 | content: "\f081"; 565 | } 566 | .fa-facebook-square:before { 567 | content: "\f082"; 568 | } 569 | .fa-camera-retro:before { 570 | content: "\f083"; 571 | } 572 | .fa-key:before { 573 | content: "\f084"; 574 | } 575 | .fa-gears:before, 576 | .fa-cogs:before { 577 | content: "\f085"; 578 | } 579 | .fa-comments:before { 580 | content: "\f086"; 581 | } 582 | .fa-thumbs-o-up:before { 583 | content: "\f087"; 584 | } 585 | .fa-thumbs-o-down:before { 586 | content: "\f088"; 587 | } 588 | .fa-star-half:before { 589 | content: "\f089"; 590 | } 591 | .fa-heart-o:before { 592 | content: "\f08a"; 593 | } 594 | .fa-sign-out:before { 595 | content: "\f08b"; 596 | } 597 | .fa-linkedin-square:before { 598 | content: "\f08c"; 599 | } 600 | .fa-thumb-tack:before { 601 | content: "\f08d"; 602 | } 603 | .fa-external-link:before { 604 | content: "\f08e"; 605 | } 606 | .fa-sign-in:before { 607 | content: "\f090"; 608 | } 609 | .fa-trophy:before { 610 | content: "\f091"; 611 | } 612 | .fa-github-square:before { 613 | content: "\f092"; 614 | } 615 | .fa-upload:before { 616 | content: "\f093"; 617 | } 618 | .fa-lemon-o:before { 619 | content: "\f094"; 620 | } 621 | .fa-phone:before { 622 | content: "\f095"; 623 | } 624 | .fa-square-o:before { 625 | content: "\f096"; 626 | } 627 | .fa-bookmark-o:before { 628 | content: "\f097"; 629 | } 630 | .fa-phone-square:before { 631 | content: "\f098"; 632 | } 633 | .fa-twitter:before { 634 | content: "\f099"; 635 | } 636 | .fa-facebook:before { 637 | content: "\f09a"; 638 | } 639 | .fa-github:before { 640 | content: "\f09b"; 641 | } 642 | .fa-unlock:before { 643 | content: "\f09c"; 644 | } 645 | .fa-credit-card:before { 646 | content: "\f09d"; 647 | } 648 | .fa-rss:before { 649 | content: "\f09e"; 650 | } 651 | .fa-hdd-o:before { 652 | content: "\f0a0"; 653 | } 654 | .fa-bullhorn:before { 655 | content: "\f0a1"; 656 | } 657 | .fa-bell:before { 658 | content: "\f0f3"; 659 | } 660 | .fa-certificate:before { 661 | content: "\f0a3"; 662 | } 663 | .fa-hand-o-right:before { 664 | content: "\f0a4"; 665 | } 666 | .fa-hand-o-left:before { 667 | content: "\f0a5"; 668 | } 669 | .fa-hand-o-up:before { 670 | content: "\f0a6"; 671 | } 672 | .fa-hand-o-down:before { 673 | content: "\f0a7"; 674 | } 675 | .fa-arrow-circle-left:before { 676 | content: "\f0a8"; 677 | } 678 | .fa-arrow-circle-right:before { 679 | content: "\f0a9"; 680 | } 681 | .fa-arrow-circle-up:before { 682 | content: "\f0aa"; 683 | } 684 | .fa-arrow-circle-down:before { 685 | content: "\f0ab"; 686 | } 687 | .fa-globe:before { 688 | content: "\f0ac"; 689 | } 690 | .fa-wrench:before { 691 | content: "\f0ad"; 692 | } 693 | .fa-tasks:before { 694 | content: "\f0ae"; 695 | } 696 | .fa-filter:before { 697 | content: "\f0b0"; 698 | } 699 | .fa-briefcase:before { 700 | content: "\f0b1"; 701 | } 702 | .fa-arrows-alt:before { 703 | content: "\f0b2"; 704 | } 705 | .fa-group:before, 706 | .fa-users:before { 707 | content: "\f0c0"; 708 | } 709 | .fa-chain:before, 710 | .fa-link:before { 711 | content: "\f0c1"; 712 | } 713 | .fa-cloud:before { 714 | content: "\f0c2"; 715 | } 716 | .fa-flask:before { 717 | content: "\f0c3"; 718 | } 719 | .fa-cut:before, 720 | .fa-scissors:before { 721 | content: "\f0c4"; 722 | } 723 | .fa-copy:before, 724 | .fa-files-o:before { 725 | content: "\f0c5"; 726 | } 727 | .fa-paperclip:before { 728 | content: "\f0c6"; 729 | } 730 | .fa-save:before, 731 | .fa-floppy-o:before { 732 | content: "\f0c7"; 733 | } 734 | .fa-square:before { 735 | content: "\f0c8"; 736 | } 737 | .fa-bars:before { 738 | content: "\f0c9"; 739 | } 740 | .fa-list-ul:before { 741 | content: "\f0ca"; 742 | } 743 | .fa-list-ol:before { 744 | content: "\f0cb"; 745 | } 746 | .fa-strikethrough:before { 747 | content: "\f0cc"; 748 | } 749 | .fa-underline:before { 750 | content: "\f0cd"; 751 | } 752 | .fa-table:before { 753 | content: "\f0ce"; 754 | } 755 | .fa-magic:before { 756 | content: "\f0d0"; 757 | } 758 | .fa-truck:before { 759 | content: "\f0d1"; 760 | } 761 | .fa-pinterest:before { 762 | content: "\f0d2"; 763 | } 764 | .fa-pinterest-square:before { 765 | content: "\f0d3"; 766 | } 767 | .fa-google-plus-square:before { 768 | content: "\f0d4"; 769 | } 770 | .fa-google-plus:before { 771 | content: "\f0d5"; 772 | } 773 | .fa-money:before { 774 | content: "\f0d6"; 775 | } 776 | .fa-caret-down:before { 777 | content: "\f0d7"; 778 | } 779 | .fa-caret-up:before { 780 | content: "\f0d8"; 781 | } 782 | .fa-caret-left:before { 783 | content: "\f0d9"; 784 | } 785 | .fa-caret-right:before { 786 | content: "\f0da"; 787 | } 788 | .fa-columns:before { 789 | content: "\f0db"; 790 | } 791 | .fa-unsorted:before, 792 | .fa-sort:before { 793 | content: "\f0dc"; 794 | } 795 | .fa-sort-down:before, 796 | .fa-sort-asc:before { 797 | content: "\f0dd"; 798 | } 799 | .fa-sort-up:before, 800 | .fa-sort-desc:before { 801 | content: "\f0de"; 802 | } 803 | .fa-envelope:before { 804 | content: "\f0e0"; 805 | } 806 | .fa-linkedin:before { 807 | content: "\f0e1"; 808 | } 809 | .fa-rotate-left:before, 810 | .fa-undo:before { 811 | content: "\f0e2"; 812 | } 813 | .fa-legal:before, 814 | .fa-gavel:before { 815 | content: "\f0e3"; 816 | } 817 | .fa-dashboard:before, 818 | .fa-tachometer:before { 819 | content: "\f0e4"; 820 | } 821 | .fa-comment-o:before { 822 | content: "\f0e5"; 823 | } 824 | .fa-comments-o:before { 825 | content: "\f0e6"; 826 | } 827 | .fa-flash:before, 828 | .fa-bolt:before { 829 | content: "\f0e7"; 830 | } 831 | .fa-sitemap:before { 832 | content: "\f0e8"; 833 | } 834 | .fa-umbrella:before { 835 | content: "\f0e9"; 836 | } 837 | .fa-paste:before, 838 | .fa-clipboard:before { 839 | content: "\f0ea"; 840 | } 841 | .fa-lightbulb-o:before { 842 | content: "\f0eb"; 843 | } 844 | .fa-exchange:before { 845 | content: "\f0ec"; 846 | } 847 | .fa-cloud-download:before { 848 | content: "\f0ed"; 849 | } 850 | .fa-cloud-upload:before { 851 | content: "\f0ee"; 852 | } 853 | .fa-user-md:before { 854 | content: "\f0f0"; 855 | } 856 | .fa-stethoscope:before { 857 | content: "\f0f1"; 858 | } 859 | .fa-suitcase:before { 860 | content: "\f0f2"; 861 | } 862 | .fa-bell-o:before { 863 | content: "\f0a2"; 864 | } 865 | .fa-coffee:before { 866 | content: "\f0f4"; 867 | } 868 | .fa-cutlery:before { 869 | content: "\f0f5"; 870 | } 871 | .fa-file-text-o:before { 872 | content: "\f0f6"; 873 | } 874 | .fa-building-o:before { 875 | content: "\f0f7"; 876 | } 877 | .fa-hospital-o:before { 878 | content: "\f0f8"; 879 | } 880 | .fa-ambulance:before { 881 | content: "\f0f9"; 882 | } 883 | .fa-medkit:before { 884 | content: "\f0fa"; 885 | } 886 | .fa-fighter-jet:before { 887 | content: "\f0fb"; 888 | } 889 | .fa-beer:before { 890 | content: "\f0fc"; 891 | } 892 | .fa-h-square:before { 893 | content: "\f0fd"; 894 | } 895 | .fa-plus-square:before { 896 | content: "\f0fe"; 897 | } 898 | .fa-angle-double-left:before { 899 | content: "\f100"; 900 | } 901 | .fa-angle-double-right:before { 902 | content: "\f101"; 903 | } 904 | .fa-angle-double-up:before { 905 | content: "\f102"; 906 | } 907 | .fa-angle-double-down:before { 908 | content: "\f103"; 909 | } 910 | .fa-angle-left:before { 911 | content: "\f104"; 912 | } 913 | .fa-angle-right:before { 914 | content: "\f105"; 915 | } 916 | .fa-angle-up:before { 917 | content: "\f106"; 918 | } 919 | .fa-angle-down:before { 920 | content: "\f107"; 921 | } 922 | .fa-desktop:before { 923 | content: "\f108"; 924 | } 925 | .fa-laptop:before { 926 | content: "\f109"; 927 | } 928 | .fa-tablet:before { 929 | content: "\f10a"; 930 | } 931 | .fa-mobile-phone:before, 932 | .fa-mobile:before { 933 | content: "\f10b"; 934 | } 935 | .fa-circle-o:before { 936 | content: "\f10c"; 937 | } 938 | .fa-quote-left:before { 939 | content: "\f10d"; 940 | } 941 | .fa-quote-right:before { 942 | content: "\f10e"; 943 | } 944 | .fa-spinner:before { 945 | content: "\f110"; 946 | } 947 | .fa-circle:before { 948 | content: "\f111"; 949 | } 950 | .fa-mail-reply:before, 951 | .fa-reply:before { 952 | content: "\f112"; 953 | } 954 | .fa-github-alt:before { 955 | content: "\f113"; 956 | } 957 | .fa-folder-o:before { 958 | content: "\f114"; 959 | } 960 | .fa-folder-open-o:before { 961 | content: "\f115"; 962 | } 963 | .fa-smile-o:before { 964 | content: "\f118"; 965 | } 966 | .fa-frown-o:before { 967 | content: "\f119"; 968 | } 969 | .fa-meh-o:before { 970 | content: "\f11a"; 971 | } 972 | .fa-gamepad:before { 973 | content: "\f11b"; 974 | } 975 | .fa-keyboard-o:before { 976 | content: "\f11c"; 977 | } 978 | .fa-flag-o:before { 979 | content: "\f11d"; 980 | } 981 | .fa-flag-checkered:before { 982 | content: "\f11e"; 983 | } 984 | .fa-terminal:before { 985 | content: "\f120"; 986 | } 987 | .fa-code:before { 988 | content: "\f121"; 989 | } 990 | .fa-reply-all:before { 991 | content: "\f122"; 992 | } 993 | .fa-mail-reply-all:before { 994 | content: "\f122"; 995 | } 996 | .fa-star-half-empty:before, 997 | .fa-star-half-full:before, 998 | .fa-star-half-o:before { 999 | content: "\f123"; 1000 | } 1001 | .fa-location-arrow:before { 1002 | content: "\f124"; 1003 | } 1004 | .fa-crop:before { 1005 | content: "\f125"; 1006 | } 1007 | .fa-code-fork:before { 1008 | content: "\f126"; 1009 | } 1010 | .fa-unlink:before, 1011 | .fa-chain-broken:before { 1012 | content: "\f127"; 1013 | } 1014 | .fa-question:before { 1015 | content: "\f128"; 1016 | } 1017 | .fa-info:before { 1018 | content: "\f129"; 1019 | } 1020 | .fa-exclamation:before { 1021 | content: "\f12a"; 1022 | } 1023 | .fa-superscript:before { 1024 | content: "\f12b"; 1025 | } 1026 | .fa-subscript:before { 1027 | content: "\f12c"; 1028 | } 1029 | .fa-eraser:before { 1030 | content: "\f12d"; 1031 | } 1032 | .fa-puzzle-piece:before { 1033 | content: "\f12e"; 1034 | } 1035 | .fa-microphone:before { 1036 | content: "\f130"; 1037 | } 1038 | .fa-microphone-slash:before { 1039 | content: "\f131"; 1040 | } 1041 | .fa-shield:before { 1042 | content: "\f132"; 1043 | } 1044 | .fa-calendar-o:before { 1045 | content: "\f133"; 1046 | } 1047 | .fa-fire-extinguisher:before { 1048 | content: "\f134"; 1049 | } 1050 | .fa-rocket:before { 1051 | content: "\f135"; 1052 | } 1053 | .fa-maxcdn:before { 1054 | content: "\f136"; 1055 | } 1056 | .fa-chevron-circle-left:before { 1057 | content: "\f137"; 1058 | } 1059 | .fa-chevron-circle-right:before { 1060 | content: "\f138"; 1061 | } 1062 | .fa-chevron-circle-up:before { 1063 | content: "\f139"; 1064 | } 1065 | .fa-chevron-circle-down:before { 1066 | content: "\f13a"; 1067 | } 1068 | .fa-html5:before { 1069 | content: "\f13b"; 1070 | } 1071 | .fa-css3:before { 1072 | content: "\f13c"; 1073 | } 1074 | .fa-anchor:before { 1075 | content: "\f13d"; 1076 | } 1077 | .fa-unlock-alt:before { 1078 | content: "\f13e"; 1079 | } 1080 | .fa-bullseye:before { 1081 | content: "\f140"; 1082 | } 1083 | .fa-ellipsis-h:before { 1084 | content: "\f141"; 1085 | } 1086 | .fa-ellipsis-v:before { 1087 | content: "\f142"; 1088 | } 1089 | .fa-rss-square:before { 1090 | content: "\f143"; 1091 | } 1092 | .fa-play-circle:before { 1093 | content: "\f144"; 1094 | } 1095 | .fa-ticket:before { 1096 | content: "\f145"; 1097 | } 1098 | .fa-minus-square:before { 1099 | content: "\f146"; 1100 | } 1101 | .fa-minus-square-o:before { 1102 | content: "\f147"; 1103 | } 1104 | .fa-level-up:before { 1105 | content: "\f148"; 1106 | } 1107 | .fa-level-down:before { 1108 | content: "\f149"; 1109 | } 1110 | .fa-check-square:before { 1111 | content: "\f14a"; 1112 | } 1113 | .fa-pencil-square:before { 1114 | content: "\f14b"; 1115 | } 1116 | .fa-external-link-square:before { 1117 | content: "\f14c"; 1118 | } 1119 | .fa-share-square:before { 1120 | content: "\f14d"; 1121 | } 1122 | .fa-compass:before { 1123 | content: "\f14e"; 1124 | } 1125 | .fa-toggle-down:before, 1126 | .fa-caret-square-o-down:before { 1127 | content: "\f150"; 1128 | } 1129 | .fa-toggle-up:before, 1130 | .fa-caret-square-o-up:before { 1131 | content: "\f151"; 1132 | } 1133 | .fa-toggle-right:before, 1134 | .fa-caret-square-o-right:before { 1135 | content: "\f152"; 1136 | } 1137 | .fa-euro:before, 1138 | .fa-eur:before { 1139 | content: "\f153"; 1140 | } 1141 | .fa-gbp:before { 1142 | content: "\f154"; 1143 | } 1144 | .fa-dollar:before, 1145 | .fa-usd:before { 1146 | content: "\f155"; 1147 | } 1148 | .fa-rupee:before, 1149 | .fa-inr:before { 1150 | content: "\f156"; 1151 | } 1152 | .fa-cny:before, 1153 | .fa-rmb:before, 1154 | .fa-yen:before, 1155 | .fa-jpy:before { 1156 | content: "\f157"; 1157 | } 1158 | .fa-ruble:before, 1159 | .fa-rouble:before, 1160 | .fa-rub:before { 1161 | content: "\f158"; 1162 | } 1163 | .fa-won:before, 1164 | .fa-krw:before { 1165 | content: "\f159"; 1166 | } 1167 | .fa-bitcoin:before, 1168 | .fa-btc:before { 1169 | content: "\f15a"; 1170 | } 1171 | .fa-file:before { 1172 | content: "\f15b"; 1173 | } 1174 | .fa-file-text:before { 1175 | content: "\f15c"; 1176 | } 1177 | .fa-sort-alpha-asc:before { 1178 | content: "\f15d"; 1179 | } 1180 | .fa-sort-alpha-desc:before { 1181 | content: "\f15e"; 1182 | } 1183 | .fa-sort-amount-asc:before { 1184 | content: "\f160"; 1185 | } 1186 | .fa-sort-amount-desc:before { 1187 | content: "\f161"; 1188 | } 1189 | .fa-sort-numeric-asc:before { 1190 | content: "\f162"; 1191 | } 1192 | .fa-sort-numeric-desc:before { 1193 | content: "\f163"; 1194 | } 1195 | .fa-thumbs-up:before { 1196 | content: "\f164"; 1197 | } 1198 | .fa-thumbs-down:before { 1199 | content: "\f165"; 1200 | } 1201 | .fa-youtube-square:before { 1202 | content: "\f166"; 1203 | } 1204 | .fa-youtube:before { 1205 | content: "\f167"; 1206 | } 1207 | .fa-xing:before { 1208 | content: "\f168"; 1209 | } 1210 | .fa-xing-square:before { 1211 | content: "\f169"; 1212 | } 1213 | .fa-youtube-play:before { 1214 | content: "\f16a"; 1215 | } 1216 | .fa-dropbox:before { 1217 | content: "\f16b"; 1218 | } 1219 | .fa-stack-overflow:before { 1220 | content: "\f16c"; 1221 | } 1222 | .fa-instagram:before { 1223 | content: "\f16d"; 1224 | } 1225 | .fa-flickr:before { 1226 | content: "\f16e"; 1227 | } 1228 | .fa-adn:before { 1229 | content: "\f170"; 1230 | } 1231 | .fa-bitbucket:before { 1232 | content: "\f171"; 1233 | } 1234 | .fa-bitbucket-square:before { 1235 | content: "\f172"; 1236 | } 1237 | .fa-tumblr:before { 1238 | content: "\f173"; 1239 | } 1240 | .fa-tumblr-square:before { 1241 | content: "\f174"; 1242 | } 1243 | .fa-long-arrow-down:before { 1244 | content: "\f175"; 1245 | } 1246 | .fa-long-arrow-up:before { 1247 | content: "\f176"; 1248 | } 1249 | .fa-long-arrow-left:before { 1250 | content: "\f177"; 1251 | } 1252 | .fa-long-arrow-right:before { 1253 | content: "\f178"; 1254 | } 1255 | .fa-apple:before { 1256 | content: "\f179"; 1257 | } 1258 | .fa-windows:before { 1259 | content: "\f17a"; 1260 | } 1261 | .fa-android:before { 1262 | content: "\f17b"; 1263 | } 1264 | .fa-linux:before { 1265 | content: "\f17c"; 1266 | } 1267 | .fa-dribbble:before { 1268 | content: "\f17d"; 1269 | } 1270 | .fa-skype:before { 1271 | content: "\f17e"; 1272 | } 1273 | .fa-foursquare:before { 1274 | content: "\f180"; 1275 | } 1276 | .fa-trello:before { 1277 | content: "\f181"; 1278 | } 1279 | .fa-female:before { 1280 | content: "\f182"; 1281 | } 1282 | .fa-male:before { 1283 | content: "\f183"; 1284 | } 1285 | .fa-gittip:before { 1286 | content: "\f184"; 1287 | } 1288 | .fa-sun-o:before { 1289 | content: "\f185"; 1290 | } 1291 | .fa-moon-o:before { 1292 | content: "\f186"; 1293 | } 1294 | .fa-archive:before { 1295 | content: "\f187"; 1296 | } 1297 | .fa-bug:before { 1298 | content: "\f188"; 1299 | } 1300 | .fa-vk:before { 1301 | content: "\f189"; 1302 | } 1303 | .fa-weibo:before { 1304 | content: "\f18a"; 1305 | } 1306 | .fa-renren:before { 1307 | content: "\f18b"; 1308 | } 1309 | .fa-pagelines:before { 1310 | content: "\f18c"; 1311 | } 1312 | .fa-stack-exchange:before { 1313 | content: "\f18d"; 1314 | } 1315 | .fa-arrow-circle-o-right:before { 1316 | content: "\f18e"; 1317 | } 1318 | .fa-arrow-circle-o-left:before { 1319 | content: "\f190"; 1320 | } 1321 | .fa-toggle-left:before, 1322 | .fa-caret-square-o-left:before { 1323 | content: "\f191"; 1324 | } 1325 | .fa-dot-circle-o:before { 1326 | content: "\f192"; 1327 | } 1328 | .fa-wheelchair:before { 1329 | content: "\f193"; 1330 | } 1331 | .fa-vimeo-square:before { 1332 | content: "\f194"; 1333 | } 1334 | .fa-turkish-lira:before, 1335 | .fa-try:before { 1336 | content: "\f195"; 1337 | } 1338 | .fa-plus-square-o:before { 1339 | content: "\f196"; 1340 | } 1341 | html._TEST_END_FILE2 {font-size: 1em;} -------------------------------------------------------------------------------- /tests/resources/styles-3.css: -------------------------------------------------------------------------------- 1 | html._TEST_INI_FILE3 { 2 | font-size: 1em; 3 | } 4 | 5 | .span { 6 | left: calc(50% - var(--gap, 10px) - var(--line-width, 50px)); 7 | } 8 | 9 | html._TEST_END_FILE3 { 10 | font-size: 1em; 11 | } 12 | --------------------------------------------------------------------------------