Hollis Creek Family Dental · Repair report

Exhibit A-3 Evidence: commit d4e6502

Committed · d4e6502d495cbd52c37013f2ba3ab4adb428fa43

Scripts and styles held back the first paint

The repository itself is private; this page carries the commit as it is recorded there.

Commit message

Fix A-3: remove render-blocking scripts, the @import chain and unused font weights

Root cause: the child theme dequeued ClinicPress's stylesheets and instead
@imported the parent stylesheet from its own (a serial download chain),
loaded all six Open Sans weights without font-display (invisible text until
they arrive), and put jQuery, jQuery Migrate, four jQuery UI widgets and a
slider script for markup removed in 2021 into the <head>, synchronously.

Fix: enqueue the child stylesheet with the parent as a dependency, keep
ClinicPress's three-weight font file (font-display: swap, two preloads),
and delete the unused slider and its jQuery UI dependencies. The only
script left is the menu toggle, deferred in the footer.

Proof: tests/phpunit/repair/RenderBlockingTest.php.

Exhibit: A-3

The change

Text files only, as recorded in git. Lines starting with + were added, lines with − removed.

Files changed

---
 site/wp-content/themes/hollis-child/assets/css/legacy-fonts.css | 37 --------------------
 site/wp-content/themes/hollis-child/assets/js/legacy-slider.js  | 16 ---------
 site/wp-content/themes/hollis-child/functions.php               |  9 ++---
 site/wp-content/themes/hollis-child/style.css                   |  2 --
 tests/phpunit/repair/RenderBlockingTest.php                     | 53 +++++++++++++++++++++++++++++
 5 files changed, 56 insertions(+), 61 deletions(-)
diff --git a/site/wp-content/themes/hollis-child/assets/css/legacy-fonts.css b/site/wp-content/themes/hollis-child/assets/css/legacy-fonts.css
deleted file mode 100644
index 775b292..0000000
--- a/site/wp-content/themes/hollis-child/assets/css/legacy-fonts.css
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Every Open Sans weight the 2019 designer tried. */
-@font-face {
-	font-family: "Open Sans";
-	font-style: normal;
-	font-weight: 300;
-	src: url("../../../clinicpress/assets/fonts/open-sans-latin-300-normal.woff2") format("woff2");
-}
-@font-face {
-	font-family: "Open Sans";
-	font-style: normal;
-	font-weight: 400;
-	src: url("../../../clinicpress/assets/fonts/open-sans-latin-400-normal.woff2") format("woff2");
-}
-@font-face {
-	font-family: "Open Sans";
-	font-style: italic;
-	font-weight: 400;
-	src: url("../../../clinicpress/assets/fonts/open-sans-latin-400-italic.woff2") format("woff2");
-}
-@font-face {
-	font-family: "Open Sans";
-	font-style: normal;
-	font-weight: 600;
-	src: url("../../../clinicpress/assets/fonts/open-sans-latin-600-normal.woff2") format("woff2");
-}
-@font-face {
-	font-family: "Open Sans";
-	font-style: normal;
-	font-weight: 700;
-	src: url("../../../clinicpress/assets/fonts/open-sans-latin-700-normal.woff2") format("woff2");
-}
-@font-face {
-	font-family: "Open Sans";
-	font-style: normal;
-	font-weight: 800;
-	src: url("../../../clinicpress/assets/fonts/open-sans-latin-800-normal.woff2") format("woff2");
-}
diff --git a/site/wp-content/themes/hollis-child/assets/js/legacy-slider.js b/site/wp-content/themes/hollis-child/assets/js/legacy-slider.js
deleted file mode 100644
index a155dc9..0000000
--- a/site/wp-content/themes/hollis-child/assets/js/legacy-slider.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Homepage slider from the 2019 redesign. The slider markup was removed in 2021; the script still runs. */
-jQuery( function ( $ ) {
-	var $slides = $( '.hc-slider .slide' );
-	var current = 0;
-	$( '.hc-tabs' ).tabs && $( '.hc-tabs' ).tabs();
-	$( '.hc-accordion' ).accordion && $( '.hc-accordion' ).accordion();
-	$( '.hc-date' ).datepicker && $( '.hc-date' ).datepicker();
-	if ( ! $slides.length ) {
-		return;
-	}
-	setInterval( function () {
-		$slides.eq( current ).fadeOut( 600 );
-		current = ( current + 1 ) % $slides.length;
-		$slides.eq( current ).fadeIn( 600 );
-	}, 5000 );
-} );
diff --git a/site/wp-content/themes/hollis-child/functions.php b/site/wp-content/themes/hollis-child/functions.php
index b4e6ed9..048b342 100644
--- a/site/wp-content/themes/hollis-child/functions.php
+++ b/site/wp-content/themes/hollis-child/functions.php
@@ -19,14 +19,11 @@ function hollis_child_setup() {
 add_action( 'after_setup_theme', 'hollis_child_setup' );
 
 /**
- * Styles and scripts. (2019: the child stylesheet @imports the parent's; all font weights; slider scripts in the head.)
+ * The child stylesheet loads after the parent's, as a dependency (no @import). The parent loads the three
+ * font weights the design uses, with font-display: swap; the unused slider and jQuery UI are gone.
  */
 function hollis_child_assets() {
-	wp_dequeue_style( 'clinicpress' );
-	wp_dequeue_style( 'clinicpress-fonts' );
-	wp_enqueue_style( 'hollis-legacy-fonts', get_stylesheet_directory_uri() . '/assets/css/legacy-fonts.css' );
-	wp_enqueue_style( 'hollis-child', get_stylesheet_uri() );
-	wp_enqueue_script( 'hollis-legacy-slider', get_stylesheet_directory_uri() . '/assets/js/legacy-slider.js', array( 'jquery', 'jquery-migrate', 'jquery-ui-core', 'jquery-ui-tabs', 'jquery-ui-accordion', 'jquery-ui-datepicker' ), '1.0', false );
+	wp_enqueue_style( 'hollis-child', get_stylesheet_uri(), array( 'clinicpress' ), HOLLIS_CHILD_VERSION );
 }
 add_action( 'wp_enqueue_scripts', 'hollis_child_assets', 20 );
 
diff --git a/site/wp-content/themes/hollis-child/style.css b/site/wp-content/themes/hollis-child/style.css
index b4fe857..a98d61e 100644
--- a/site/wp-content/themes/hollis-child/style.css
+++ b/site/wp-content/themes/hollis-child/style.css
@@ -8,8 +8,6 @@ License: GPL-2.0-or-later
 Text Domain: hollis-child
 */
 
-@import url("../clinicpress/style.css");
-
 .main-nav a {
 	color: #8aa7a2;
 	font-weight: 400;
diff --git a/tests/phpunit/repair/RenderBlockingTest.php b/tests/phpunit/repair/RenderBlockingTest.php
new file mode 100644
index 0000000..71af1cd
--- /dev/null
+++ b/tests/phpunit/repair/RenderBlockingTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Exhibit A-3: nothing in the <head> holds back the first paint except the two stylesheets the page needs.
+ *
+ * @package Hollis\Tests
+ */
+
+/**
+ * Render-blocking resources.
+ */
+class RenderBlockingTest extends WP_UnitTestCase {
+
+	/**
+	 * The markup wp_head() prints on the home page.
+	 *
+	 * @return string
+	 */
+	private function head_html() {
+		$this->go_to( home_url( '/' ) );
+		ob_start();
+		wp_head();
+		return ob_get_clean();
+	}
+
+	public function test_head_has_no_synchronous_scripts() {
+		preg_match_all( '/<script\b[^>]*\ssrc=[^>]*>/', $this->head_html(), $m );
+		foreach ( $m[0] as $tag ) {
+			$this->assertMatchesRegularExpression( '/\s(defer|async)\b/', $tag, 'Blocking script in <head>: ' . $tag );
+		}
+	}
+
+	public function test_no_jquery_ui_or_slider_is_loaded() {
+		$this->head_html();
+		foreach ( wp_scripts()->queue as $handle ) {
+			$this->assertStringNotContainsString( 'jquery-ui', $handle );
+			$this->assertStringNotContainsString( 'slider', $handle );
+		}
+		$this->assertFalse( wp_script_is( 'jquery', 'enqueued' ) );
+	}
+
+	public function test_stylesheets_are_two_plus_plugins_and_the_child_does_not_import_the_parent() {
+		preg_match_all( '/<link\b[^>]*rel=[\'"]stylesheet[\'"][^>]*>/', $this->head_html(), $m );
+		$this->assertLessThanOrEqual( 5, count( $m[0] ), implode( "\n", $m[0] ) );
+		$this->assertContains( 'clinicpress', wp_styles()->registered['hollis-child']->deps );
+		$this->assertStringNotContainsString( '@import', file_get_contents( get_stylesheet_directory() . '/style.css' ) );
+	}
+
+	public function test_fonts_are_three_weights_and_swap() {
+		$css = file_get_contents( get_template_directory() . '/assets/css/fonts.css' );
+		$this->assertSame( substr_count( $css, '@font-face' ), substr_count( $css, 'font-display: swap' ) );
+		$this->assertLessThanOrEqual( 4, substr_count( $css, '@font-face' ) );
+	}
+}

The proving test

tests/phpunit/repair/RenderBlockingTest.php

tests/phpunit/repair/RenderBlockingTest.php, as committed.

<?php
/**
 * Exhibit A-3: nothing in the <head> holds back the first paint except the two stylesheets the page needs.
 *
 * @package Hollis\Tests
 */

/**
 * Render-blocking resources.
 */
class RenderBlockingTest extends WP_UnitTestCase {

	/**
	 * The markup wp_head() prints on the home page.
	 *
	 * @return string
	 */
	private function head_html() {
		$this->go_to( home_url( '/' ) );
		ob_start();
		wp_head();
		return ob_get_clean();
	}

	public function test_head_has_no_synchronous_scripts() {
		preg_match_all( '/<script\b[^>]*\ssrc=[^>]*>/', $this->head_html(), $m );
		$blocking = array_filter(
			$m[0],
			static function ( $tag ) {
				return ! preg_match( '/\s(defer|async)\b/', $tag );
			}
		);
		$this->assertSame( array(), array_values( $blocking ) );
	}

	public function test_no_jquery_ui_or_slider_is_loaded() {
		$this->head_html();
		foreach ( wp_scripts()->queue as $handle ) {
			$this->assertStringNotContainsString( 'jquery-ui', $handle );
			$this->assertStringNotContainsString( 'slider', $handle );
		}
		$this->assertFalse( wp_script_is( 'jquery', 'enqueued' ) );
	}

	public function test_stylesheets_are_two_plus_plugins_and_the_child_does_not_import_the_parent() {
		preg_match_all( '/<link\b[^>]*rel=[\'"]stylesheet[\'"][^>]*>/', $this->head_html(), $m );
		$this->assertLessThanOrEqual( 5, count( $m[0] ), implode( "\n", $m[0] ) );
		$this->assertContains( 'clinicpress', wp_styles()->registered['hollis-child']->deps );
		$this->assertStringNotContainsString( '@import', file_get_contents( get_stylesheet_directory() . '/style.css' ) );
	}

	public function test_fonts_are_three_weights_and_swap() {
		$css = file_get_contents( get_template_directory() . '/assets/css/fonts.css' );
		$this->assertSame( substr_count( $css, '@font-face' ), substr_count( $css, 'font-display: swap' ) );
		$this->assertLessThanOrEqual( 4, substr_count( $css, '@font-face' ) );
	}
}

Back to Exhibit A-3