Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Jetpack_Perf_Optimize_Assets often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Perf_Optimize_Assets, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 11 | class Jetpack_Perf_Optimize_Assets { |
||
| 12 | private static $__instance = null; |
||
| 13 | private $remove_remote_fonts = false; |
||
| 14 | private $inline_scripts_and_styles = false; |
||
| 15 | private $async_scripts = false; |
||
| 16 | private $defer_scripts = false; |
||
| 17 | private $inject_critical_css = false; |
||
| 18 | private $minify_html = false; |
||
| 19 | const INLINE_ASSET_MAX_SIZE_BYTES = 50 * 1024; // 10kb |
||
| 20 | const INLINE_CSS_URL_MAX_SIZE_BYTES = 5 * 1024; // 5kb |
||
| 21 | |||
| 22 | /** |
||
| 23 | * Singleton implementation |
||
| 24 | * |
||
| 25 | * @return object |
||
| 26 | */ |
||
| 27 | public static function instance() { |
||
| 34 | |||
| 35 | public function disable_for_request() { |
||
| 41 | |||
| 42 | /** |
||
| 43 | * TODO: detect if this is worth doing for wp-admin? |
||
|
|
|||
| 44 | */ |
||
| 45 | |||
| 46 | /** |
||
| 47 | * Registers actions |
||
| 48 | */ |
||
| 49 | private function __construct() { |
||
| 50 | // global $concatenate_scripts; |
||
| 51 | // $concatenate_scripts = true; |
||
| 52 | $this->is_first_load = ! isset( $_COOKIE['jetpack_perf_loaded'] ); |
||
| 53 | $this->remove_remote_fonts = get_option( 'perf_remove_remote_fonts', true ); |
||
| 54 | $this->inline_always = get_option( 'perf_inline_on_every_request', false ); |
||
| 55 | $this->inline_scripts_and_styles = get_option( 'perf_inline_scripts_and_styles', true ) && ( $this->is_first_load || $this->inline_always ); |
||
| 56 | $this->async_scripts = get_option( 'perf_async_scripts', true ); |
||
| 57 | $this->defer_scripts = get_option( 'perf_defer_scripts', true ); |
||
| 58 | $this->move_scripts_above_css_in_header = true; |
||
| 59 | $this->remove_core_emojis = true; |
||
| 60 | $this->prevent_jetpack_implode_css = true; |
||
| 61 | $this->inject_critical_css = true; |
||
| 62 | $this->preload_scripts = true; |
||
| 63 | $this->minify_html = false; |
||
| 64 | |||
| 65 | if ( $this->minify_html ) { |
||
| 66 | require_once dirname( __FILE__ ) . '/class.jetpack-perf-optimize-html.php'; |
||
| 67 | Jetpack_Perf_Optimize_HTML::instance(); |
||
| 68 | } |
||
| 69 | |||
| 70 | if ( $this->remove_remote_fonts ) { |
||
| 71 | add_filter( 'jetpack_perf_remove_script', array( $this, 'remove_external_font_scripts' ), 10, 3 ); |
||
| 72 | add_filter( 'jetpack_perf_remove_style', array( $this, 'remove_external_font_styles' ), 10, 3 ); |
||
| 73 | } |
||
| 74 | |||
| 75 | add_action( 'wp_enqueue_scripts', array( $this, 'relocate_assets' ), PHP_INT_MAX ); |
||
| 76 | |||
| 77 | // relocate assets |
||
| 78 | // add_filter( 'jetpack_perf_style_group', array( $this, 'set_style_groups' ), 10, 2 ); |
||
| 79 | // add_filter( 'jetpack_perf_script_group', array( $this, 'set_script_groups' ), 10, 2 ); |
||
| 80 | |||
| 81 | if ( $this->inject_critical_css ) { |
||
| 82 | add_action( 'wp_head', array( $this, 'render_critical_css' ), 0 ); |
||
| 83 | } |
||
| 84 | |||
| 85 | if ( $this->preload_scripts ) { |
||
| 86 | add_action( 'wp_print_scripts', array( $this, 'preload_scripts' ) ); |
||
| 87 | } |
||
| 88 | |||
| 89 | |||
| 90 | // munge footer scripts |
||
| 91 | //add_filter( 'wp_footer', array( $this, 'encode_footer_scripts' ), -PHP_INT_MAX ); |
||
| 92 | |||
| 93 | // necessary to catch some woocommerce scripts that get localized inside wp_print_scripts at pri 5 |
||
| 94 | //add_filter( 'wp_print_footer_scripts', array( $this, 'encode_footer_scripts' ), 9 ); |
||
| 95 | |||
| 96 | // in modern browsers (post-2008) there is a small performance win from |
||
| 97 | // loading CSS after external scripts in the header, rather than before |
||
| 98 | if ( $this->move_scripts_above_css_in_header ) { |
||
| 99 | add_action( 'init', array( $this, 'move_styles_to_bottom_of_header' ), PHP_INT_MAX ); |
||
| 100 | } |
||
| 101 | |||
| 102 | if ( $this->prevent_jetpack_implode_css ) { |
||
| 103 | add_filter( 'jetpack_implode_frontend_css', '__return_false' ); |
||
| 104 | } |
||
| 105 | |||
| 106 | // ensure dashicons is loading inline |
||
| 107 | add_filter( 'jetpack_perf_async_style', function( $should_async, $handle, $src ) { |
||
| 108 | if ( 'dashicons' === $handle ) { |
||
| 109 | return false; |
||
| 110 | } |
||
| 111 | return $should_async; |
||
| 112 | }, 10, 3 ); |
||
| 113 | |||
| 114 | add_filter( 'jetpack_perf_inline_style', function( $should_inline, $handle, $src ) { |
||
| 115 | if ( 'dashicons' === $handle ) { |
||
| 116 | return true; |
||
| 117 | } |
||
| 118 | return $should_inline; |
||
| 119 | }, 10, 3 ); |
||
| 120 | |||
| 121 | add_filter( 'script_loader_src', array( $this, 'filter_inline_scripts' ), -100, 2 ); |
||
| 122 | add_filter( 'script_loader_tag', array( $this, 'print_inline_scripts' ), -100, 3 ); |
||
| 123 | add_filter( 'style_loader_src', array( $this, 'filter_inline_styles' ), -100, 2 ); |
||
| 124 | add_filter( 'style_loader_tag', array( $this, 'print_inline_styles' ), -100, 4 ); |
||
| 125 | |||
| 126 | add_action( 'init', array( $this, 'set_first_load_cookie' ) ); |
||
| 127 | |||
| 128 | /** |
||
| 129 | * Feature, theme and plugin-specific hacks |
||
| 130 | */ |
||
| 131 | |||
| 132 | // remove emoji detection - TODO a setting for this |
||
| 133 | if ( $this->remove_core_emojis ) { |
||
| 134 | add_action( 'init', array( $this, 'disable_emojis' ) ); |
||
| 135 | } |
||
| 136 | |||
| 137 | // inline/defer/async stuff for Jetpack |
||
| 138 | add_action( 'init', array( $this, 'optimize_jetpack' ) ); |
||
| 139 | } |
||
| 140 | |||
| 141 | /** Disabling Emojis **/ |
||
| 142 | // improves page load performance |
||
| 143 | |||
| 144 | function disable_emojis() { |
||
| 159 | |||
| 160 | function optimize_jetpack() { |
||
| 163 | |||
| 164 | /** |
||
| 165 | * Filter function used to remove the tinymce emoji plugin. |
||
| 166 | * |
||
| 167 | * @param array $plugins |
||
| 168 | * @return array Difference betwen the two arrays |
||
| 169 | */ |
||
| 170 | function disable_emojis_tinymce( $plugins ) { |
||
| 177 | |||
| 178 | /** |
||
| 179 | * Remove emoji CDN hostname from DNS prefetching hints. |
||
| 180 | * |
||
| 181 | * @param array $urls URLs to print for resource hints. |
||
| 182 | * @param string $relation_type The relation type the URLs are printed for. |
||
| 183 | * @return array Difference betwen the two arrays. |
||
| 184 | */ |
||
| 185 | function disable_emojis_remove_dns_prefetch( $urls, $relation_type ) { |
||
| 195 | |||
| 196 | // preload scripts we expect to find in the footer |
||
| 197 | function preload_scripts() { |
||
| 198 | global $wp_scripts; |
||
| 199 | $wp_scripts->all_deps( $wp_scripts->queue, false ); |
||
| 200 | foreach( $wp_scripts->to_do as $handle ) { |
||
| 201 | $registration = $wp_scripts->registered[$handle]; |
||
| 202 | // preload anything not async'd, since these scripts are likely to be a higher priority |
||
| 203 | $is_footer_script = isset( $registration->extra['group'] ) && 1 == $registration->extra['group']; |
||
| 204 | // TODO: this doesn't currently affect any scripts - will it ever? |
||
| 205 | if ( ! $this->should_async_script( $registration ) && $is_footer_script && ! $this->should_inline_script( $registration ) ) { |
||
| 206 | echo '<link rel="preload" as="script" href="'. esc_attr( $registration->src ) .'" />'; |
||
| 207 | } |
||
| 208 | } |
||
| 209 | } |
||
| 210 | |||
| 211 | function render_critical_css() { |
||
| 214 | |||
| 215 | // by default we only inline scripts+styles on first page load for a given user |
||
| 216 | function set_first_load_cookie() { |
||
| 221 | |||
| 222 | // this code essentially sets the default asset location to the footer rather than the head |
||
| 223 | function relocate_assets() { |
||
| 257 | |||
| 258 | // scripts that run after CSS <link>s in the header block waiting for the CSS to load |
||
| 259 | // so we move styles as late as possible in the wp_head action to maximise the chance |
||
| 260 | // of non-blocking rendering |
||
| 261 | function move_styles_to_bottom_of_header() { |
||
| 265 | |||
| 266 | function set_script_groups( $group, $handle ) { |
||
| 290 | |||
| 291 | function set_style_groups( $group, $handle ) { |
||
| 302 | |||
| 303 | // this is wild: inline scripts in the footer can block rendering, but we don't have a chance to intercept them in the header |
||
| 304 | // so let's mark them as defer (which guarantees order) - which also requires adding deps with the localize script base64-encoded |
||
| 305 | |||
| 306 | // so, I don't think this is a performance win - stuff in the footer is already "deferred" and forcing the defer attribute |
||
| 307 | // only makes them load one after the other, rather than allowing the browser to do paralellisation |
||
| 308 | |||
| 309 | // we're better off concatenating |
||
| 310 | function encode_footer_scripts() { |
||
| 332 | |||
| 333 | /** FILTERS **/ |
||
| 334 | public function remove_external_font_scripts( $should_remove, $handle, $asset_url ) { |
||
| 338 | |||
| 339 | public function remove_external_font_styles( $should_remove, $handle, $asset_url ) { |
||
| 343 | |||
| 344 | /** SCRIPTS **/ |
||
| 345 | View Code Duplication | public function filter_inline_scripts( $src, $handle ) { |
|
| 361 | |||
| 362 | public function print_inline_scripts( $tag, $handle, $src ) { |
||
| 395 | |||
| 396 | private function should_async_script( $script ) { |
||
| 397 | // this could be dangerous if scripts have undeclared dependencies |
||
| 398 | // only make scripts async if nothing depends on them |
||
| 399 | // turns out this is a problem - lots of plugins import (say) jquery extensions and then use them in the page from inline scripts. Bah. |
||
| 400 | // but if a script has been declared in the footer? hm maybe that's ok........ |
||
| 401 | $should_async_script = ! $this->script_has_deps( $script->handle ) && isset( $script->extra['group'] ) && $script->extra['group'] === 1; |
||
| 402 | |||
| 403 | // you can override this logic by setting jetpack-async |
||
| 404 | $should_async_script = $should_async_script || ( isset( $script->extra['jetpack-async'] ) && $script->extra['jetpack-async'] ); |
||
| 405 | return $this->async_scripts && apply_filters( 'jetpack_perf_async_script', $should_async_script, $script->handle, $script->src ); |
||
| 406 | } |
||
| 407 | |||
| 408 | private function script_has_deps( $handle ) { |
||
| 419 | |||
| 420 | private function should_defer_script( $script ) { |
||
| 438 | |||
| 439 | private function should_remove_script( $script ) { |
||
| 442 | |||
| 443 | private function should_inline_script( $script ) { |
||
| 446 | |||
| 447 | /** STYLES **/ |
||
| 448 | View Code Duplication | public function filter_inline_styles( $src, $handle ) { |
|
| 463 | |||
| 464 | public function print_inline_styles( $tag, $handle, $href, $media ) { |
||
| 496 | |||
| 497 | // we can async styles if we load critical CSS in the header |
||
| 498 | private function should_async_style( $style ) { |
||
| 501 | |||
| 502 | // for CSS urls()s, replace with base64-encoded content if smaller than a certain size |
||
| 503 | public function fix_css_urls( $css, $css_url ) { |
||
| 525 | |||
| 526 | // see: http://stackoverflow.com/questions/4444475/transfrom-relative-path-into-absolute-url-using-php |
||
| 527 | private function rel2abspath( $rel, $path) { |
||
| 547 | |||
| 548 | private function should_inline_style( $style ) { |
||
| 551 | |||
| 552 | private function should_remove_style( $style ) { |
||
| 555 | |||
| 556 | /** shared code **/ |
||
| 557 | |||
| 558 | private function should_inline_asset( $filter, $dependency ) { |
||
| 583 | |||
| 584 | private function local_url_to_file_path( $url ) { |
||
| 590 | |||
| 591 | private function should_remove_asset( $filter, $dependency ) { |
||
| 594 | |||
| 595 | /** |
||
| 596 | * if inline assets are enabled, renders inline |
||
| 597 | * TODO: enable this just for certain paths/patterns/filetypes |
||
| 598 | * This is actually currently unused |
||
| 599 | */ |
||
| 600 | public function register_inline_script( $handle, $file, $plugin_file, $deps = false, $ver = false, $in_footer = false ) { |
||
| 611 | |||
| 612 | /** |
||
| 613 | * if inline assets are enabled, renders inline |
||
| 614 | * TODO: enable this just for certain paths/patterns/filetypes |
||
| 615 | * This is actually currently unused |
||
| 616 | */ |
||
| 617 | public function register_inline_style( $handle, $file, $plugin_file, $deps = array(), $ver = false, $media = 'all' ) { |
||
| 626 | } |
||
| 627 |