-
Notifications
You must be signed in to change notification settings - Fork 220
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1019 from wp-media/feature/combine-inline-scripts
Minify Refactor #3: CSS minification/combine
- Loading branch information
Showing
6 changed files
with
729 additions
and
0 deletions.
There are no files selected for viewing
142 changes: 142 additions & 0 deletions
142
inc/classes/optimization/CSS/class-abstract-css-optimization.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
<?php | ||
namespace WP_Rocket\Optimization\CSS; | ||
|
||
use WP_Rocket\Optimization\Abstract_Optimization; | ||
use WP_Rocket\Admin\Options_Data as Options; | ||
use Wa72\HtmlPageDom\HtmlPageCrawler; | ||
|
||
/** | ||
* Abstract class for CSS Optimization | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
*/ | ||
abstract class Abstract_CSS_Optimization extends Abstract_Optimization { | ||
const FILE_TYPE = 'css'; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @param HtmlPageCrawler $crawler Crawler instance. | ||
* @param Options $options Options instance. | ||
*/ | ||
public function __construct( HtmlPageCrawler $crawler, Options $options ) { | ||
parent::__construct( $crawler ); | ||
|
||
$this->options = $options; | ||
$this->minify_key = $this->options->get( 'minify_css_key', create_rocket_uniqid() ); | ||
$this->excluded_files = $this->get_excluded_files(); | ||
$this->minify_base_path = WP_ROCKET_MINIFY_CACHE_PATH . get_current_blog_id() . '/'; | ||
$this->minify_base_url = WP_ROCKET_MINIFY_CACHE_URL . get_current_blog_id() . '/'; | ||
} | ||
|
||
/** | ||
* Get all files to exclude from minification/concatenation. | ||
* | ||
* @since 2.11 | ||
* @author Remy Perona | ||
* | ||
* @return string | ||
*/ | ||
protected function get_excluded_files() { | ||
$excluded_files = $this->options->get( 'exclude_css', array() ); | ||
|
||
/** | ||
* Filters CSS files to exclude from minification/concatenation. | ||
* | ||
* @since 2.6 | ||
* | ||
* @param array $excluded_files List of excluded CSS files. | ||
*/ | ||
$excluded_files = apply_filters( 'rocket_exclude_css', $excluded_files ); | ||
|
||
if ( empty( $excluded_files ) ) { | ||
return ''; | ||
} | ||
|
||
foreach ( $excluded_files as $i => $excluded_file ) { | ||
$excluded_files[ $i ] = str_replace( '#', '\#', $excluded_file ); | ||
} | ||
|
||
return implode( '|', $excluded_files ); | ||
} | ||
|
||
/** | ||
* Returns the CDN zones. | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @return array | ||
*/ | ||
public function get_zones() { | ||
return array( 'all', 'css_and_js', self::FILE_TYPE ); | ||
} | ||
|
||
/** | ||
* Gets the minify URL | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @param string $filename Minified filename. | ||
* @return string | ||
*/ | ||
protected function get_minify_url( $filename ) { | ||
$minify_url = get_rocket_cdn_url( $this->minify_base_url . $filename, $this->get_zones() ); | ||
|
||
/** | ||
* Filters CSS file URL with CDN hostname | ||
* | ||
* @since 2.1 | ||
* | ||
* @param string $minify_url Minified file URL. | ||
*/ | ||
return apply_filters( 'rocket_css_url', $minify_url ); | ||
} | ||
|
||
/** | ||
* Determines if it is a file excluded from minification | ||
* | ||
* @since 2.11 | ||
* @author Remy Perona | ||
* | ||
* @param HtmlPageCrawler $node Node corresponding to a CSS file. | ||
* @return bool True if it is a file excluded, false otherwise | ||
*/ | ||
protected function is_minify_excluded_file( $node ) { | ||
// File should not be minified. | ||
if ( $node->attr( 'data-minify' ) || $node->attr( 'data-no-minify' ) ) { | ||
return true; | ||
} | ||
|
||
$media = $node->attr( 'media' ); | ||
|
||
if ( $media && ! preg_match( '/(all|screen)/iU', $media ) ) { | ||
return true; | ||
} | ||
|
||
if ( $media && false !== strpos( $media, 'only screen and' ) ) { | ||
return true; | ||
} | ||
|
||
$file_path = rocket_extract_url_component( $node->attr( 'href' ), PHP_URL_PATH ); | ||
|
||
// File extension is not css. | ||
if ( pathinfo( $file_path, PATHINFO_EXTENSION ) !== self::FILE_TYPE ) { | ||
return true; | ||
} | ||
|
||
if ( ! empty( $this->excluded_files ) ) { | ||
// File is excluded from minification/concatenation. | ||
if ( preg_match( '#^(' . $this->excluded_files . ')$#', $file_path ) ) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
<?php | ||
namespace WP_Rocket\Optimization\CSS; | ||
|
||
use WP_Rocket\Admin\Options_Data as Options; | ||
use Wa72\HtmlPageDom\HtmlPageCrawler; | ||
use MatthiasMullie\Minify; | ||
|
||
/** | ||
* Minify & Combine CSS files | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
*/ | ||
class Combine extends Abstract_CSS_Optimization { | ||
use Path_Rewriter; | ||
|
||
/** | ||
* Minifier instance | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @var Minify\CSS | ||
*/ | ||
private $minifier; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @param HtmlPageCrawler $crawler Crawler instance. | ||
* @param Options $options Options instance. | ||
* @param Minify\CSS $minifier Minifier instance. | ||
*/ | ||
public function __construct( HtmlPageCrawler $crawler, Options $options, Minify\CSS $minifier ) { | ||
parent::__construct( $crawler, $options ); | ||
|
||
$this->minifier = $minifier; | ||
} | ||
|
||
/** | ||
* Combines multiple Google Fonts links into one | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @return string | ||
*/ | ||
public function optimize() { | ||
$nodes = $this->find( 'link[href*=".css"]' ); | ||
|
||
if ( ! $nodes ) { | ||
return $this->crawler->saveHTML(); | ||
} | ||
|
||
$combine_nodes = $nodes->each( function( \Wa72\HtmlPageDom\HtmlPageCrawler $node, $i ) { | ||
$src = $node->attr( 'href' ); | ||
|
||
if ( $this->is_external_file( $src ) ) { | ||
return; | ||
} | ||
|
||
if ( $this->is_minify_excluded_file( $node ) ) { | ||
return; | ||
} | ||
|
||
return $node; | ||
} ); | ||
|
||
if ( empty( $combine_nodes ) ) { | ||
return $this->crawler->saveHTML(); | ||
} | ||
|
||
$urls = array_map( function( $node ) { | ||
return $node->attr( 'href' ); | ||
}, $combine_nodes ); | ||
|
||
$minify_url = $this->combine( $urls ); | ||
|
||
if ( ! $minify_url ) { | ||
return $this->crawler->saveHTML(); | ||
} | ||
|
||
if ( ! $this->inject_combined_url( $minify_url ) ) { | ||
return $this->crawler->saveHTML(); | ||
} | ||
|
||
foreach ( $combine_nodes as $node ) { | ||
$node->remove(); | ||
} | ||
|
||
return $this->crawler->saveHTML(); | ||
} | ||
|
||
/** | ||
* Adds the combined CSS URL to the HTML | ||
* | ||
* @since 3.1 | ||
* @author Remy Perona | ||
* | ||
* @param string $minify_url URL to insert. | ||
* @return bool | ||
*/ | ||
protected function inject_combined_url( $minify_url ) { | ||
try { | ||
$this->crawler->filter( 'head' )->prepend( '<link rel="stylesheet" href="' . $minify_url . '" data-minify="1" />' ); | ||
} catch ( Exception $e ) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Creates the minify URL if the minification is successful | ||
* | ||
* @since 2.11 | ||
* @author Remy Perona | ||
* | ||
* @param string $urls Original file URL. | ||
* @return string|bool The minify URL if successful, false otherwise | ||
*/ | ||
protected function combine( $urls ) { | ||
if ( empty( $urls ) ) { | ||
return false; | ||
} | ||
|
||
foreach ( $urls as $url ) { | ||
$file_path[] = $this->get_file_path( $url ); | ||
} | ||
|
||
$file_hash = implode( ',', $urls ); | ||
$filename = md5( $file_hash . $this->minify_key ) . '.css'; | ||
|
||
$minified_file = $this->minify_base_path . $filename; | ||
|
||
if ( ! rocket_direct_filesystem()->exists( $minified_file ) ) { | ||
$minified_content = $this->minify( $file_path ); | ||
|
||
if ( ! $minified_content ) { | ||
return false; | ||
} | ||
|
||
$minify_filepath = $this->write_file( $minified_content, $minified_file ); | ||
|
||
if ( ! $minify_filepath ) { | ||
return false; | ||
} | ||
} | ||
|
||
return $this->get_minify_url( $filename ); | ||
} | ||
|
||
/** | ||
* Minifies the content | ||
* | ||
* @since 2.11 | ||
* @author Remy Perona | ||
* | ||
* @param string|array $files Files to minify. | ||
* @return string|bool Minified content, false if empty | ||
*/ | ||
protected function minify( $files ) { | ||
foreach ( $files as $file ) { | ||
$file_content = $this->get_file_content( $file ); | ||
$file_content = $this->rewrite_paths( $file, $file_content ); | ||
|
||
$this->minifier->add( $file_content ); | ||
} | ||
|
||
$minified_content = $this->minifier->minify(); | ||
|
||
if ( empty( $minified_content ) ) { | ||
return false; | ||
} | ||
|
||
return $minified_content; | ||
} | ||
} |
Oops, something went wrong.