Skip to content

Commit

Permalink
Merge pull request #1019 from wp-media/feature/combine-inline-scripts
Browse files Browse the repository at this point in the history
Minify Refactor #3: CSS minification/combine
  • Loading branch information
Screenfeed authored Jun 15, 2018
2 parents 5f7bc44 + bad5eca commit 3a250c6
Show file tree
Hide file tree
Showing 6 changed files with 729 additions and 0 deletions.
142 changes: 142 additions & 0 deletions inc/classes/optimization/CSS/class-abstract-css-optimization.php
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;
}
}
182 changes: 182 additions & 0 deletions inc/classes/optimization/CSS/class-combine.php
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;
}
}
Loading

0 comments on commit 3a250c6

Please sign in to comment.