Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #3539 Combine @import rules into the minified CSS files #3603

Merged
merged 19 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 272 additions & 1 deletion inc/Engine/Optimization/CSSTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public function rewrite_paths( $source, $target, $content ) {
*/
$target = apply_filters( 'rocket_css_asset_target_path', $target );

$content = $this->move( $this->get_converter( $source, $target ), $content, $source );

$content = $this->combine_imports( $content, $target );
remyperona marked this conversation as resolved.
Show resolved Hide resolved

/**
* Filters the content of a CSS file
*
Expand All @@ -44,7 +48,7 @@ public function rewrite_paths( $source, $target, $content ) {
* @param string $source Source filepath.
* @param string $target Target filepath.
*/
return apply_filters( 'rocket_css_content', $this->move( $this->get_converter( $source, $target ), $content, $source ), $source, $target );
return apply_filters( 'rocket_css_content', $content, $source, $target );
}

/**
Expand Down Expand Up @@ -191,6 +195,132 @@ protected function move( ConverterInterface $converter, $content, $source ) {
return str_replace( $search, $replace, $content );
}

/**
* Replace local imports with their contents recursively.
*
* @since 3.8.6
*
* @param string $content CSS Content.
* @param string $target Target CSS file path.
*
* @return string
*/
protected function combine_imports( $content, $target ) {
$import_regexes = [
// @import url(xxx)
'/
# import statement
@import

# whitespace
\s+

# open url()
url\(

# (optional) open path enclosure
(?P<quotes>["\']?)

# fetch path
(?P<path>.+?)

# (optional) close path enclosure
(?P=quotes)

# close url()
\)

# (optional) trailing whitespace
\s*

# (optional) media statement(s)
(?P<media>[^;]*)

# (optional) trailing whitespace
\s*

# (optional) closing semi-colon
;?

/ix',

// @import 'xxx'
'/

# import statement
@import

# whitespace
\s+

# open path enclosure
(?P<quotes>["\'])

# fetch path
(?P<path>.+?)

# close path enclosure
(?P=quotes)

# (optional) trailing whitespace
\s*

# (optional) media statement(s)
(?P<media>[^;]*)

# (optional) trailing whitespace
\s*

# (optional) closing semi-colon
;?

/ix',
];

// find all relative imports in css.
$matches = [];
foreach ( $import_regexes as $import_regexe ) {
if ( preg_match_all( $import_regexe, $content, $regex_matches, PREG_SET_ORDER ) ) {
$matches = array_merge( $matches, $regex_matches );
}
}
remyperona marked this conversation as resolved.
Show resolved Hide resolved

if ( empty( $matches ) ) {
return $content;
}

$search = [];
$replace = [];

// loop the matches.
foreach ( $matches as $match ) {
if ( apply_filters( 'rocket_skip_import_replacement', false, $match['path'], $match ) ) {
engahmeds3ed marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

list( $import_path, $import_content ) = $this->get_internal_file_contents( $match['path'], dirname( $target ) );

if ( empty( $import_content ) ) {
continue;
}

// check if this is only valid for certain media.
if ( ! empty( $match['media'] ) ) {
$import_content = '@media ' . $match['media'] . '{' . $import_content . '}';
}

// Use recursion to rewrite paths and combine imports again for imported content.
$import_content = $this->rewrite_paths( $import_path, $target, $import_content );

// add to replacement array.
$search[] = $match[0];
$replace[] = $import_content;
}

// replace the import statements.
return str_replace( $search, $replace, $content );
}

/**
* Applies font-display:swap to all font-family rules without a previously set font-display property.
*
Expand Down Expand Up @@ -219,4 +349,145 @@ function ( $matches ) {
$css_file_content
);
}

/**
* Get internal file full path and contents.
*
* @since 3.8.6
*
* @param string $file Internal file path (maybe external url or relative path).
* @param string $base_path Base path as reference for relative paths.
*
* @return array Array of two values ( full path, contents )
*/
private function get_internal_file_contents( $file, $base_path ) {
if ( $this->is_external_path( $file ) && wp_http_validate_url( $file ) ) {
/**
* Filter Import external urls.
*
* @since 3.8.6
*
* @param bool Allow status.
* @param string $file File path/URL.
*/
if ( (bool) apply_filters( 'rocket_allow_import_externals', false, $file ) ) {
return [ $file, rocket_direct_filesystem()->get_contents( $file ) ];
}

return [ $file, false ];
}

// Check if this file is readable or it's relative path so we add base_path at it's start.
if ( ! rocket_direct_filesystem()->is_readable( $this->get_local_path( $file ) ) ) {
$ds = rocket_get_constant( 'DIRECTORY_SEPARATOR' );
$file = $base_path . $ds . str_replace( '/', $ds, $file );
}

$import_content = rocket_direct_filesystem()->get_contents( $this->get_local_path( $file ) );

return [ $file, $import_content ];
}

/**
* Determines if the file is external.
*
* @since 3.8.6
*
* @param string $url URL of the file.
* @return bool True if external, false otherwise.
*/
protected function is_external_path( $url ) {
$file = get_rocket_parse_url( $url );

if ( empty( $file['path'] ) ) {
return true;
}

$parsed_site_url = wp_parse_url( site_url() );

if ( empty( $parsed_site_url['host'] ) ) {
return true;
}

// This filter is documented in inc/Engine/Admin/Settings/Settings.php.
$hosts = (array) apply_filters( 'rocket_cdn_hosts', [], [ 'all' ] );
$hosts[] = $parsed_site_url['host'];
$langs = get_rocket_i18n_uri();

// Get host for all langs.
foreach ( $langs as $lang ) {
$url_host = wp_parse_url( $lang, PHP_URL_HOST );

if ( ! isset( $url_host ) ) {
continue;
}

$hosts[] = $url_host;
}

$hosts = array_unique( $hosts );

if ( empty( $hosts ) ) {
return true;
}

// URL has domain and domain is part of the internal domains.
if ( ! empty( $file['host'] ) ) {
foreach ( $hosts as $host ) {
if ( false !== strpos( $url, $host ) ) {
return false;
}
}

return true;
}

return false;
}

/**
* Get local absolute path for image.
*
* @since 3.8.6
*
* @param string $url Image url.
*
* @return string Image absolute local path.
*/
private function get_local_path( $url ) {
$url = $this->normalize_url( $url );

$path = rocket_url_to_path( $url );
if ( $path ) {
return $path;
}

$relative_url = ltrim( wp_make_link_relative( $url ), '/' );
$ds = rocket_get_constant( 'DIRECTORY_SEPARATOR' );
$base_path = isset( $_SERVER['DOCUMENT_ROOT'] ) ? ( sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) . $ds ) : '';

return $base_path . str_replace( '/', $ds, $relative_url );
}

/**
* Normalize relative url to full url.
*
* @since 3.8.6
*
* @param string $url Url to be normalized.
*
* @return string Normalized url.
*/
private function normalize_url( $url ) {
$url_host = wp_parse_url( $url, PHP_URL_HOST );

if ( empty( $url_host ) ) {
$relative_url = ltrim( wp_make_link_relative( $url ), '/' );
$site_url_components = wp_parse_url( site_url( '/' ) );
return $site_url_components['scheme'] . '://' . $site_url_components['host'] . '/' . $relative_url;
}

return $url;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,31 @@
'wp-content/cache/min/1/b6dcf622d68835c7b1cd01e3cb339560.css',
'wp-content/cache/min/1/b6dcf622d68835c7b1cd01e3cb339560.css.gz',
],
'css' => '@import url(vfs://public/wp-content/themes/twentytwenty/style.css);body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
'css' => 'body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
],

'cdn_host' => [],
'cdn_url' => 'http://example.org',
'site_url' => 'http://example.org',
],

'combineCssFilesWithNestedImport' => [
'original' =>
'<html><head><title>Sample Page</title>' .
'<link rel="stylesheet" href="http://example.org/wp-content/themes/twentytwenty/style-import2.css" type="text/css" media="all">' .
'<link rel="stylesheet" href="http://example.org/wp-content/plugins/hello-dolly/style.css">' .
'<link rel="stylesheet" href="http://example.org/wp-includes/css/dashicons.min.css">' .
'</head><body></body></html>',

'expected' => [
'html' => '<html><head><title>Sample Page</title>' .
'<link rel="stylesheet" href="http://example.org/wp-content/cache/min/1/a41edef8114680bb60b530fa32be3ca5.css" media="all" data-minify="1" />' .
'</head><body></body></html>',
'files' => [
'wp-content/cache/min/1/a41edef8114680bb60b530fa32be3ca5.css',
'wp-content/cache/min/1/a41edef8114680bb60b530fa32be3ca5.css.gz',
],
'css' => '@import "http://www.google.com/style.css";.style-import-external{color:green}.style-another-import2{color:green}.style-another-import{color:red}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
],

'cdn_host' => [],
Expand All @@ -88,7 +112,7 @@
'wp-content/cache/min/1/afeb29591023f7eb6314ad594ca01138.css',
'wp-content/cache/min/1/afeb29591023f7eb6314ad594ca01138.css.gz',
],
'css' => '@import url(vfs://public/wp-content/themes/twentytwenty/style.css);body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
'css' => 'body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
],

'cdn_host' => [],
Expand Down Expand Up @@ -247,7 +271,7 @@
'wp-content/cache/min/1/074f89d1546ea3c6df831e874538e908.css',
'wp-content/cache/min/1/074f89d1546ea3c6df831e874538e908.css.gz',
],
'css' => "@import url(vfs://public/wp-content/themes/twentytwenty/style.css);@font-face{font-display:swap;font-family:'FontAwesome';src:url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.eot?v=4.7.0);src:url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.woff2?v=4.7.0) format('woff2'),url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.woff?v=4.7.0) format('woff'),url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.ttf?v=4.7.0) format('truetype'),url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:400;font-style:normal}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}@font-face{font-display:swap;font-family:Helvetica}footer{color:red}",
'css' => "@font-face{font-display:swap;font-family:'FontAwesome';src:url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.eot?v=4.7.0);src:url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.woff2?v=4.7.0) format('woff2'),url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.woff?v=4.7.0) format('woff'),url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.ttf?v=4.7.0) format('truetype'),url('https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:400;font-style:normal}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}@font-face{font-display:swap;font-family:Helvetica}body{font-family:Helvetica,Arial,sans-serif;text-align:center}footer{color:red}",
],

'cdn_host' => [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,34 @@
'wp-content/cache/min/1/5619350d0ac97fc96b40673a7e395900.css',
'wp-content/cache/min/1/5619350d0ac97fc96b40673a7e395900.css.gz',
],
'css' => '@import url(vfs://public/wp-content/themes/twentytwenty/style.css);body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
'css' => 'body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
],

'settings' => [
'minify_concatenate_css' => 1,
'cdn' => 0,
'cdn_cnames' => [],
'cdn_zone' => [],
],
],

'combineCssFilesWithNestedImport' => [
'original' =>
'<html><head><title>Sample Page</title>' .
'<link rel="stylesheet" href="http://example.org/wp-content/themes/twentytwenty/style-import2.css" type="text/css" media="all">' .
'<link rel="stylesheet" href="http://example.org/wp-content/plugins/hello-dolly/style.css">' .
'<link rel="stylesheet" href="http://example.org/wp-includes/css/dashicons.min.css">' .
'</head><body></body></html>',

'expected' => [
'html' => '<html><head><title>Sample Page</title>' .
'<link rel="stylesheet" href="http://example.org/wp-content/cache/min/1/29cb84c177f1a73204fd92c3d4dae284.css" media="all" data-minify="1" />' .
'</head><body></body></html>',
'files' => [
'wp-content/cache/min/1/29cb84c177f1a73204fd92c3d4dae284.css',
'wp-content/cache/min/1/29cb84c177f1a73204fd92c3d4dae284.css.gz',
],
'css' => '@import "http://www.google.com/style.css";.style-import-external{color:green}.style-another-import2{color:green}.style-another-import{color:red}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
],

'settings' => [
Expand Down Expand Up @@ -408,7 +435,7 @@
'wp-content/cache/min/1/77d8f7c4cbcc265ddf66e8e60dab3e7c.css',
'wp-content/cache/min/1/77d8f7c4cbcc265ddf66e8e60dab3e7c.css.gz',
],
'css' => '@import url(vfs://public/wp-content/themes/twentytwenty/style.css);body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
'css' => 'body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}body{font-family:Helvetica,Arial,sans-serif;text-align:center}',
],

'settings' => [
Expand Down
Loading