-
Notifications
You must be signed in to change notification settings - Fork 128
Use HTML Tag Processor to audit blocking scripts & styles in Site Health’s enqueued-assets test #2059
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
base: trunk
Are you sure you want to change the base?
Use HTML Tag Processor to audit blocking scripts & styles in Site Health’s enqueued-assets test #2059
Changes from all commits
1e60c9a
8baf9af
0f4b4ba
4a37fff
8e5b9dc
d53ad9b
3bdae33
2846222
668808a
40377b8
092ef31
84692cf
f341208
12d1cc7
e58e451
f83151b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -33,7 +33,7 @@ function perflab_aea_enqueued_js_assets_test(): array { | |||||||||
} | ||||||||||
|
||||||||||
$result = array( | ||||||||||
'label' => __( 'Enqueued scripts', 'performance-lab' ), | ||||||||||
'label' => __( 'Blocking scripts', 'performance-lab' ), | ||||||||||
'status' => 'good', | ||||||||||
'badge' => array( | ||||||||||
'label' => __( 'Performance', 'performance-lab' ), | ||||||||||
|
@@ -45,8 +45,8 @@ function perflab_aea_enqueued_js_assets_test(): array { | |||||||||
sprintf( | ||||||||||
/* translators: 1: Number of enqueued styles. 2.Styles size. */ | ||||||||||
_n( | ||||||||||
'The amount of %1$s enqueued script (size: %2$s) is acceptable.', | ||||||||||
'The amount of %1$s enqueued scripts (size: %2$s) is acceptable.', | ||||||||||
'The amount of %1$s blocking script (size: %2$s) is acceptable.', | ||||||||||
'The amount of %1$s blocking scripts (size: %2$s) is acceptable.', | ||||||||||
$enqueued_scripts, | ||||||||||
'performance-lab' | ||||||||||
), | ||||||||||
|
@@ -86,8 +86,8 @@ function perflab_aea_enqueued_js_assets_test(): array { | |||||||||
sprintf( | ||||||||||
/* translators: 1: Number of enqueued styles. 2.Styles size. */ | ||||||||||
_n( | ||||||||||
'Your website enqueues %1$s script (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
'Your website enqueues %1$s scripts (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
'Your website has %1$s blocking script (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
'Your website has %1$s blocking scripts (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
$enqueued_scripts, | ||||||||||
'performance-lab' | ||||||||||
), | ||||||||||
|
@@ -128,7 +128,7 @@ function perflab_aea_enqueued_css_assets_test(): array { | |||||||||
return array( 'omitted' => true ); | ||||||||||
} | ||||||||||
$result = array( | ||||||||||
'label' => __( 'Enqueued styles', 'performance-lab' ), | ||||||||||
'label' => __( 'Blocking styles', 'performance-lab' ), | ||||||||||
'status' => 'good', | ||||||||||
'badge' => array( | ||||||||||
'label' => __( 'Performance', 'performance-lab' ), | ||||||||||
|
@@ -140,8 +140,8 @@ function perflab_aea_enqueued_css_assets_test(): array { | |||||||||
sprintf( | ||||||||||
/* translators: 1: Number of enqueued styles. 2.Styles size. */ | ||||||||||
_n( | ||||||||||
'The amount of %1$s enqueued style (size: %2$s) is acceptable.', | ||||||||||
'The amount of %1$s enqueued styles (size: %2$s) is acceptable.', | ||||||||||
'The amount of %1$s blocking style (size: %2$s) is acceptable.', | ||||||||||
'The amount of %1$s blocking styles (size: %2$s) is acceptable.', | ||||||||||
$enqueued_styles, | ||||||||||
'performance-lab' | ||||||||||
), | ||||||||||
|
@@ -180,8 +180,8 @@ function perflab_aea_enqueued_css_assets_test(): array { | |||||||||
sprintf( | ||||||||||
/* translators: 1: Number of enqueued styles. 2.Styles size. */ | ||||||||||
_n( | ||||||||||
'Your website enqueues %1$s style (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
'Your website enqueues %1$s styles (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
'Your website has %1$s blocking style (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
'Your website has %1$s blocking styles (size: %2$s). Try to reduce the number or to concatenate them.', | ||||||||||
$enqueued_styles, | ||||||||||
'performance-lab' | ||||||||||
), | ||||||||||
|
@@ -212,10 +212,10 @@ function perflab_aea_enqueued_css_assets_test(): array { | |||||||||
* @return int|false Number of total scripts or false if transient hasn't been set. | ||||||||||
*/ | ||||||||||
function perflab_aea_get_total_enqueued_scripts() { | ||||||||||
$enqueued_scripts = false; | ||||||||||
$list_enqueued_scripts = get_transient( 'aea_enqueued_front_page_scripts' ); | ||||||||||
if ( is_array( $list_enqueued_scripts ) ) { | ||||||||||
$enqueued_scripts = count( $list_enqueued_scripts ); | ||||||||||
$enqueued_scripts = false; | ||||||||||
$blocking_assets = get_transient( 'aea_blocking_assets' ); | ||||||||||
if ( is_array( $blocking_assets ) && is_array( $blocking_assets['scripts'] ) ) { | ||||||||||
$enqueued_scripts = count( $blocking_assets['scripts'] ); | ||||||||||
} | ||||||||||
return $enqueued_scripts; | ||||||||||
} | ||||||||||
|
@@ -228,11 +228,11 @@ function perflab_aea_get_total_enqueued_scripts() { | |||||||||
* @return int|false Byte Total size or false if transient hasn't been set. | ||||||||||
*/ | ||||||||||
function perflab_aea_get_total_size_bytes_enqueued_scripts() { | ||||||||||
$total_size = false; | ||||||||||
$list_enqueued_scripts = get_transient( 'aea_enqueued_front_page_scripts' ); | ||||||||||
if ( is_array( $list_enqueued_scripts ) ) { | ||||||||||
$total_size = false; | ||||||||||
$blocking_assets = get_transient( 'aea_blocking_assets' ); | ||||||||||
if ( is_array( $blocking_assets ) && isset( $blocking_assets['scripts'] ) && is_array( $blocking_assets['scripts'] ) ) { | ||||||||||
$total_size = 0; | ||||||||||
foreach ( $list_enqueued_scripts as $enqueued_script ) { | ||||||||||
foreach ( $blocking_assets['scripts'] as $enqueued_script ) { | ||||||||||
if ( is_array( $enqueued_script ) && array_key_exists( 'size', $enqueued_script ) && is_int( $enqueued_script['size'] ) ) { | ||||||||||
$total_size += $enqueued_script['size']; | ||||||||||
} | ||||||||||
|
@@ -249,10 +249,10 @@ function perflab_aea_get_total_size_bytes_enqueued_scripts() { | |||||||||
* @return int|false Number of total styles or false if transient hasn't been set. | ||||||||||
*/ | ||||||||||
function perflab_aea_get_total_enqueued_styles() { | ||||||||||
$enqueued_styles = false; | ||||||||||
$list_enqueued_styles = get_transient( 'aea_enqueued_front_page_styles' ); | ||||||||||
if ( is_array( $list_enqueued_styles ) ) { | ||||||||||
$enqueued_styles = count( $list_enqueued_styles ); | ||||||||||
$enqueued_styles = false; | ||||||||||
$blocking_assets = get_transient( 'aea_blocking_assets' ); | ||||||||||
if ( is_array( $blocking_assets ) && isset( $blocking_assets['styles'] ) && is_array( $blocking_assets['styles'] ) ) { | ||||||||||
$enqueued_styles = count( $blocking_assets['styles'] ); | ||||||||||
} | ||||||||||
return $enqueued_styles; | ||||||||||
} | ||||||||||
|
@@ -265,11 +265,11 @@ function perflab_aea_get_total_enqueued_styles() { | |||||||||
* @return int|false Byte Total size or false if transient hasn't been set. | ||||||||||
*/ | ||||||||||
function perflab_aea_get_total_size_bytes_enqueued_styles() { | ||||||||||
$total_size = false; | ||||||||||
$list_enqueued_styles = get_transient( 'aea_enqueued_front_page_styles' ); | ||||||||||
if ( is_array( $list_enqueued_styles ) ) { | ||||||||||
$total_size = false; | ||||||||||
$blocking_assets = get_transient( 'aea_blocking_assets' ); | ||||||||||
if ( is_array( $blocking_assets ) && isset( $blocking_assets['styles'] ) && is_array( $blocking_assets['styles'] ) ) { | ||||||||||
$total_size = 0; | ||||||||||
foreach ( $list_enqueued_styles as $enqueued_style ) { | ||||||||||
foreach ( $blocking_assets['styles'] as $enqueued_style ) { | ||||||||||
if ( is_array( $enqueued_style ) && array_key_exists( 'size', $enqueued_style ) && is_int( $enqueued_style['size'] ) ) { | ||||||||||
$total_size += $enqueued_style['size']; | ||||||||||
} | ||||||||||
|
@@ -293,6 +293,11 @@ function perflab_aea_get_path_from_resource_url( string $resource_url ): string | |||||||||
return ''; | ||||||||||
} | ||||||||||
|
||||||||||
// Remove query string if present. | ||||||||||
if ( false !== strpos( $resource_url, '?' ) ) { | ||||||||||
$resource_url = substr( $resource_url, 0, strpos( $resource_url, '?' ) ); | ||||||||||
} | ||||||||||
Comment on lines
+297
to
+299
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Nevertheless, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes its not being used anymore I just put comment about it here #2059 (comment). |
||||||||||
|
||||||||||
// Different content folder ex. /content/. | ||||||||||
if ( 0 === strpos( $resource_url, content_url() ) ) { | ||||||||||
return WP_CONTENT_DIR . substr( $resource_url, strlen( content_url() ) ); | ||||||||||
|
@@ -307,3 +312,28 @@ function perflab_aea_get_path_from_resource_url( string $resource_url ): string | |||||||||
// Standard wp-content configuration. | ||||||||||
return untrailingslashit( ABSPATH ) . wp_make_link_relative( $resource_url ); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* Gets the content length of an asset. | ||||||||||
* | ||||||||||
* @since n.e.x.t | ||||||||||
* | ||||||||||
* @param string $resource_url URL of the resource. | ||||||||||
* @return int|null Returns the content length in bytes or null if it cannot be determined. | ||||||||||
*/ | ||||||||||
function perflab_aea_get_asset_content_length( string $resource_url ): ?int { | ||||||||||
$head_response = wp_remote_head( $resource_url, array( 'timeout' => 10 ) ); | ||||||||||
if ( is_wp_error( $head_response ) || 200 !== wp_remote_retrieve_response_code( $head_response ) ) { | ||||||||||
return null; | ||||||||||
} | ||||||||||
|
||||||||||
$content_length = wp_remote_retrieve_header( $head_response, 'content-length' ); | ||||||||||
if ( is_array( $content_length ) && isset( $content_length[0] ) ) { | ||||||||||
$content_length = $content_length[0]; | ||||||||||
} | ||||||||||
$content_length = (int) $content_length; | ||||||||||
if ( $content_length <= 0 ) { | ||||||||||
return null; | ||||||||||
} | ||||||||||
return $content_length; | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,107 +13,104 @@ | |
// @codeCoverageIgnoreEnd | ||
|
||
/** | ||
* Audit enqueued and printed scripts in is_front_page(). Ignore /wp-includes scripts. | ||
* Audit blocking assets on the front page. | ||
* | ||
* It will save information in a transient for 12 hours. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @global WP_Scripts $wp_scripts | ||
* @since n.e.x.t | ||
*/ | ||
function perflab_aea_audit_enqueued_scripts(): void { | ||
if ( ! is_admin() && is_front_page() && current_user_can( 'view_site_health_checks' ) && false === get_transient( 'aea_enqueued_front_page_scripts' ) ) { | ||
global $wp_scripts; | ||
$enqueued_scripts = array(); | ||
function perflab_aea_audit_blocking_assets(): void { | ||
if ( | ||
! is_admin() || | ||
! current_user_can( 'view_site_health_checks' ) || | ||
false !== get_transient( 'aea_blocking_assets' ) | ||
) { | ||
return; | ||
} | ||
|
||
$response = wp_remote_get( | ||
home_url( '/' ), | ||
array( | ||
'timeout' => 10, | ||
'headers' => array( | ||
'Accept' => 'text/html', | ||
'Cache-Control' => 'no-cache', | ||
), | ||
Comment on lines
+33
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the site has HTTP Basic auth, then these can be copied in the request headers. This is done in core in the plugin/theme file editors. |
||
) | ||
); | ||
|
||
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { | ||
return; | ||
} | ||
|
||
foreach ( $wp_scripts->done as $handle ) { | ||
$script = $wp_scripts->registered[ $handle ]; | ||
$html = wp_remote_retrieve_body( $response ); | ||
if ( '' === $html ) { | ||
return; | ||
} | ||
|
||
$assets = array( | ||
'scripts' => array(), | ||
'styles' => array(), | ||
); | ||
|
||
$processor = new WP_HTML_Tag_Processor( $html ); | ||
|
||
if ( ! $script->src || false !== strpos( $script->src, 'wp-includes' ) ) { | ||
while ( $processor->next_tag() ) { | ||
$tag = $processor->get_tag(); | ||
|
||
if ( 'SCRIPT' === $tag ) { | ||
$src = $processor->get_attribute( 'src' ); | ||
if ( ! is_string( $src ) ) { | ||
continue; | ||
} | ||
|
||
// Add any extra data (inlined) that was passed with the script. | ||
$inline_size = 0; | ||
if ( | ||
isset( $script->extra['after'] ) && | ||
is_array( $script->extra['after'] ) | ||
) { | ||
foreach ( $script->extra['after'] as $extra ) { | ||
$inline_size += ( is_string( $extra ) ) ? mb_strlen( $extra, '8bit' ) : 0; | ||
} | ||
} | ||
// Note that when the "type" attribute is absent or empty, the element is treated as a classic JavaScript script. | ||
$type = $processor->get_attribute( 'type' ); | ||
|
||
$path = perflab_aea_get_path_from_resource_url( $script->src ); | ||
if ( '' === $path ) { | ||
// Skip external script with "async" or "defer" attributes. | ||
if ( null !== $processor->get_attribute( 'async' ) || null !== $processor->get_attribute( 'defer' ) ) { | ||
continue; | ||
} | ||
|
||
$enqueued_scripts[] = array( | ||
'src' => $script->src, | ||
'size' => wp_filesize( $path ) + $inline_size, | ||
); | ||
|
||
} | ||
set_transient( 'aea_enqueued_front_page_scripts', $enqueued_scripts, 12 * HOUR_IN_SECONDS ); | ||
} | ||
} | ||
add_action( 'wp_footer', 'perflab_aea_audit_enqueued_scripts', PHP_INT_MAX ); | ||
// Skip external script with a "type" attribute set to "module" as they are deferred by default. | ||
if ( 'module' === strtolower( (string) $type ) ) { | ||
continue; | ||
} | ||
|
||
/** | ||
* Audit enqueued and printed styles in the frontend. Ignore /wp-includes styles. | ||
* | ||
* It will save information in a transient for 12 hours. | ||
* | ||
* @since 1.0.0 | ||
* | ||
* @global WP_Styles $wp_styles The WP_Styles current instance. | ||
*/ | ||
function perflab_aea_audit_enqueued_styles(): void { | ||
if ( ! is_admin() && is_front_page() && current_user_can( 'view_site_health_checks' ) && false === get_transient( 'aea_enqueued_front_page_styles' ) ) { | ||
global $wp_styles; | ||
$enqueued_styles = array(); | ||
foreach ( $wp_styles->done as $handle ) { | ||
$style = $wp_styles->registered[ $handle ]; | ||
|
||
if ( ! $style->src || false !== strpos( $style->src, 'wp-includes' ) ) { | ||
// Skip external script with a "type" attribute that is not JavaScript. | ||
if ( '' !== $type && 'text/javascript' !== strtolower( (string) $type ) ) { | ||
continue; | ||
} | ||
|
||
// Check if we already have the style's path ( part of a refactor for block styles from 5.9 ). | ||
if ( | ||
isset( $style->extra['path'] ) && | ||
is_string( $style->extra['path'] ) && | ||
'' !== $style->extra['path'] | ||
) { | ||
$path = $style->extra['path']; | ||
} else { // Fallback to getting the path from the style's src. | ||
$path = perflab_aea_get_path_from_resource_url( $style->src ); | ||
if ( '' === $path ) { | ||
continue; | ||
} | ||
$size = perflab_aea_get_asset_content_length( $src ); | ||
if ( null !== $size ) { | ||
$assets['scripts'][] = array( | ||
'src' => $src, | ||
'size' => $size, | ||
); | ||
} | ||
} elseif ( 'LINK' === $tag ) { | ||
$rel = $processor->get_attribute( 'rel' ); | ||
if ( 'stylesheet' !== strtolower( (string) $rel ) ) { | ||
continue; | ||
} | ||
|
||
// Add any extra data (inlined) that was passed with the style. | ||
$inline_size = 0; | ||
if ( | ||
isset( $style->extra['after'] ) && | ||
is_array( $style->extra['after'] ) | ||
) { | ||
foreach ( $style->extra['after'] as $extra ) { | ||
$inline_size += ( is_string( $extra ) ) ? mb_strlen( $extra, '8bit' ) : 0; | ||
} | ||
$href = $processor->get_attribute( 'href' ); | ||
if ( ! is_string( $href ) ) { | ||
continue; | ||
} | ||
|
||
$enqueued_styles[] = array( | ||
'src' => $style->src, | ||
'size' => wp_filesize( $path ) + $inline_size, | ||
); | ||
$size = perflab_aea_get_asset_content_length( $href ); | ||
if ( null !== $size ) { | ||
$assets['styles'][] = array( | ||
'src' => $href, | ||
'size' => $size, | ||
); | ||
} | ||
} | ||
set_transient( 'aea_enqueued_front_page_styles', $enqueued_styles, 12 * HOUR_IN_SECONDS ); | ||
} | ||
|
||
set_transient( 'aea_blocking_assets', $assets, 12 * HOUR_IN_SECONDS ); | ||
} | ||
add_action( 'wp_footer', 'perflab_aea_audit_enqueued_styles', PHP_INT_MAX ); | ||
add_action( 'admin_init', 'perflab_aea_audit_blocking_assets' ); | ||
|
||
/** | ||
* Adds tests to site health. | ||
|
@@ -158,6 +155,8 @@ function perflab_aea_clean_aea_audit_action(): void { | |
* @since 1.0.0 | ||
*/ | ||
function perflab_aea_invalidate_cache_transients(): void { | ||
delete_transient( 'aea_blocking_assets' ); | ||
// Keeping legacy transients deletion for backward compatibility. | ||
delete_transient( 'aea_enqueued_front_page_scripts' ); | ||
delete_transient( 'aea_enqueued_front_page_styles' ); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to remove the function
perflab_aea_get_path_from_resource_url
entirely, as a HEAD request is now made for local assets as well?performance/plugins/performance-lab/includes/site-health/audit-enqueued-assets/helper.php
Lines 281 to 291 in 8856a14
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the function can be removed.