on interaction). add_filter( 'script_loader_tag', array( Minify_Filters::class, 'delay_script_tag' ), 30, 3 ); add_action( 'wp_footer', array( Minify_Filters::class, 'print_delay_bootstrap' ), 1000 ); // script_loader_tag only fires for wp_enqueue_script()'d assets. // Analytics / pixel / chat-widget tags printed straight into // wp_head bypass it, and those are usually the heaviest scripts // on the page — so sweep the finished buffer too. Runs before // minify_html (same filter, default priority) and is baked into // the cache file, so it replays on static hits where PHP never // boots. add_filter( 'xspeed_cache_final_html', array( Minify_Filters::class, 'delay_raw_script_tags' ), 20 ); } if ( ! empty( $opts['async_css'] ) ) { add_filter( 'style_loader_tag', array( Minify_Filters::class, 'async_style_tag' ), 20, 2 ); } /* * CSS combining runs on the FINISHED HTML, not the enqueue queue. * * The queue-walking version could not be made correct: whatever it * wrote at priority 999, WordPress edited afterwards. Core's * wp_maybe_inline_styles() inlines any queued handle carrying a `path` * and sets src=false on it, which silently threw away the combined URL * and took the sheets we had blanked with it — six stylesheets became * one and the site rendered unstyled. See Css_Combine_Buffer's header * for the full trace. (#195) * * Two entry points, because the page cache's filter is not always * available: `xspeed_cache_final_html` fires only on a cacheable MISS, * so on a site with the cache off — or on an excluded URL like /cart — * combining would silently stop working. Css_Combine_Buffer::boot() * opens its own buffer in exactly those cases and no-ops otherwise, so * the page is transformed once either way. */ if ( ! empty( $opts['combine_css'] ) ) { add_filter( 'xspeed_cache_final_html', array( Css_Combine_Buffer::class, 'process' ), 5 ); Css_Combine_Buffer::boot(); } if ( ! empty( $opts['combine_js'] ) ) { // JS stays on the enqueue path for now: dependency order, // async/defer and wp_add_inline_script make it a different // problem, and the reported break is CSS-only. Moving it is worth // its own change rather than doubling the blast radius here. add_action( 'wp_enqueue_scripts', array( Asset_Combiner::class, 'combine_scripts' ), 999 ); } } /** * HTML elements that participate in an inline formatting context, where * whitespace between two of them renders as a visible space. * * Deliberately excludes
(nothing to separate) and replaced/embedded * inline elements that sit alone. Anything not listed is treated as block * level, where inter-tag whitespace collapses to nothing and is safe to * strip. (FBS-84090) */ private const INLINE_TAGS = array( 'a', 'abbr', 'b', 'bdi', 'bdo', 'cite', 'code', 'data', 'del', 'dfn', 'em', 'i', 'ins', 'kbd', 'label', 'mark', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr', 'img', 'button', 'select', 'output', ); /** True when $tag renders inline, so whitespace beside it is visible. */ private static function is_inline( string $tag ): bool { return in_array( strtolower( $tag ), self::INLINE_TAGS, true ); } /** * Why minification is being skipped, when it is. '' when it will run. * * minify_html can read "on" in every settings surface while producing * byte-identical HTML, because the guard below silently returns the * input. Field report: a live site showed `minify_html: on` with 3,856 * indented lines in the delivered HTML and nothing anywhere explaining * the contradiction — the setting looked broken rather than suppressed. * Callers that report status MUST consult this so the refusal is * visible. (Same class as Cache::static_rewrite_block_reason().) * * @return string 'wp_debug', 'filter', or ''. */ public static function skip_reason(): string { $debug_skip = defined( 'WP_DEBUG' ) && WP_DEBUG; if ( ! apply_filters( 'xspeed_skip_minify', $debug_skip ) ) { return ''; } // Distinguish the built-in WP_DEBUG rule from a third party // filtering the escape hatch — the fixes are different. return $debug_skip ? 'wp_debug' : 'filter'; } public static function minify_html( $html ) { if ( '' !== self::skip_reason() ) { return $html; } $placeholders = array(); $pattern = '#<(pre|textarea|script|style)\b[^>]*>.*?#is'; $html = preg_replace_callback( $pattern, function ( $m ) use ( &$placeholders ) { $key = '__XSPEED_PH_' . count( $placeholders ) . '__'; // The placeholder pass exists to protect content whose // whitespace is significant (
,