diff --git a/inc/perf-asset-policy.php b/inc/perf-asset-policy.php new file mode 100644 index 0000000..da75c9a --- /dev/null +++ b/inc/perf-asset-policy.php @@ -0,0 +1,313 @@ +queue ) ) { + $elementor_post_styles = array(); + foreach ( $wp_styles->queue as $handle ) { + if ( isset( $wp_styles->registered[ $handle ] ) ) { + $src = $wp_styles->registered[ $handle ]->src; + // Detect Elementor per-post CSS by path pattern. + if ( $src && preg_match( '/uploads\/elementor\/css\/post-\d+\.css/', $src ) ) { + $elementor_post_styles[] = $handle; + } + } + } + + foreach ( $elementor_post_styles as $handle ) { + wp_dequeue_style( $handle ); + } + } + } + + // Ultimate Social Media Icons: dequeue on homepage, archives, and blog index. + if ( is_front_page() || is_home() || is_archive() ) { + $usmi_handles = apply_filters( + 'peptide_starter_perf_usmi_handles', + array( + 'SFSImainCss', + 'SFSIjqueryModernizr', + 'SFSIjqueryShuffle', + 'SFSIjqueryrandom-shuffle', + 'SFSICustomJs', + ) + ); + + foreach ( $usmi_handles as $handle ) { + wp_dequeue_style( $handle ); + wp_dequeue_script( $handle ); + } + } +} + +/** + * Slim Google Fonts to essential weights only. + * + * Rewrites fonts.googleapis.com URLs to keep only: + * - Roboto: 400, 500, 700 (no italics) + * - Roboto Slab: 400, 700 (no italics) + * + * This reduces the font CSS from ~6KB to <1KB and the font payload by ~80%. + * Filterable per family via 'peptide_starter_perf_font_weights'. + * + * @param string $src Stylesheet URL. + * @param string $handle Stylesheet handle. + * @return string Rewritten URL or original. + */ +function peptide_starter_perf_slim_google_fonts( $src, $handle ) { + if ( ! $src || false === strpos( $src, 'fonts.googleapis.com' ) ) { + return $src; + } + + // Parse the URL to get the query string. + $parsed = wp_parse_url( $src ); + if ( empty( $parsed['query'] ) ) { + return $src; + } + + // Extract family parameter. + parse_str( $parsed['query'], $query_vars ); + if ( empty( $query_vars['family'] ) ) { + return $src; + } + + $families_str = $query_vars['family']; + + // Decode the family string (it may be URL-encoded). + $families_str = rawurldecode( $families_str ); + + // Split families by + (multiple families in one request). + $families = explode( '+', $families_str ); + + // Get configured font weights from filter. + $font_weights_config = apply_filters( + 'peptide_starter_perf_font_weights', + array( + 'Roboto' => array( 400, 500, 700 ), + 'Roboto Slab' => array( 400, 700 ), + ) + ); + + $rewritten_families = array(); + foreach ( $families as $family ) { + // Clean up the family name (URL decode + trim). + $family = trim( rawurldecode( $family ) ); + + // Check if this family has a configured weight list. + $matched = false; + foreach ( $font_weights_config as $config_family => $weights ) { + if ( 0 === strcasecmp( $family, $config_family ) ) { + // Rewrite with only the specified weights. + $weights_str = implode( ',', $weights ); + $rewritten_families[] = $config_family . ':' . $weights_str; + $matched = true; + break; + } + } + + // If no config match, pass through unchanged. + if ( ! $matched ) { + $rewritten_families[] = $family; + } + } + + // If nothing changed, return original. + if ( count( $rewritten_families ) === count( $families ) ) { + // Check if any actually changed (compare raw values). + $original_str = implode( '+', $families ); + $rewritten_str = implode( '+', $rewritten_families ); + + if ( $original_str === $rewritten_str ) { + return $src; + } + } + + // Rebuild the URL with the new family parameter. + $new_query_vars = $query_vars; + $new_query_vars['family'] = implode( '+', $rewritten_families ); + $new_query_str = http_build_query( $new_query_vars ); + + // Reconstruct the full URL. + $new_src = $parsed['scheme'] . '://' . $parsed['host'] . $parsed['path'] . '?' . $new_query_str; + + return $new_src; +} + +/** + * Add preconnect hints for font servers. + * + * Reduces TLS + DNS latency for Google Fonts requests on cold cache. + * Adds preconnect for fonts.googleapis.com and fonts.gstatic.com. + * + * @param array $urls List of URLs to add hints for. + * @param string $rel The relationship type (preconnect, dns-prefetch, etc.). + * @return array Modified URL list. + */ +function peptide_starter_perf_resource_hints( $urls, $rel ) { + // Only add hints for preconnect relationships. + if ( 'preconnect' !== $rel ) { + return $urls; + } + + // Add font servers if not already present. + $font_urls = apply_filters( + 'peptide_starter_perf_font_preconnect_urls', + array( + 'https://fonts.googleapis.com', + 'https://fonts.gstatic.com', + ) + ); + + foreach ( $font_urls as $url ) { + if ( ! in_array( $url, $urls, true ) ) { + $urls[] = $url; + } + } + + return $urls; +} + +/** + * Defer the cookie-notice script to avoid render-blocking. + * + * Adds 'defer' attribute to cookie-notice JS while preserving DOM-ready order. + * This ensures the cookie banner loads without blocking initial rendering. + * + * @param string $tag The script tag HTML. + * @param string $handle The script handle. + * @param string $src The script URL (unused). + * @return string Modified script tag. + */ +function peptide_starter_perf_defer_cookie_notice( $tag, $handle, $src ) { + // Only modify the cookie-notice script. + if ( 'cookie-notice-front' !== $handle ) { + return $tag; + } + + // Add defer attribute if not already present. + if ( false === strpos( $tag, 'defer' ) ) { + // Insert defer before the closing >. + $tag = str_replace( ''; + $result = peptide_starter_perf_defer_cookie_notice( $tag, 'cookie-notice-front', 'http://example.com/cookie-notice.js' ); + + $this->assertStringContainsString( 'defer', $result ); + } + + /** + * Test: Defer not applied to other scripts. + * + * The defer filter should only affect cookie-notice-front, not other handles. + */ + public function test_defer_not_applied_to_other_scripts() { + $tag = ''; + $result = peptide_starter_perf_defer_cookie_notice( $tag, 'other-script', 'http://example.com/other.js' ); + + // Should not have defer added. + $this->assertStringNotContainsString( 'defer', $result ); + } + + /** + * Test: page_uses_elementor helper returns false for non-singular. + */ + public function test_page_uses_elementor_non_singular() { + // Go to homepage (not singular). + $this->go_to( home_url( '/' ) ); + + $result = peptide_starter_page_uses_elementor( 0 ); + $this->assertFalse( $result ); + } +}