| @@ -57,9 +57,14 @@ | ||
| 57 | 57 | '_stq', |
| 58 | 58 | 'nonce', |
| 59 | 59 | 'post_id', |
| 60 | 60 | 'data-noptimize', |
| 61 | + 'data-cfasync', | |
| 62 | + 'data-pagespeed-no-defer', | |
| 61 | 63 | 'logHuman', |
| 64 | + 'amp-mobile-version-switcher', | |
| 65 | + 'data-rocketlazyloadscript', | |
| 66 | + 'rocket-browser-checker-js-after', | |
| 62 | 67 | ); |
| 63 | 68 | |
| 64 | 69 | /** |
| 65 | 70 | * List of to be moved JS. |
| @@ -108,8 +113,22 @@ | ||
| 108 | 113 | */ |
| 109 | 114 | private $aggregate = true; |
| 110 | 115 | |
| 111 | 116 | /** |
| 117 | + * Setting; if not aggregated, should we defer? | |
| 118 | + * | |
| 119 | + * @var bool | |
| 120 | + */ | |
| 121 | + private $defer_not_aggregate = false; | |
| 122 | + | |
| 123 | + /** | |
| 124 | + * Setting; defer inline JS? | |
| 125 | + * | |
| 126 | + * @var bool | |
| 127 | + */ | |
| 128 | + private $defer_inline = false; | |
| 129 | + | |
| 130 | + /** | |
| 112 | 131 | * Setting; try/catch wrapping or not. |
| 113 | 132 | * |
| 114 | 133 | * @var bool |
| 115 | 134 | */ |
| @@ -199,9 +218,19 @@ | ||
| 199 | 218 | * @param array $options all options. |
| 200 | 219 | */ |
| 201 | 220 | public function read( $options ) |
| 202 | 221 | { |
| 203 | - $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content ); | |
| 222 | + $noptimize_js = false; | |
| 223 | + | |
| 224 | + // If page/ post check post_meta to see if optimize is off. | |
| 225 | + if ( false === autoptimizeConfig::get_post_meta_ao_settings( 'ao_post_js_optimize' ) ) { | |
| 226 | + $noptimize_js = true; | |
| 227 | + } | |
| 228 | + | |
| 229 | + // And a filter to enforce JS noptimize. | |
| 230 | + $noptimize_js = apply_filters( 'autoptimize_filter_js_noptimize', $noptimize_js, $this->content ); | |
| 231 | + | |
| 232 | + // And finally bail if noptimize_js is true. | |
| 204 | 233 | if ( $noptimize_js ) { |
| 205 | 234 | return false; |
| 206 | 235 | } |
| 207 | 236 | |
| @@ -206,9 +235,8 @@ | ||
| 206 | 235 | } |
| 207 | 236 | |
| 208 | 237 | // only optimize known good JS? |
| 209 | 238 | $allowlist_js = apply_filters( 'autoptimize_filter_js_allowlist', '', $this->content ); |
| 210 | - $allowlist_js = apply_filters( 'autoptimize_filter_js_whitelist', $allowlist_js, $this->content ); // fixme: to be removed in next version. | |
| 211 | 239 | if ( ! empty( $allowlist_js ) ) { |
| 212 | 240 | $this->allowlist = array_filter( array_map( 'trim', explode( ',', $allowlist_js ) ) ); |
| 213 | 241 | } |
| 214 | 242 | |
| @@ -232,9 +260,21 @@ | ||
| 232 | 260 | // Returning true for "dontaggregate" turns off aggregation. |
| 233 | 261 | if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) { |
| 234 | 262 | $this->aggregate = false; |
| 235 | 263 | } |
| 264 | + // and the filter that should have been there to begin with. | |
| 265 | + $this->aggregate = apply_filters( 'autoptimize_filter_js_aggregate', $this->aggregate ); | |
| 236 | 266 | |
| 267 | + // Defer when not aggregating. | |
| 268 | + if ( false === $this->aggregate && apply_filters( 'autoptimize_filter_js_defer_not_aggregate', $options['defer_not_aggregate'] ) ) { | |
| 269 | + $this->defer_not_aggregate = true; | |
| 270 | + } | |
| 271 | + | |
| 272 | + // Defer inline JS? | |
| 273 | + if ( ( true === $this->defer_not_aggregate && apply_filters( 'autoptimize_js_filter_defer_inline', $options['defer_inline'] ) ) || apply_filters( 'autoptimize_js_filter_force_defer_inline', false ) ) { | |
| 274 | + $this->defer_inline = true; | |
| 275 | + } | |
| 276 | + | |
| 237 | 277 | // include inline? |
| 238 | 278 | if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) { |
| 239 | 279 | $this->include_inline = true; |
| 240 | 280 | } |
| @@ -335,8 +375,25 @@ | ||
| 335 | 375 | } |
| 336 | 376 | } |
| 337 | 377 | } |
| 338 | 378 | |
| 379 | + // not aggregating but deferring? | |
| 380 | + if ( $this->defer_not_aggregate && false === $this->aggregate && ( str_replace( $this->dontmove, '', $path ) === $path || ( apply_filters( 'autoptimize_filter_js_defer_external', true ) && str_replace( $this->dontmove, '', $orig_tag ) === $orig_tag ) ) && strpos( $new_tag, ' defer' ) === false ) { | |
| 381 | + if ( false !== strpos( $new_tag, ' async' ) && true === apply_filters( 'autoptimize_filter_js_defer_trumps_async', true ) ) { | |
| 382 | + // remove async flag to ensure JS is properly deferred, otherwise the asynced JS might fire | |
| 383 | + // before deferred inlined JS is executed, off course except filter is set to false which | |
| 384 | + // re-institutes previous behavior. | |
| 385 | + $new_tag = str_replace( array( " async='async'", ' async="async"', ' async=async', ' async' ), '', $new_tag ); | |
| 386 | + } | |
| 387 | + | |
| 388 | + if ( false === strpos( $new_tag, ' async' ) ) { | |
| 389 | + // either async wasn't there to begin with or it was removed. | |
| 390 | + // if async is there, the autoptimize_filter_js_defer_trumps_async | |
| 391 | + // filter was set to false and in that case defer should not be added. | |
| 392 | + $new_tag = str_replace( '<script ', '<script defer ', $new_tag ); | |
| 393 | + } | |
| 394 | + } | |
| 395 | + | |
| 339 | 396 | // Should we minify the non-aggregated script? |
| 340 | 397 | // -> if aggregate is on and exclude minify is on |
| 341 | 398 | // -> if aggregate is off and the file is not in dontmove. |
| 342 | 399 | if ( $path && $this->minify_excluded ) { |
| @@ -353,8 +410,13 @@ | ||
| 353 | 410 | } |
| 354 | 411 | } |
| 355 | 412 | } |
| 356 | 413 | |
| 414 | + // Check if we still need to CDN (esp. for already minified resources). | |
| 415 | + if ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) { | |
| 416 | + $new_tag = str_replace( $url, $this->url_replace_cdn( $url ), $new_tag ); | |
| 417 | + } | |
| 418 | + | |
| 357 | 419 | if ( $this->ismovable( $new_tag ) ) { |
| 358 | 420 | // can be moved, flags and all. |
| 359 | 421 | if ( $this->movetolast( $new_tag ) ) { |
| 360 | 422 | $this->move['last'][] = $new_tag; |
| @@ -385,18 +447,46 @@ | ||
| 385 | 447 | $code = preg_replace( '#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] ); |
| 386 | 448 | $code = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code ); |
| 387 | 449 | $this->scripts[] = 'INLINE;' . $code; |
| 388 | 450 | } else { |
| 389 | - // Can we move this? | |
| 390 | - $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag ); | |
| 391 | - if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) { | |
| 392 | - if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) { | |
| 393 | - $this->move['last'][] = $tag; | |
| 451 | + $_inline_deferable = apply_filters( 'autoptimize_filters_js_inline_deferable', array( 'nonce', 'post_id', 'syntaxhighlighter' ) ); | |
| 452 | + $_inline_dontmove = array_values( array_diff( $this->dontmove, $_inline_deferable ) ); | |
| 453 | + if ( false === $this->defer_inline ) { | |
| 454 | + // Can we move this? | |
| 455 | + $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag ); | |
| 456 | + if ( $this->ismovable( $tag ) || '' !== $autoptimize_js_moveable ) { | |
| 457 | + if ( $this->movetolast( $tag ) || 'last' === $autoptimize_js_moveable ) { | |
| 458 | + $this->move['last'][] = $tag; | |
| 459 | + } else { | |
| 460 | + $this->move['first'][] = $tag; | |
| 461 | + } | |
| 394 | 462 | } else { |
| 395 | - $this->move['first'][] = $tag; | |
| 463 | + $tag = ''; | |
| 396 | 464 | } |
| 465 | + } else if ( str_replace( $_inline_dontmove, '', $tag ) === $tag && strlen( $tag ) < apply_filters( 'autoptimize_filter_script_defer_inline_maxsize', 200000 ) ) { | |
| 466 | + // defer inline JS by base64 encoding it but only if string is not ridiculously huge (to avoid issues with below regex mainly). | |
| 467 | + preg_match( '#<script(?:[^>](?!id=))*\s*(?:id=(["\'])([^"\']+)\1)*+[^>]*+>(.*?)<\/script>#is', $tag, $match ); | |
| 468 | + if ( $match[2] ) { | |
| 469 | + $_id = 'id="' . $match[2] . '" '; | |
| 470 | + } else { | |
| 471 | + $_id = ''; | |
| 472 | + } | |
| 473 | + | |
| 474 | + // if "minify inline" is on and if more then 9 spaces or 4 line breaks are found | |
| 475 | + // in the inline JS then it is likely not minified, so minify before base64-encoding. | |
| 476 | + $_script_contents = $match[3]; | |
| 477 | + if ( 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_html_minify_inline', 'off' ) && substr_count( $_script_contents, ' ' ) > 9 && substr_count( $_script_contents, "\n" ) > 4 && true === apply_filters( 'autoptimize_filter_script_defer_inline_minify', true ) ) { | |
| 478 | + $_tmp_script_contents = trim( JSMin::minify( $_script_contents ) ); | |
| 479 | + if ( ! empty( $_tmp_script_contents ) ) { | |
| 480 | + $_script_contents = $_tmp_script_contents; | |
| 481 | + } | |
| 482 | + } | |
| 483 | + | |
| 484 | + // base64 and defer the lot already. | |
| 485 | + $new_tag = '<script defer ' . $_id . 'src="data:text/javascript;base64,' . base64_encode( $_script_contents ) . '"></script>'; | |
| 486 | + $this->content = str_replace( $this->hide_comments( $tag ), $new_tag, $this->content ); | |
| 487 | + $tag = ''; | |
| 397 | 488 | } else { |
| 398 | - // We shouldn't touch this. | |
| 399 | 489 | $tag = ''; |
| 400 | 490 | } |
| 401 | 491 | } |
| 402 | 492 | // Re-hide comments to be able to do the removal based on tag from $this->content. |
| @@ -428,9 +518,9 @@ | ||
| 428 | 518 | * |
| 429 | 519 | * @param string $tag Script node & child(ren). |
| 430 | 520 | * @return bool |
| 431 | 521 | */ |
| 432 | - public function should_aggregate( $tag ) | |
| 522 | + public static function should_aggregate( $tag ) | |
| 433 | 523 | { |
| 434 | 524 | if ( empty( $tag ) ) { |
| 435 | 525 | return false; |
| 436 | 526 | } |
| @@ -443,9 +533,9 @@ | ||
| 443 | 533 | if ( ! empty( $tag_parts[1] ) ) { |
| 444 | 534 | $tag_without_contents = $tag_parts[1]; |
| 445 | 535 | } |
| 446 | 536 | |
| 447 | - $has_type = ( strpos( $tag_without_contents, 'type' ) !== false ); | |
| 537 | + $has_type = ( strpos( $tag_without_contents, ' type' ) !== false ); | |
| 448 | 538 | |
| 449 | 539 | $type_valid = false; |
| 450 | 540 | if ( $has_type ) { |
| 451 | 541 | $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents ); |
| @@ -698,9 +788,8 @@ | ||
| 698 | 788 | /** |
| 699 | 789 | * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on: |
| 700 | 790 | * - inject_min_late being active |
| 701 | 791 | * - filename ending in `min.js` |
| 702 | - * - filename matching `js/jquery/jquery.js` (WordPress core jquery, is minified) | |
| 703 | 792 | * - filename matching one passed in the consider minified filter |
| 704 | 793 | * |
| 705 | 794 | * @param string $js_path Path to JS file. |
| 706 | 795 | * @return bool |
| @@ -709,9 +798,9 @@ | ||
| 709 | 798 | $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false ); |
| 710 | 799 | if ( true !== $this->inject_min_late ) { |
| 711 | 800 | // late-inject turned off. |
| 712 | 801 | return false; |
| 713 | - } elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( false === strpos( $js_path, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) { | |
| 802 | + } elseif ( ( false === strpos( $js_path, 'min.js' ) ) && ( str_replace( $consider_minified_array, '', $js_path ) === $js_path ) ) { | |
| 714 | 803 | // file not minified based on filename & filter. |
| 715 | 804 | return false; |
| 716 | 805 | } else { |
| 717 | 806 | // phew, all is safe, we can late-inject. |