├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src └── Roumen │ └── Asset │ ├── Asset.php │ └── AssetServiceProvider.php └── tests ├── AssetTest.php └── files ├── cache.json └── cdn ├── 1.1.1 └── test.min.js ├── 2.2.2 └── test.min.js ├── 3.3.3 └── test.min.js └── test ├── test-1.1.1.min.js ├── test-2.1.3.min.js └── test-3.3.3.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject 2 | /vendor 3 | composer.phar 4 | composer.lock 5 | .directory 6 | .DS_Store 7 | Thumbs.db 8 | *.sh 9 | .project -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5.9 5 | - 5.6 6 | - 7.0 7 | - hhvm 8 | 9 | branches: 10 | only: 11 | - master 12 | 13 | before_script: 14 | - curl -s http://getcomposer.org/installer | php 15 | - php composer.phar install 16 | 17 | script: phpunit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) <2013-2015> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | Developer’s Certificate of Origin 1.1 23 | 24 | By making a contribution to this project, I certify that: 25 | 26 | (a) The contribution was created in whole or in part by me and I 27 | have the right to submit it under the open source license 28 | indicated in the file; or 29 | 30 | (b) The contribution is based upon previous work that, to the best 31 | of my knowledge, is covered under an appropriate open source 32 | license and I have the right under that license to submit that 33 | work with modifications, whether created in whole or in part 34 | by me, under the same open source license (unless I am 35 | permitted to submit under a different license), as indicated 36 | in the file; or 37 | 38 | (c) The contribution was provided directly to me by some other 39 | person who certified (a), (b) or (c) and I have not modified 40 | it. 41 | 42 | (d) I understand and agree that this project and the contribution 43 | are public and that a record of the contribution (including all 44 | personal information I submit with it, including my sign-off) is 45 | maintained indefinitely and may be redistributed consistent with 46 | this project or the open source license(s) involved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [laravel-assets](http://roumen.it/projects/laravel-assets) package 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/roumen/asset/version.png)](https://packagist.org/packages/roumen/asset) [![Total Downloads](https://poser.pugx.org/roumen/asset/d/total.png)](https://packagist.org/packages/roumen/asset) [![Build Status](https://travis-ci.org/RoumenDamianoff/laravel-assets.png?branch=master)](https://travis-ci.org/RoumenDamianoff/laravel-assets) [![License](https://poser.pugx.org/roumen/asset/license.png)](https://packagist.org/packages/roumen/asset) 4 | 5 | A simple assets manager for Laravel 5. 6 | 7 | ## Notes 8 | 9 | Latest supported version for Laravel 4 is 2.4.* (e.g v2.4.3) 10 | 11 | Branch dev-master is for development and is unstable 12 | 13 | ## Installation 14 | 15 | Run the following command and provide the latest stable version (e.g v2.5.4) : 16 | 17 | ```bash 18 | composer require roumen/asset 19 | ``` 20 | 21 | or add the following to your `composer.json` file : 22 | 23 | ```json 24 | "roumen/asset": "2.5.*" 25 | ``` 26 | 27 | Then register this service provider with Laravel : 28 | 29 | ```php 30 | 'Roumen\Asset\AssetServiceProvider', 31 | ``` 32 | 33 | and add class alias for easy usage 34 | ```php 35 | 'Asset' => 'Roumen\Asset\Asset', 36 | ``` 37 | 38 | Don't forget to use ``composer update`` and ``composer dump-autoload`` when is needed! 39 | 40 | ## Examples 41 | 42 | [Example usage and layout structure](https://github.com/RoumenDamianoff/laravel-assets/wiki) 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roumen/asset", 3 | "description": "Assets manager for Laravel.", 4 | "homepage": "https://roumen.it/projects/laravel-assets", 5 | "keywords": ["laravel", "asset", "assets", "manager"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Roumen Damianoff", 10 | "email": "roumen@dawebs.com", 11 | "role": "Developer", 12 | "homepage": "https://roumen.it" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.5.9", 17 | "illuminate/support": "~5" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^4.8" 21 | }, 22 | "autoload": { 23 | "psr-0": { 24 | "Roumen\\Asset": "src/" 25 | } 26 | }, 27 | "minimum-stability": "stable" 28 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Roumen/Asset/Asset.php: -------------------------------------------------------------------------------- 1 | 7 | * @version 2.5.4 8 | * @link http://roumen.it/projects/laravel-assets 9 | * @license http://opensource.org/licenses/mit-license.php MIT License 10 | */ 11 | 12 | use Illuminate\Support\Facades\Cache; 13 | 14 | class Asset 15 | { 16 | // assets type 17 | const ADD_TO_NONE = 0; 18 | const ADD_TO_CSS = 1; 19 | const ADD_TO_LESS = 2; 20 | const ADD_TO_JS = 3; 21 | 22 | // if added asset don't have an extension or it is unknown 23 | const ON_UNKNOWN_EXTENSION_NONE = 0; 24 | const ON_UNKNOWN_EXTENSION_CSS = 1; 25 | const ON_UNKNOWN_EXTENSION_LESS = 2; 26 | const ON_UNKNOWN_EXTENSION_JS = 3; 27 | 28 | private static $ON_UNKNOWN_EXTENSION_TO_ADD_TO = [ 29 | Asset::ON_UNKNOWN_EXTENSION_NONE => Asset::ADD_TO_CSS, 30 | Asset::ON_UNKNOWN_EXTENSION_LESS => Asset::ADD_TO_LESS, 31 | Asset::ON_UNKNOWN_EXTENSION_JS => Asset::ADD_TO_JS 32 | ]; 33 | 34 | public static $css = []; 35 | public static $less = []; 36 | public static $styles = []; 37 | public static $js = []; 38 | public static $jsParams = []; 39 | public static $scripts = []; 40 | public static $domain = '/'; 41 | public static $prefix = ''; 42 | public static $hash = []; 43 | public static $environment = null; 44 | public static $secure = false; 45 | public static $cacheEnabled = true; 46 | public static $cacheDuration = 360; // 6 hours 47 | public static $cacheKey = 'laravel-assets'; 48 | protected static $cacheBusterGeneratorFunction = null; 49 | private static $useShortHandReady = false; 50 | private static $onUnknownExtensionDefault = Asset::ON_UNKNOWN_EXTENSION_NONE; 51 | 52 | /** 53 | * Check environment 54 | * 55 | * @return void 56 | */ 57 | public static function checkEnv() 58 | { 59 | if (static::$environment == null) 60 | { 61 | static::$environment = \App::environment(); 62 | } 63 | 64 | // use only local files in local environment 65 | if (static::$environment == 'local' && (static::$domain != '/')) 66 | { 67 | static::$domain = '/'; 68 | } 69 | } 70 | 71 | /** 72 | * Set domain name 73 | * 74 | * @param string $url 75 | * 76 | * @return void 77 | */ 78 | public static function setDomain($url) 79 | { 80 | if (is_string($url)) 81 | { 82 | static::$domain = $url; 83 | } 84 | } 85 | 86 | /** 87 | * Set prefix 88 | * 89 | * @param string $prefix 90 | * 91 | * @return void 92 | */ 93 | public static function setPrefix($prefix) 94 | { 95 | static::$prefix = $prefix; 96 | } 97 | 98 | /** 99 | * Set cache buster JSON file 100 | * 101 | * @param string $cachebuster 102 | * 103 | * @return void 104 | */ 105 | public static function setCachebuster($cachebuster) 106 | { 107 | if (file_exists($cachebuster)) 108 | { 109 | static::$hash = json_decode(file_get_contents($cachebuster), true); 110 | } 111 | } 112 | 113 | /** 114 | * Set cache buster function 115 | * 116 | * @param Callable $fn 117 | * 118 | * Callable must accepts ONE argument {String} (filename) 119 | * and return a {String} (hash without filename and "?") 120 | * 121 | * @return void 122 | */ 123 | public static function setCacheBusterGeneratorFunction($fn) 124 | { 125 | static::$cacheBusterGeneratorFunction = $fn; 126 | } 127 | 128 | /** 129 | * Generate cache buster filename 130 | * 131 | * @param string $a 132 | * @param string $name 133 | * 134 | * @return void 135 | */ 136 | private static function generateCacheBusterFilename($a) 137 | { 138 | $hash = ''; 139 | if(!is_callable(static::$cacheBusterGeneratorFunction)) 140 | { 141 | if(is_array(static::$hash) && array_key_exists($a, static::$hash)) { 142 | $hash .= static::$hash[$a]; 143 | } 144 | } else 145 | { 146 | $hash = call_user_func_array(static::$cacheBusterGeneratorFunction, array($a)); 147 | } 148 | 149 | if(is_string($hash) && $hash !== '') { 150 | $a .= '?' . $hash; 151 | } 152 | return $a; 153 | } 154 | 155 | /** 156 | * Set indicator if use Short Hand [$()] or Normal $( document ).ready() 157 | * 158 | * @param boolean $useShortHandReady 159 | * 160 | * @return void 161 | */ 162 | public static function setUseShortHandReady($useShortHandReady) 163 | { 164 | static::$useShortHandReady = $useShortHandReady; 165 | } 166 | 167 | /** 168 | * Indicate what to do by default if an unknown extension is found. 169 | * 170 | * @param int (static::ON_UNKNOWN_EXTENSION_NONE, 171 | * static::ON_UNKNOWN_EXTENSION_JS) $onUnknownExtensionDefault 172 | * 173 | * @return void 174 | */ 175 | public static function setOnUnknownExtensionDefault($onUnknownExtensionDefault) 176 | { 177 | if ((!is_int($onUnknownExtensionDefault)) 178 | || ($onUnknownExtensionDefault < static::ON_UNKNOWN_EXTENSION_NONE) 179 | || ($onUnknownExtensionDefault > static::ON_UNKNOWN_EXTENSION_JS)) 180 | { 181 | $onUnknownExtensionDefault = static::ON_UNKNOWN_EXTENSION_NONE; 182 | } 183 | 184 | static::$onUnknownExtensionDefault = $onUnknownExtensionDefault; 185 | } 186 | 187 | /** 188 | * Add new asset 189 | * 190 | * @param string $a 191 | * @param string/array $params 192 | * @param int (static::ON_UNKNOWN_EXTENSION_NONE, 193 | * static::ON_UNKNOWN_EXTENSION_JS) $onUnknownExtension 194 | * 195 | * @return void 196 | */ 197 | public static function add($a, $params = 'footer', $onUnknownExtension = false) 198 | { 199 | if (is_array($a)) 200 | { 201 | foreach ($a as $item) 202 | { 203 | static::processAdd($item, $params, $onUnknownExtension); 204 | } 205 | } else 206 | { 207 | static::processAdd($a, $params, $onUnknownExtension); 208 | } 209 | } 210 | 211 | /** 212 | * Identify where to add an asset: 213 | * 214 | * @param string $a 215 | * @param int (static::ON_UNKNOWN_EXTENSION_NONE, 216 | * static::ON_UNKNOWN_EXTENSION_JS)/boolean $onUnknownExtension 217 | * 218 | * @return int (static::ADD_TO_NONE, static::ADD_TO_JS) 219 | */ 220 | private static function getAddTo($a, $onUnknownExtension = false) 221 | { 222 | if (false === $onUnknownExtension) 223 | { 224 | $onUnknownExtension = static::$onUnknownExtensionDefault; 225 | } 226 | 227 | if (preg_match("/(\.css|\/css\?)/i", $a)) 228 | { 229 | // css 230 | return static::ADD_TO_CSS; 231 | 232 | } elseif (preg_match("/\.less/i", $a)) 233 | { 234 | // less 235 | return static::ADD_TO_LESS; 236 | 237 | } elseif (preg_match("/\.js|\/js/i", $a)) 238 | { 239 | // js 240 | return static::ADD_TO_JS; 241 | 242 | } elseif ( (static::ON_UNKNOWN_EXTENSION_NONE != $onUnknownExtension) && isset(static::$ON_UNKNOWN_EXTENSION_TO_ADD_TO[$onUnknownExtension]) ) 243 | { 244 | return static::$ON_UNKNOWN_EXTENSION_TO_ADD_TO[$onUnknownExtension]; 245 | } 246 | 247 | return static::ADD_TO_NONE; 248 | } 249 | 250 | /** 251 | * Process add method 252 | * 253 | * @param string $a 254 | * @param string/array $params 255 | * 256 | * @return void 257 | */ 258 | protected static function processAdd($a, $params, $onUnknownExtension = false) 259 | { 260 | // check for '*' character 261 | static::checkVersion($a); 262 | 263 | switch (static::getAddTo($a, $onUnknownExtension)) 264 | { 265 | case static::ADD_TO_CSS: 266 | 267 | static::$css[$a] = $a; 268 | break; 269 | 270 | case static::ADD_TO_LESS: 271 | 272 | static::$less[$a] = $a; 273 | break; 274 | 275 | case static::ADD_TO_JS: 276 | 277 | if (is_array($params) && !empty($params['name'])) 278 | { 279 | $name = $params['name']; 280 | static::$jsParams[$name][$a] = $params; 281 | } 282 | else 283 | { 284 | $name = $params; 285 | } 286 | 287 | static::$js[$name][$a] = $a; 288 | break; 289 | } 290 | } 291 | 292 | /** 293 | * Add new asset as first in its array 294 | * 295 | * @param string $a 296 | * @param string/array $params 297 | * 298 | * @return void 299 | */ 300 | public static function addFirst($a, $params = 'footer', $onUnknownExtension = false) 301 | { 302 | // check for '*' character 303 | static::checkVersion($a); 304 | 305 | switch (static::getAddTo($a, $onUnknownExtension)) 306 | { 307 | case static::ADD_TO_CSS: 308 | 309 | static::$css = [$a => $a] + static::$css; 310 | break; 311 | 312 | case static::ADD_TO_LESS: 313 | 314 | static::$less = [$a => $a] + static::$less; 315 | break; 316 | 317 | case static::ADD_TO_JS: 318 | 319 | if (is_array($params) && !empty($params['name'])) 320 | { 321 | $name = $params['name']; 322 | static::$jsParams[$name][$a] = $params; 323 | } 324 | else 325 | { 326 | $name = $params; 327 | } 328 | 329 | if (!empty(static::$js[$name])) 330 | { 331 | static::$js[$name] = [$a => $a] + static::$js[$name]; 332 | } 333 | else 334 | { 335 | static::$js[$name][$a] = $a; 336 | } 337 | break; 338 | } 339 | } 340 | 341 | /** 342 | * Add new asset before another asset in its array 343 | * 344 | * @param string $a 345 | * @param string $b 346 | * @param string/array $params 347 | * 348 | * @return void 349 | */ 350 | public static function addBefore($a, $b, $params = 'footer', $onUnknownExtension = false) 351 | { 352 | // check for '*' character 353 | static::checkVersion($a); 354 | 355 | switch (static::getAddTo($a, $onUnknownExtension)) 356 | { 357 | case static::ADD_TO_CSS: 358 | 359 | $bpos = array_search($b, array_keys(static::$css)); 360 | 361 | if ($bpos === 0) 362 | { 363 | static::addFirst($a, $name); 364 | } 365 | elseif ($bpos >= 1) 366 | { 367 | $barr = array_slice(static::$css, $bpos); 368 | $aarr = array_slice(static::$css, 0, $bpos); 369 | $aarr[$a] = $a; 370 | static::$css = array_merge($aarr, $barr); 371 | } 372 | else 373 | { 374 | static::$css[$a] = $a; 375 | } 376 | 377 | break; 378 | 379 | case static::ADD_TO_LESS: 380 | 381 | $bpos = array_search($b, array_keys(static::$less)); 382 | 383 | if ($bpos === 0) 384 | { 385 | static::addFirst($a, $name); 386 | } 387 | elseif ($bpos >= 1) 388 | { 389 | $barr = array_slice(static::$less, $bpos); 390 | $aarr = array_slice(static::$less, 0, $bpos); 391 | $aarr[$a] = $a; 392 | static::$less = array_merge($aarr, $barr); 393 | } 394 | else 395 | { 396 | static::$less[$a] = $a; 397 | } 398 | 399 | break; 400 | 401 | case static::ADD_TO_JS: 402 | 403 | if (is_array($params) && !empty($params['name'])) 404 | { 405 | $name = $params['name']; 406 | static::$jsParams[$name][$a] = $params; 407 | } 408 | else 409 | { 410 | $name = $params; 411 | } 412 | 413 | if (!empty(static::$js[$name])) 414 | { 415 | $bpos = array_search($b, array_keys(static::$js[$name])); 416 | 417 | if ($bpos === 0) 418 | { 419 | static::addFirst($a, $name); 420 | } 421 | elseif ($bpos >= 1) 422 | { 423 | $barr = array_slice(static::$js[$name], $bpos); 424 | $aarr = array_slice(static::$js[$name], 0, $bpos); 425 | $aarr[$a] = $a; 426 | static::$js[$name] = array_merge($aarr, $barr); 427 | } 428 | else 429 | { 430 | static::$js[$name][$a] = $a; 431 | } 432 | } 433 | 434 | break; 435 | } 436 | } 437 | 438 | /** 439 | * Add new asset after another asset in its array 440 | * 441 | * @param string $a 442 | * @param string $b 443 | * @param string/array $params 444 | * 445 | * @return void 446 | */ 447 | public static function addAfter($a, $b, $params = 'footer', $onUnknownExtension = false) 448 | { 449 | // check for '*' character 450 | static::checkVersion($a); 451 | 452 | switch (static::getAddTo($a, $onUnknownExtension)) 453 | { 454 | case static::ADD_TO_CSS: 455 | 456 | $bpos = array_search($b, array_keys(static::$css)); 457 | 458 | if ($bpos === 0 || $bpos > 0) 459 | { 460 | $barr = array_slice(static::$css, $bpos+1); 461 | $aarr = array_slice(static::$css, 0, $bpos+1); 462 | $aarr[$a] = $a; 463 | static::$css = array_merge($aarr, $barr); 464 | } 465 | else 466 | { 467 | static::$css[$a] = $a; 468 | } 469 | 470 | break; 471 | 472 | case static::ADD_TO_LESS: 473 | 474 | $bpos = array_search($b, array_keys(static::$less)); 475 | 476 | if ($bpos === 0 || $bpos > 0) 477 | { 478 | $barr = array_slice(static::$less, $bpos+1); 479 | $aarr = array_slice(static::$less, 0, $bpos+1); 480 | $aarr[$a] = $a; 481 | static::$less = array_merge($aarr, $barr); 482 | } 483 | else 484 | { 485 | static::$less[$a] = $a; 486 | } 487 | 488 | break; 489 | 490 | case static::ADD_TO_JS: 491 | 492 | if (is_array($params) && !empty($params['name'])) 493 | { 494 | $name = $params['name']; 495 | static::$jsParams[$name][$a] = $params; 496 | } 497 | else 498 | { 499 | $name = $params; 500 | } 501 | 502 | if (!empty(static::$js[$name])) 503 | { 504 | $bpos = array_search($b, array_keys(static::$js[$name])); 505 | 506 | if ($bpos === 0 || $bpos > 0) 507 | { 508 | $barr = array_slice(static::$js[$name], $bpos+1); 509 | $aarr = array_slice(static::$js[$name], 0, $bpos+1); 510 | $aarr[$a] = $a; 511 | static::$js[$name] = array_merge($aarr, $barr); 512 | } 513 | else 514 | { 515 | static::$js[$name][$a] = $a; 516 | } 517 | } 518 | 519 | break; 520 | } 521 | } 522 | 523 | /** 524 | * Add new script 525 | * 526 | * @param string $s 527 | * @param string $name 528 | * 529 | * @return void 530 | */ 531 | public static function addScript($s, $name = 'footer') 532 | { 533 | static::$scripts[$name][] = $s; 534 | } 535 | 536 | /** 537 | * Add new style 538 | * 539 | * @param string $style 540 | * @param string $s 541 | * 542 | * @return void 543 | */ 544 | public static function addStyle($style, $s = 'header') 545 | { 546 | static::$styles[$s][] = $style; 547 | } 548 | 549 | /** 550 | * Returns the full-path for an asset. 551 | * 552 | * @param string $source 553 | * @return string 554 | */ 555 | protected static function url($file) 556 | { 557 | if (preg_match('/(https?:)?\/\//i', $file)) 558 | { 559 | return $file; 560 | } 561 | 562 | $file = static::generateCacheBusterFilename($file); 563 | 564 | if (static::$domain == '/' && static::$environment != 'testing') 565 | { 566 | return asset($file, static::$secure); 567 | } 568 | 569 | return rtrim(static::$domain, '/') .'/' . ltrim($file, '/'); 570 | } 571 | 572 | /** 573 | * Loads all items from $css array not wrapped in tags 574 | * 575 | * @param string $separator 576 | * 577 | * @return void 578 | */ 579 | public static function cssRaw($separator = "") 580 | { 581 | static::checkEnv(); 582 | 583 | if (!empty(static::$css)) 584 | { 585 | foreach(static::$css as $file) 586 | { 587 | echo static::$prefix, static::url($file), $separator; 588 | } 589 | } 590 | } 591 | 592 | /** 593 | * Loads all items from $css array 594 | * 595 | * @return void 596 | */ 597 | public static function css() 598 | { 599 | static::checkEnv(); 600 | 601 | if (!empty(static::$css)) 602 | { 603 | foreach(static::$css as $file) 604 | { 605 | echo static::$prefix, '\n"; 606 | } 607 | } 608 | } 609 | 610 | /** 611 | * Loads all items from $less array not wrapped in tags 612 | * 613 | * @param string $separator 614 | * 615 | * @return void 616 | */ 617 | public static function lessRaw($separator = "") 618 | { 619 | static::checkEnv(); 620 | 621 | if (!empty(static::$less)) 622 | { 623 | foreach(static::$less as $file) 624 | { 625 | echo static::$prefix, static::url($file), $separator; 626 | } 627 | } 628 | } 629 | 630 | /** 631 | * Loads all items from $less array 632 | * 633 | * @return void 634 | */ 635 | public static function less() 636 | { 637 | static::checkEnv(); 638 | 639 | if (!empty(static::$less)) 640 | { 641 | foreach(static::$less as $file) 642 | { 643 | echo static::$prefix, '\n"; 644 | } 645 | } 646 | } 647 | 648 | /** 649 | * Loads all items from $styles array 650 | * 651 | * @param string $name 652 | * 653 | * @return void 654 | */ 655 | public static function styles($name = 'header') 656 | { 657 | if (($name !== '') && (!empty(static::$styles[$name]))) 658 | { 659 | echo "\n", static::$prefix, "\n"; 667 | 668 | } elseif (!empty(static::$styles)) 669 | { 670 | echo static::$prefix, "\n"; 678 | } 679 | } 680 | 681 | /** 682 | * Loads items from $js array not wrapped in '."\n"; 734 | 735 | echo $e; 736 | } 737 | } 738 | } 739 | 740 | /** 741 | * Loads items from $scripts array 742 | * 743 | * @param string $name 744 | * 745 | * @return void 746 | */ 747 | public static function scripts($name = 'footer') 748 | { 749 | if ($name == 'ready') 750 | { 751 | if (!empty(static::$scripts['ready'])) 752 | { 753 | echo static::$prefix, '\n"; 761 | } 762 | } 763 | else 764 | { 765 | if (!empty(static::$scripts[$name])) 766 | { 767 | foreach(static::$scripts[$name] as $script) 768 | { 769 | echo static::$prefix, "\n"; 770 | } 771 | } 772 | } 773 | } 774 | 775 | /** 776 | * Checks if the asset has wildcard '*' character 777 | * 778 | * @param string &$a 779 | * 780 | * @return void 781 | */ 782 | public static function checkVersion(&$a) 783 | { 784 | // check for '*' character 785 | if (preg_match("/\*/i", $a)) 786 | { 787 | $a_org = $a; 788 | 789 | // check for cached version 790 | if (static::$cacheEnabled && Cache::has(static::$cacheKey.$a)) 791 | { 792 | // use cached version 793 | $a = Cache::get(static::$cacheKey.$a); 794 | } 795 | else 796 | { 797 | // get latest version 798 | preg_match("/(.*?)(\*)(.*)/", $a, $m1); 799 | preg_match("/(.*)\/(.*)/", $m1[1], $m2); 800 | 801 | // check if is url or path 802 | (preg_match('/(https?:)?\/\//i', $a)) ? $f = file_get_contents($m2[1]) : $f = json_encode(scandir($m2[1], 1)); 803 | 804 | if ($m2[2] != '') 805 | { 806 | preg_match_all("/(".str_replace('/', '\/', $m2[2]).")(\d+(?:\.\d+){1,9})/i", $f, $m3, PREG_PATTERN_ORDER); 807 | usort($m3[2],'version_compare'); 808 | $a = $m2[0].end($m3[2]).$m1[3]; 809 | } 810 | else 811 | { 812 | preg_match_all("/(\d+(?:\.\d+){1,9})(".str_replace('/', '\/', $m1[3]).")/i", $f, $m3, PREG_PATTERN_ORDER); 813 | 814 | if (!empty($m3[1])) 815 | { 816 | usort($m3[1],'version_compare'); 817 | $a = $m2[0].end($m3[1]).$m1[3]; 818 | } 819 | else 820 | { 821 | preg_match_all("/(\d+(?:\.\d+){1,9})/i", $f, $m3, PREG_PATTERN_ORDER); 822 | usort($m3[1],'version_compare'); 823 | $a = $m2[0].end($m3[1]).$m1[3]; 824 | } 825 | } 826 | } 827 | 828 | // cache latest version 829 | if (static::$cacheEnabled) Cache::put(static::$cacheKey.$a_org, $a, static::$cacheDuration); 830 | } 831 | } 832 | 833 | } 834 | -------------------------------------------------------------------------------- /src/Roumen/Asset/AssetServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind('assets', function() 32 | { 33 | return new Asset(); 34 | }); 35 | 36 | } 37 | 38 | /** 39 | * Get the services provided by the provider. 40 | * 41 | * @return array 42 | */ 43 | public function provides() 44 | { 45 | return ['asset']; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /tests/AssetTest.php: -------------------------------------------------------------------------------- 1 | 'footer2', 'type'=>'text/jsx', 'async' => 'true', 'defer'=>'true']); 24 | 25 | $this->assertEquals('style.css', Asset::$css['style.css']); 26 | $this->assertEquals('style.less', Asset::$less['style.less']); 27 | $this->assertEquals('script.js', Asset::$js['footer']['script.js']); 28 | $this->assertEquals('script.js', Asset::$js['foobar']['script.js']); 29 | 30 | $this->assertEquals('scriptWithParams.js', Asset::$js['footer2']['scriptWithParams.js']); 31 | } 32 | 33 | public function testAddWildcard() 34 | { 35 | /* local assets */ 36 | 37 | Asset::add('tests/files/cdn/test/test-*.min.js','foobar'); 38 | Asset::add('tests/files/cdn/*/test.min.js','foobar'); 39 | 40 | $this->assertEquals('tests/files/cdn/test/test-3.3.3.min.js', Asset::$js['foobar']['tests/files/cdn/test/test-3.3.3.min.js']); 41 | $this->assertEquals('tests/files/cdn/3.3.3/test.min.js', Asset::$js['foobar']['tests/files/cdn/3.3.3/test.min.js']); 42 | 43 | // NOTE: KEEP TESTS BELLOW COMMENTED OUT! Versions change often and they will fail. 44 | 45 | /* cdn.roumen.it */ 46 | 47 | //sset::add('https://cdn.roumen.it/repo/jquery/jquery-*.min.js','foobar'); 48 | //Asset::add('https://cdn.roumen.it/repo/jquery-ui/*/jquery-ui.min.js','foobar'); 49 | //Asset::add('https://cdn.roumen.it/repo/bootstrap/*/css/bootstrap.min.css'); 50 | //Asset::add('https://cdn.roumen.it/repo/bootstrap/*/js/bootstrap.min.js','foobar'); 51 | //Asset::add('https://cdn.roumen.it/repo/ckeditor/*/full/ckeditor.js','foobar'); 52 | //Asset::add('https://cdn.roumen.it/repo/respond.js/*/respond.min.js','foobar'); 53 | //Asset::add('https://cdn.roumen.it/repo/html5shiv/*/html5shiv.js','foobar'); 54 | 55 | //$this->assertEquals('https://cdn.roumen.it/repo/jquery/jquery-2.1.4.min.js', Asset::$js['foobar']['https://cdn.roumen.it/repo/jquery/jquery-2.1.4.min.js']); 56 | //$this->assertEquals('https://cdn.roumen.it/repo/jquery-ui/1.11.4/jquery-ui.min.js', Asset::$js['foobar']['https://cdn.roumen.it/repo/jquery-ui/1.11.4/jquery-ui.min.js']); 57 | //$this->assertEquals('https://cdn.roumen.it/repo/bootstrap/3.3.1/css/bootstrap.min.css', Asset::$css['https://cdn.roumen.it/repo/bootstrap/3.3.1/css/bootstrap.min.css']); 58 | //$this->assertEquals('https://cdn.roumen.it/repo/bootstrap/3.3.1/js/bootstrap.min.js', Asset::$js['foobar']['https://cdn.roumen.it/repo/bootstrap/3.3.1/js/bootstrap.min.js']); 59 | //$this->assertEquals('https://cdn.roumen.it/repo/ckeditor/4.4.6/full/ckeditor.js', Asset::$js['foobar']['https://cdn.roumen.it/repo/ckeditor/4.4.6/full/ckeditor.js']); 60 | //$this->assertEquals('https://cdn.roumen.it/repo/respond.js/1.4.2/respond.min.js', Asset::$js['foobar']['https://cdn.roumen.it/repo/respond.js/1.4.2/respond.min.js']); 61 | //$this->assertEquals('https://cdn.roumen.it/repo/html5shiv/3.7.0/html5shiv.js', Asset::$js['foobar']['https://cdn.roumen.it/repo/html5shiv/3.7.0/html5shiv.js']); 62 | 63 | /* code.jquery.com */ 64 | 65 | //Asset::add('https://code.jquery.com/jquery-*.min.js','foobar'); 66 | //Asset::add('https://code.jquery.com/ui/*/jquery-ui.min.js','foobar'); 67 | //Asset::add('https://code.jquery.com/mobile/*/jquery.mobile-1.4.5.min.js','foobar'); 68 | //Asset::add('https://code.jquery.com/color/jquery.color-*.min.js','foobar'); 69 | 70 | //$this->assertEquals('https://code.jquery.com/jquery-2.1.4.min.js', Asset::$js['foobar']['https://code.jquery.com/jquery-2.1.4.min.js']); 71 | //$this->assertEquals('https://code.jquery.com/ui/1.11.4/jquery-ui.min.js', Asset::$js['foobar']['https://code.jquery.com/ui/1.11.4/jquery-ui.min.js']); 72 | //$this->assertEquals('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js', Asset::$js['foobar']['https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js']); 73 | //$this->assertEquals('https://code.jquery.com/color/jquery.color-2.1.2.min.js', Asset::$js['foobar']['https://code.jquery.com/color/jquery.color-2.1.2.min.js']); 74 | } 75 | 76 | public function testAddScript() 77 | { 78 | Asset::addScript('test'); 79 | 80 | $this->assertEquals('test', Asset::$scripts['footer'][0]); 81 | 82 | Asset::addScript('test','foobar'); 83 | 84 | $this->assertEquals('test', Asset::$scripts['foobar'][0]); 85 | } 86 | 87 | public function testAddStyle() 88 | { 89 | Asset::addStyle('test'); 90 | Asset::addStyle('test2'); 91 | $this->assertEquals('test2', Asset::$styles['header'][1]); 92 | 93 | Asset::addStyle('test123','foobar'); 94 | $this->assertEquals('test123', Asset::$styles['foobar'][0]); 95 | } 96 | 97 | public function testAddFirst() 98 | { 99 | Asset::$css = []; 100 | Asset::$less = []; 101 | Asset::$js = []; 102 | 103 | Asset::add('style.css'); 104 | Asset::addFirst('styleFirst.css'); 105 | 106 | // get keys as numbers 107 | $keys = array_keys(Asset::$css); 108 | 109 | $this->assertEquals('styleFirst.css', Asset::$css[$keys[0]]); 110 | $this->assertEquals('style.css', Asset::$css[$keys[1]]); 111 | 112 | Asset::add('style.less'); 113 | Asset::addFirst('styleFirst.less'); 114 | 115 | $keys = array_keys(Asset::$less); 116 | 117 | $this->assertEquals('styleFirst.less', Asset::$less[$keys[0]]); 118 | $this->assertEquals('style.less', Asset::$less[$keys[1]]); 119 | 120 | Asset::add('script.js'); 121 | Asset::addFirst('scriptFirst.js'); 122 | 123 | $keys = array_keys(Asset::$js['footer']); 124 | 125 | $this->assertEquals('scriptFirst.js', Asset::$js['footer'][$keys[0]]); 126 | $this->assertEquals('script.js', Asset::$js['footer'][$keys[1]]); 127 | 128 | Asset::add('script3.js','foobar'); 129 | Asset::addFirst('scriptFirst.js','foobar'); 130 | Asset::addFirst('scriptFirst2.js',['name'=>'foobar','type'=>'text/jsx','async'=>'true','defer'=>'false']); 131 | 132 | $keys = array_keys(Asset::$js['foobar']); 133 | 134 | $this->assertEquals('scriptFirst2.js', Asset::$js['foobar'][$keys[0]]); 135 | $this->assertEquals('scriptFirst.js', Asset::$js['foobar'][$keys[1]]); 136 | $this->assertEquals('script3.js', Asset::$js['foobar'][$keys[2]]); 137 | $this->assertEquals(['name'=>'foobar','type'=>'text/jsx','async'=>'true','defer'=>'false'], Asset::$jsParams['foobar']['scriptFirst2.js']); 138 | 139 | } 140 | 141 | 142 | public function testAddBefore() 143 | { 144 | Asset::$css = []; 145 | Asset::$less = []; 146 | Asset::$js = []; 147 | 148 | Asset::add(['1.css','2.css','3.css']); 149 | Asset::addBefore('before2.css','2.css'); 150 | 151 | $keys = array_keys(Asset::$css); 152 | 153 | $this->assertEquals('before2.css', Asset::$css[$keys[1]]); 154 | $this->assertEquals('2.css', Asset::$css[$keys[2]]); 155 | 156 | 157 | Asset::add(['1.less','2.less','3.less']); 158 | Asset::addBefore('before2.less','2.less'); 159 | 160 | $keys = array_keys(Asset::$less); 161 | 162 | $this->assertEquals('before2.less', Asset::$less[$keys[1]]); 163 | $this->assertEquals('2.less', Asset::$less[$keys[2]]); 164 | 165 | Asset::add(['1.js','2.js','3.js']); 166 | Asset::addBefore('before2.js','2.js'); 167 | 168 | $keys = array_keys(Asset::$js['footer']); 169 | 170 | $this->assertEquals('before2.js', Asset::$js['footer'][$keys[1]]); 171 | $this->assertEquals('2.js', Asset::$js['footer'][$keys[2]]); 172 | 173 | Asset::add(['1.js','2.js','3.js'],'foobar'); 174 | Asset::addBefore('before2.js','2.js', 'foobar'); 175 | Asset::addBefore('before3.js','3.js',['name'=>'foobar','type'=>'text/jsx','async'=>'true','defer'=>'false']); 176 | 177 | $keys = array_keys(Asset::$js['foobar']); 178 | 179 | $this->assertEquals('before2.js', Asset::$js['foobar'][$keys[1]]); 180 | $this->assertEquals('2.js', Asset::$js['foobar'][$keys[2]]); 181 | $this->assertEquals('before3.js', Asset::$js['foobar'][$keys[3]]); 182 | $this->assertEquals(['name'=>'foobar','type'=>'text/jsx','async'=>'true','defer'=>'false'], Asset::$jsParams['foobar']['before3.js']); 183 | } 184 | 185 | public function testAddAfter() 186 | { 187 | Asset::$css = []; 188 | Asset::$less = []; 189 | Asset::$js = []; 190 | 191 | Asset::add(['1.css','2.css','3.css']); 192 | Asset::addAfter('after2.css','2.css'); 193 | 194 | $keys = array_keys(Asset::$css); 195 | 196 | $this->assertEquals('after2.css', Asset::$css[$keys[2]]); 197 | $this->assertEquals('2.css', Asset::$css[$keys[1]]); 198 | 199 | Asset::add(['1.less','2.less','3.less']); 200 | Asset::addAfter('after2.less','2.less'); 201 | 202 | $keys = array_keys(Asset::$less); 203 | 204 | $this->assertEquals('after2.less', Asset::$less[$keys[2]]); 205 | $this->assertEquals('2.less', Asset::$less[$keys[1]]); 206 | 207 | Asset::add(['1.js','2.js','3.js']); 208 | Asset::addAfter('after2.js','2.js'); 209 | 210 | $keys = array_keys(Asset::$js['footer']); 211 | 212 | $this->assertEquals('after2.js', Asset::$js['footer'][$keys[2]]); 213 | $this->assertEquals('2.js', Asset::$js['footer'][$keys[1]]); 214 | 215 | Asset::add(['1.js','2.js','3.js'],'foobar'); 216 | Asset::addAfter('after2.js','2.js', 'foobar'); 217 | Asset::addAfter('after1.js','1.js',['name'=>'foobar','type'=>'text/jsx','async'=>'true','defer'=>'false']); 218 | 219 | $keys = array_keys(Asset::$js['foobar']); 220 | 221 | $this->assertEquals('after2.js', Asset::$js['foobar'][$keys[3]]); 222 | $this->assertEquals('2.js', Asset::$js['foobar'][$keys[2]]); 223 | $this->assertEquals('after1.js', Asset::$js['foobar'][$keys[1]]); 224 | 225 | $this->assertEquals(['name'=>'foobar','type'=>'text/jsx','async'=>'true','defer'=>'false'], Asset::$jsParams['foobar']['after1.js']); 226 | } 227 | 228 | public function testCssGoogleFonts() 229 | { 230 | Asset::$css = []; 231 | 232 | Asset::add([ 233 | 'http://fonts.googleapis.com/css?family=Londrina+Outline', 234 | 'http://fonts.googleapis.com/css?family=Nova+Square', 235 | 'http://fonts.googleapis.com/css?family=Special+Elite' 236 | ]); 237 | 238 | $keys = array_keys(Asset::$css); 239 | 240 | $this->assertEquals('http://fonts.googleapis.com/css?family=Londrina+Outline', Asset::$css[$keys[0]]); 241 | $this->assertEquals('http://fonts.googleapis.com/css?family=Nova+Square', Asset::$css[$keys[1]]); 242 | $this->assertEquals('http://fonts.googleapis.com/css?family=Special+Elite', Asset::$css[$keys[2]]); 243 | 244 | Asset::addFirst('http://fonts.googleapis.com/css?family=Share+Tech+Mono'); 245 | Asset::addAfter('http://fonts.googleapis.com/css?family=Playfair+Display+SC', 'http://fonts.googleapis.com/css?family=Share+Tech+Mono'); 246 | Asset::addBefore('http://fonts.googleapis.com/css?family=Arapey', 'http://fonts.googleapis.com/css?family=Londrina+Outline'); 247 | 248 | $keys = array_keys(Asset::$css); 249 | 250 | $this->assertEquals('http://fonts.googleapis.com/css?family=Share+Tech+Mono', Asset::$css[$keys[0]]); 251 | $this->assertEquals('http://fonts.googleapis.com/css?family=Playfair+Display+SC', Asset::$css[$keys[1]]); 252 | $this->assertEquals('http://fonts.googleapis.com/css?family=Arapey', Asset::$css[$keys[2]]); 253 | $this->assertEquals('http://fonts.googleapis.com/css?family=Londrina+Outline', Asset::$css[$keys[3]]); 254 | $this->assertEquals('http://fonts.googleapis.com/css?family=Nova+Square', Asset::$css[$keys[4]]); 255 | $this->assertEquals('http://fonts.googleapis.com/css?family=Special+Elite', Asset::$css[$keys[5]]); 256 | 257 | } 258 | 259 | public function testCssRaw() 260 | { 261 | Asset::$css = []; 262 | 263 | Asset::add(['1.css','2.css','3.css']); 264 | 265 | $this->expectOutputString('/1.css,/2.css,/3.css,', Asset::cssRaw(',')); 266 | } 267 | 268 | public function testCssWrapped() 269 | { 270 | Asset::$css = []; 271 | Asset::add(['1.css','http://foo.dev/2.css'], 'header'); 272 | 273 | $expected = ''."\n".''."\n"; 274 | 275 | $this->expectOutputString($expected, Asset::css('header')); 276 | } 277 | 278 | public function testLessRaw() 279 | { 280 | Asset::$less = []; 281 | 282 | Asset::add(['1.less','2.less','3.less']); 283 | $this->expectOutputString('/1.less,/2.less,/3.less,', Asset::lessRaw(',')); 284 | } 285 | 286 | public function testLessWrapped() 287 | { 288 | Asset::$less = []; 289 | Asset::add(['1.less','http://foo.dev/2.less'], 'header'); 290 | 291 | $expected = ''."\n".''."\n"; 292 | 293 | $this->expectOutputString($expected, Asset::less('header')); 294 | } 295 | 296 | public function testJsRaw() 297 | { 298 | Asset::$js = []; 299 | 300 | Asset::add(['1.js','2.js','3.js']); 301 | $this->expectOutputString('/1.js,/2.js,/3.js,', Asset::jsRaw(',')); 302 | } 303 | 304 | public function testJsWrapped() 305 | { 306 | Asset::$js = []; 307 | Asset::add(['1.js','http://foo.dev/2.js'], 'footer'); 308 | Asset::add('scriptWithParams.js',['name'=>'footer', 'type'=>'text/jsx', 'async' => 'true', 'defer'=>'true']); 309 | 310 | 311 | $expected = ''."\n".''."\n"; 312 | $expected .= ''."\n"; 313 | 314 | $this->expectOutputString($expected, Asset::js('footer')); 315 | } 316 | 317 | public function testStyles() 318 | { 319 | Asset::$styles = []; 320 | $s = 'h1 {font:26px;}'; 321 | Asset::addStyle($s); 322 | 323 | $expected = "\n" . '' . "\n"; 324 | 325 | $this->expectOutputString($expected, Asset::styles()); 326 | } 327 | 328 | public function testDomain() 329 | { 330 | Asset::setDomain('http://cdn.domain.tld/'); 331 | 332 | $this->assertEquals('http://cdn.domain.tld/', Asset::$domain); 333 | } 334 | 335 | public function testCheckEnv() 336 | { 337 | Asset::setDomain('http://cdn.domain.tld/'); 338 | Asset::$environment = 'online'; 339 | Asset::checkEnv(); 340 | 341 | $this->assertEquals('http://cdn.domain.tld/', Asset::$domain); 342 | 343 | Asset::$environment = 'local'; 344 | Asset::checkEnv(); 345 | 346 | $this->assertEquals('/', Asset::$domain); 347 | } 348 | 349 | public function testSecure() 350 | { 351 | Asset::$secure = true; 352 | 353 | $this->assertEquals(true, Asset::$secure); 354 | } 355 | 356 | public function testCachebusterFile() 357 | { 358 | Asset::$js = []; 359 | Asset::$css = []; 360 | Asset::$hash = []; 361 | 362 | Asset::setCachebuster('tests/files/cache.json'); 363 | 364 | $this->assertEquals(Asset::$hash, [ 365 | '1.js' => '27f771f4d8aeea4878c2b5ac39a2031f', 366 | '3.js' => '82f0e3247f8516bd91abcdbed83c71c0', 367 | '2.css' => '42b98f2980dc1366cf1d2677d4891eda' 368 | ] 369 | ); 370 | 371 | Asset::add(['1.js','2.js','3.js']); 372 | Asset::add(['1.css','2.css','3.css']); 373 | 374 | $this->expectOutputString('/1.js?27f771f4d8aeea4878c2b5ac39a2031f,/2.js,/3.js?82f0e3247f8516bd91abcdbed83c71c0,/1.css,/2.css?42b98f2980dc1366cf1d2677d4891eda,/3.css,', Asset::jsRaw(','), Asset::cssRaw(',')); 375 | } 376 | 377 | public function testCachebusterFunction() 378 | { 379 | Asset::$js = []; 380 | Asset::$css = []; 381 | 382 | function _hash($name) 383 | { 384 | if($name == '1.js') { 385 | return ''; 386 | } 387 | if($name == '2.css') { 388 | return null; 389 | } 390 | return substr($name, 0, 1); 391 | } 392 | 393 | Asset::setCacheBusterGeneratorFunction('_hash'); 394 | 395 | Asset::add(['1.js','2.js','3.js']); 396 | Asset::add(['1.css','2.css','3.css']); 397 | 398 | $this->expectOutputString('/1.js,/2.js?2,/3.js?3,/1.css?1,/2.css,/3.css?3,', Asset::jsRaw(','), Asset::cssRaw(',')); 399 | } 400 | 401 | } 402 | -------------------------------------------------------------------------------- /tests/files/cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.js": "27f771f4d8aeea4878c2b5ac39a2031f", 3 | "3.js": "82f0e3247f8516bd91abcdbed83c71c0", 4 | "2.css": "42b98f2980dc1366cf1d2677d4891eda" 5 | } 6 | -------------------------------------------------------------------------------- /tests/files/cdn/1.1.1/test.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaraPalCom/laravel-assets/b35ac522616b2c505dfc940d6c315302fb6a033a/tests/files/cdn/1.1.1/test.min.js -------------------------------------------------------------------------------- /tests/files/cdn/2.2.2/test.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaraPalCom/laravel-assets/b35ac522616b2c505dfc940d6c315302fb6a033a/tests/files/cdn/2.2.2/test.min.js -------------------------------------------------------------------------------- /tests/files/cdn/3.3.3/test.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaraPalCom/laravel-assets/b35ac522616b2c505dfc940d6c315302fb6a033a/tests/files/cdn/3.3.3/test.min.js -------------------------------------------------------------------------------- /tests/files/cdn/test/test-1.1.1.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaraPalCom/laravel-assets/b35ac522616b2c505dfc940d6c315302fb6a033a/tests/files/cdn/test/test-1.1.1.min.js -------------------------------------------------------------------------------- /tests/files/cdn/test/test-2.1.3.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaraPalCom/laravel-assets/b35ac522616b2c505dfc940d6c315302fb6a033a/tests/files/cdn/test/test-2.1.3.min.js -------------------------------------------------------------------------------- /tests/files/cdn/test/test-3.3.3.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LaraPalCom/laravel-assets/b35ac522616b2c505dfc940d6c315302fb6a033a/tests/files/cdn/test/test-3.3.3.min.js --------------------------------------------------------------------------------