├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Fitztrev │ └── LaravelHtmlMinify │ │ ├── LaravelHtmlMinifyCompiler.php │ │ └── LaravelHtmlMinifyServiceProvider.php └── config │ └── config.php └── tests └── BaseMinifyTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | 6 | report 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | 8 | before_install: 9 | - wget http://cs.sensiolabs.org/get/php-cs-fixer.phar 10 | 11 | before_script: 12 | - composer self-update 13 | - composer install --dev 14 | 15 | script: 16 | - phpunit 17 | - output=$(php php-cs-fixer.phar fix -v --dry-run --level=psr2 .); if [[ $output ]]; then while read -r line; do echo -e "\e[00;31m$line\e[00m"; done <<< "$output"; false; fi; 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Trevor Fitzgerald 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## UPDATE for Laravel 5/5.1+ 2 | 3 | Read my preferred way of minifying HTML in Laravel 5/5.1+ apps here: [Using Gulp to Minify Laravel Blade Templates](https://github.com/fitztrev/laravel-html-minify/wiki/Laravel-5---5.1-HTML-Minifying) 4 | 5 | --- 6 | 7 | # Laravel HTML Minify 8 | 9 | ### For Laravel 4 - See [here](https://github.com/fitztrev/laravel-html-minify/wiki/Laravel-5---5.1-HTML-Minifying) for L5+ 10 | 11 | [![Build Status](https://travis-ci.org/fitztrev/laravel-html-minify.png)](https://travis-ci.org/fitztrev/laravel-html-minify) 12 | [![Total Downloads](https://poser.pugx.org/fitztrev/laravel-html-minify/downloads.png)](https://packagist.org/packages/fitztrev/laravel-html-minify) 13 | 14 | ## About 15 | 16 | This package compresses the HTML output from your Laravel 4 application, seamlessly reducing the overall response size of your pages. 17 | 18 | Other scripts that I've seen will compress the HTML output on-the-fly for each request. Instead, this package extends the Blade compiler to save the compiled template files to disk in their compressed state, reducing the overhead for each request. 19 | 20 | ## Why? 21 | 22 | Even with gzip enabled, there is still an improvement in the response size for HTML content-type documents. 23 | 24 | Test Page | w/o Gzip | w/ Gzip | w/ Gzip + Laravel HTML Minify 25 | --- | ---: | ---: | :---: 26 | **#1** | 8,039 bytes | 1,944 bytes | **1,836 bytes** (5.6% improvement) 27 | **#2** | 377,867 bytes | 5,247 bytes | **4,314 bytes** (17.8% improvement) 28 | 29 | ## Installation 30 | 31 | 1. Add `"fitztrev/laravel-html-minify": "1.*"` to **composer.json**. 32 | 2. Run `composer update` 33 | 3. Add `Fitztrev\LaravelHtmlMinify\LaravelHtmlMinifyServiceProvider` to the list of providers in **app/config/app.php**. 34 | 4. **Important:** You won't see any changes until you edit your `*.blade.php` template files. Once Laravel detects a change, it will recompile them, which is when this package will go to work. To force all views to be recompiled, just run this command: `find . -name "*.blade.php" -exec touch {} \;` 35 | 36 | ## Config 37 | 38 | Optionally, you can choose to customize how the minifier functions for different environments. Publish the configuration file and edit accordingly. 39 | 40 | $ php artisan config:publish fitztrev/laravel-html-minify 41 | 42 | ### Options 43 | 44 | - **`enabled`** - *boolean*, default **true** 45 | 46 | If you are using a javascript framework that conflicts with Blade's tags, you can change them. 47 | 48 | - **`blade.contentTags`** - *array*, default `{{` and `}}` 49 | - **`blade.escapedContentTags`** - *array*, default `{{{` and `}}}` 50 | 51 | #### Skipping minification 52 | 53 | To prevent the minification of a view file, add `skipmin` somewhere in the view. 54 | 55 | ``` 56 | {{-- skipmin --}} 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fitztrev/laravel-html-minify", 3 | "description": "Minifies the HTML output of Laravel 4 applications", 4 | "keywords": ["laravel", "l4", "html", "optimization", "performance", "compress", "minify", "minifier", "blade"], 5 | "homepage": "https://github.com/fitztrev/laravel-html-minify", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Trevor Fitzgerald", 10 | "email": "fitztrev@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "illuminate/view": "4.x" 15 | }, 16 | "require-dev": { 17 | "mockery/mockery": "dev-master", 18 | "phpunit/phpunit": "3.7.*", 19 | "phpunit/php-code-coverage": ">=1.2.10,<1.3.0" 20 | }, 21 | "autoload": { 22 | "psr-0": { 23 | "Fitztrev\\LaravelHtmlMinify": "src/" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | 21 | src/Fitztrev/LaravelHtmlMinify/LaravelHtmlMinifyCompiler.php 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Fitztrev/LaravelHtmlMinify/LaravelHtmlMinifyCompiler.php: -------------------------------------------------------------------------------- 1 | _config = $config; 14 | 15 | // Add Minify to the list of compilers 16 | if ($this->_config['enabled'] === true) { 17 | $this->compilers[] = 'Minify'; 18 | } 19 | 20 | // Set Blade contentTags and escapedContentTags 21 | $this->setContentTags( 22 | $this->_config['blade']['contentTags'][0], 23 | $this->_config['blade']['contentTags'][1] 24 | ); 25 | 26 | $this->setEscapedContentTags( 27 | $this->_config['blade']['escapedContentTags'][0], 28 | $this->_config['blade']['escapedContentTags'][1] 29 | ); 30 | 31 | } 32 | 33 | /** 34 | * We'll only compress a view if none of the following conditions are met. 35 | * 1)
 or 
107 |             
108 |         ';
109 |         $expected = '
110 |             
111 |                 
112 |             
113 |         ';
114 | 
115 |         $result = $this->compiler->compileString($string);
116 |         $this->assertEquals($expected, $result);
117 |     }
118 | 
119 |     public function testTextareaTagWithAttributes()
120 |     {
121 |         $string = '
122 |             
123 |                 
124 |             
125 |         ';
126 |         $expected = '
127 |             
128 |                 
129 |             
130 |         ';
131 | 
132 |         $result = $this->compiler->compileString($string);
133 |         $this->assertEquals($expected, $result);
134 |     }
135 | 
136 |     /* *** */
137 | 
138 |     public function testSingleExternalScriptTag()
139 |     {
140 |         $string = '
141 |             
142 |                 
143 |             
144 |         ';
145 |         $expected = '    ';
146 | 
147 |         $result = $this->compiler->compileString($string);
148 | 
149 |         if ($this->config['enabled']) {
150 |             $this->assertEquals($expected, $result);
151 |         } else {
152 |             $this->assertEquals($string, $result);
153 |         }
154 |     }
155 | 
156 |     public function testSingleExternalScriptTagWithCacheBuster()
157 |     {
158 |         $string = '
159 |             
160 |                 
161 |             
162 |         ';
163 |         $expected = '    ';
164 | 
165 |         $result = $this->compiler->compileString($string);
166 | 
167 |         if ($this->config['enabled']) {
168 |             $this->assertEquals($expected, $result);
169 |         } else {
170 |             $this->assertEquals($string, $result);
171 |         }
172 |     }
173 | 
174 |     public function testMultipleExternalScriptTag()
175 |     {
176 |         $string = '
177 |             
178 |                 
179 |                 
180 |             
181 |         ';
182 |         $expected = '     ';
183 | 
184 |         $result = $this->compiler->compileString($string);
185 | 
186 |         if ($this->config['enabled']) {
187 |             $this->assertEquals($expected, $result);
188 |         } else {
189 |             $this->assertEquals($string, $result);
190 |         }
191 |     }
192 | 
193 |     public function testExternalAndEmbeddedScriptTag()
194 |     {
195 |         $string = '
196 |             
197 |                 
198 |                 
201 |             
202 |         ';
203 |         $expected = '
204 |             
205 |                 
206 |                 
209 |             
210 |         ';
211 | 
212 |         $result = $this->compiler->compileString($string);
213 |         $this->assertEquals($expected, $result);
214 |     }
215 | 
216 |     public function testGoogleAdSenseEmbedTag()
217 |     {
218 |         $string = '
219 |             
220 |                 
228 |                 
231 |             
232 |         ';
233 | 
234 |         $expected = '
235 |             
236 |                 
244 |                 
247 |             
248 |         ';
249 | 
250 |         $result = $this->compiler->compileString($string);
251 |         $this->assertEquals($expected, $result);
252 |     }
253 | 
254 |     public function testEmbeddedScriptTagSingleLine()
255 |     {
256 |         $string = '
257 |             
258 |                 
259 |             
260 |         ';
261 |         $expected = '
262 |             
263 |                 
264 |             
265 |         ';
266 | 
267 |         $result = $this->compiler->compileString($string);
268 |         $this->assertEquals($expected, $result);
269 |     }
270 | 
271 |     public function testEmbeddedScriptTagMultipleLines()
272 |     {
273 |         $string = '
274 |             
275 |                 
279 |             
280 |         ';
281 |         $expected = '
282 |             
283 |                 
287 |             
288 |         ';
289 | 
290 |         $result = $this->compiler->compileString($string);
291 |         $this->assertEquals($expected, $result);
292 |     }
293 | 
294 |     /* *** */
295 | 
296 |     public function testValueWithoutMultipleSpacesSingleWord()
297 |     {
298 |         $string = '
299 |             
300 |                 
301 | 302 |
303 | 304 | '; 305 | $expected = '
'; 306 | 307 | $result = $this->compiler->compileString($string); 308 | 309 | if ($this->config['enabled']) { 310 | $this->assertEquals($expected, $result); 311 | } else { 312 | $this->assertEquals($string, $result); 313 | } 314 | } 315 | 316 | public function testValueWithoutMultipleSpacesSingleWordSingleQuotes() 317 | { 318 | $string = ' 319 | 320 |
321 | 322 |
323 | 324 | '; 325 | $expected = '
'; 326 | 327 | $result = $this->compiler->compileString($string); 328 | 329 | if ($this->config['enabled']) { 330 | $this->assertEquals($expected, $result); 331 | } else { 332 | $this->assertEquals($string, $result); 333 | } 334 | } 335 | 336 | public function testValueWithoutMultipleSpacesMultipleWords() 337 | { 338 | $string = ' 339 | 340 |
341 | 342 |
343 | 344 | '; 345 | $expected = '
'; 346 | 347 | $result = $this->compiler->compileString($string); 348 | 349 | if ($this->config['enabled']) { 350 | $this->assertEquals($expected, $result); 351 | } else { 352 | $this->assertEquals($string, $result); 353 | } 354 | } 355 | 356 | public function testValueWithMultipleSpaces() 357 | { 358 | $string = ' 359 | 360 |
361 | 362 |
363 | 364 | '; 365 | $expected = ' 366 | 367 |
368 | 369 |
370 | 371 | '; 372 | 373 | $result = $this->compiler->compileString($string); 374 | $this->assertEquals($expected, $result); 375 | } 376 | 377 | public function testValueWithMultipleSpacesSingleQuotes() 378 | { 379 | $string = ' 380 | 381 |
382 | 383 |
384 | 385 | '; 386 | $expected = ' 387 | 388 |
389 | 390 |
391 | 392 | '; 393 | 394 | $result = $this->compiler->compileString($string); 395 | $this->assertEquals($expected, $result); 396 | } 397 | 398 | /* *** */ 399 | 400 | public function testAllowedHtml() 401 | { 402 | $string = ' 403 | 404 |

hello

405 | 406 | '; 407 | $expected = '

hello

'; 408 | 409 | $result = $this->compiler->compileString($string); 410 | 411 | if ($this->config['enabled']) { 412 | $this->assertEquals($expected, $result); 413 | } else { 414 | $this->assertEquals($string, $result); 415 | } 416 | } 417 | 418 | public function testMultipleSpaces() 419 | { 420 | $string = ' 421 | 422 |

hello with random spaces

423 | 424 | '; 425 | $expected = '

hello with random spaces

'; 426 | 427 | $result = $this->compiler->compileString($string); 428 | 429 | if ($this->config['enabled']) { 430 | $this->assertEquals($expected, $result); 431 | } else { 432 | $this->assertEquals($string, $result); 433 | } 434 | } 435 | 436 | public function testNoIndentation() 437 | { 438 | $string = ''; 441 | $expected = ''; 442 | 443 | $result = $this->compiler->compileString($string); 444 | 445 | if ($this->config['enabled']) { 446 | $this->assertEquals($expected, $result); 447 | } else { 448 | $this->assertEquals($string, $result); 449 | } 450 | } 451 | 452 | public function testSkipMinification() 453 | { 454 | $string = '

455 | 456 |

'; 457 | $expected = '

458 | 459 |

'; 460 | 461 | $result = $this->compiler->compileString($string); 462 | $this->assertEquals($expected, $result); 463 | } 464 | 465 | } 466 | 467 | class EnabledTester extends BaseMinifyTester 468 | { 469 | public function __construct() 470 | { 471 | $this->config = array( 472 | 'enabled' => true, 473 | 'blade' => array( 474 | 'contentTags' => array('{{', '}}'), 475 | 'escapedContentTags' => array('{{{', '}}}') 476 | ) 477 | ); 478 | } 479 | 480 | public function testConfigEnabled() 481 | { 482 | $this->assertEquals($this->config['enabled'], true); 483 | } 484 | } 485 | 486 | class DisabledTester extends BaseMinifyTester 487 | { 488 | public function __construct() 489 | { 490 | $this->config = array( 491 | 'enabled' => false, 492 | 'blade' => array( 493 | 'contentTags' => array('{{', '}}'), 494 | 'escapedContentTags' => array('{{{', '}}}') 495 | ) 496 | ); 497 | } 498 | 499 | public function testConfigDisabled() 500 | { 501 | $this->assertEquals($this->config['enabled'], false); 502 | } 503 | } 504 | 505 | class DefaultContentTags extends BaseMinifyTester 506 | { 507 | public function __construct() 508 | { 509 | $this->config = array( 510 | 'enabled' => false, 511 | 'blade' => array( 512 | 'contentTags' => array('{{', '}}'), 513 | 'escapedContentTags' => array('{{{', '}}}') 514 | ) 515 | ); 516 | } 517 | 518 | public function testDefaultContentTags() 519 | { 520 | $string = '{{ "hello world" }}'; 521 | $expected = ''; 522 | 523 | $result = $this->compiler->compileString($string); 524 | 525 | $this->assertEquals($expected, $result); 526 | } 527 | 528 | public function testDefaultEscapedContentTags() 529 | { 530 | $string = '{{{ "hello world" }}}'; 531 | $expected = ''; 532 | 533 | $result = $this->compiler->compileString($string); 534 | 535 | $this->assertEquals($expected, $result); 536 | } 537 | } 538 | 539 | class ChangedContentTags extends BaseMinifyTester 540 | { 541 | public function __construct() 542 | { 543 | $this->config = array( 544 | 'enabled' => false, 545 | 'blade' => array( 546 | 'contentTags' => array('[[', ']]'), 547 | 'escapedContentTags' => array('[[[', ']]]') 548 | ) 549 | ); 550 | } 551 | 552 | public function testChangedContentTags() 553 | { 554 | $string = '[[ "hello world" ]]'; 555 | $expected = ''; 556 | 557 | $result = $this->compiler->compileString($string); 558 | 559 | $this->assertEquals($expected, $result); 560 | } 561 | 562 | public function testChangedEscapedContentTags() 563 | { 564 | $string = '[[[ "hello world" ]]]'; 565 | $expected = ''; 566 | 567 | $result = $this->compiler->compileString($string); 568 | 569 | $this->assertEquals($expected, $result); 570 | } 571 | } 572 | --------------------------------------------------------------------------------