| 1 |
<?php |
| 2 |
namespace ABlocks\Performance; |
| 3 |
|
| 4 |
if ( ! defined( 'ABSPATH' ) ) { |
| 5 |
exit; |
| 6 |
} |
| 7 |
|
| 8 |
use ABlocks\Helper; |
| 9 |
|
| 10 |
/** |
| 11 |
* Performance Suite — delay JavaScript until the first user interaction. |
| 12 |
* |
| 13 |
* The aBlocks combined frontend script is emitted in a non-executing form and |
| 14 |
* swapped in on the first interaction (scroll/key/pointer/touch) or after a |
| 15 |
* fallback timeout, cutting main-thread work during initial load (INP/TBT). |
| 16 |
* |
| 17 |
* Opt-in via `perf_delay_js`. The handles affected are filterable so it stays |
| 18 |
* scoped to aBlocks scripts (third-party JS is never touched). |
| 19 |
*/ |
| 20 |
class DelayJs { |
| 21 |
|
| 22 |
public static function init() { |
| 23 |
if ( is_admin() ) { |
| 24 |
return; |
| 25 |
} |
| 26 |
$enabled = (bool) apply_filters( |
| 27 |
'ablocks/perf/perf_delay_js', |
| 28 |
(bool) Helper::get_settings( 'perf_delay_js', false ) |
| 29 |
); |
| 30 |
if ( ! $enabled ) { |
| 31 |
return; |
| 32 |
} |
| 33 |
// Never delay scripts for a logged-in editor viewing the frontend, so |
| 34 |
// building/previewing the site is unaffected; real visitors still get it. |
| 35 |
if ( is_user_logged_in() && current_user_can( 'edit_posts' ) |
| 36 |
&& (bool) apply_filters( 'ablocks/perf/bypass_optimizations_for_editors', true ) ) { |
| 37 |
return; |
| 38 |
} |
| 39 |
$self = new self(); |
| 40 |
// The tag rewriting is shared with consent gating; see ScriptGate. |
| 41 |
( new ScriptGate( |
| 42 |
'ablocks/delayed', |
| 43 |
function ( $handle ) use ( $self ) { |
| 44 |
return $self->should_delay( $handle ); |
| 45 |
}, |
| 46 |
null, |
| 47 |
true |
| 48 |
) )->hook( 20 ); |
| 49 |
add_action( 'wp_footer', [ $self, 'print_loader' ], 99 ); |
| 50 |
} |
| 51 |
|
| 52 |
private function handles() { |
| 53 |
return (array) apply_filters( |
| 54 |
'ablocks/perf/delay_js_handles', |
| 55 |
[ 'ablocks-blocks-combine-script' ] |
| 56 |
); |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Whether a script handle should be delayed. Covers the combined per-page |
| 61 |
* script (assets-generation on) and the per-block frontend scripts |
| 62 |
* (assets-generation off), so delay works in both modes. |
| 63 |
* |
| 64 |
* @param string $handle Script handle. |
| 65 |
* @return bool |
| 66 |
*/ |
| 67 |
public function should_delay( $handle ) { |
| 68 |
if ( in_array( $handle, $this->handles(), true ) ) { |
| 69 |
return true; |
| 70 |
} |
| 71 |
return (bool) preg_match( '/^ablocks-.+-block-static-script$/', (string) $handle ); |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* Print the tiny vanilla loader that activates delayed scripts on interaction. |
| 76 |
*/ |
| 77 |
public function print_loader() { |
| 78 |
$timeout = (int) Helper::get_settings( 'perf_delay_js_timeout', 5000 ); |
| 79 |
$timeout = max( 0, $timeout ); |
| 80 |
?> |
| 81 |
<script id="ablocks-delay-js-loader"> |
| 82 |
( function () { |
| 83 |
var loaded = false; |
| 84 |
var events = [ 'scroll', 'keydown', 'pointerdown', 'touchstart', 'mousemove' ]; |
| 85 |
function load() { |
| 86 |
if ( loaded ) { return; } |
| 87 |
loaded = true; |
| 88 |
events.forEach( function ( e ) { window.removeEventListener( e, load ); } ); |
| 89 |
document.querySelectorAll( 'script[type="ablocks/delayed"]' ).forEach( function ( s ) { |
| 90 |
var n = document.createElement( 'script' ); |
| 91 |
var src = s.getAttribute( 'data-ablocks-src' ); |
| 92 |
if ( src ) { n.src = src; } |
| 93 |
if ( s.id ) { n.id = s.id + '-delayed'; } |
| 94 |
document.body.appendChild( n ); |
| 95 |
} ); |
| 96 |
} |
| 97 |
events.forEach( function ( e ) { window.addEventListener( e, load, { passive: true, once: true } ); } ); |
| 98 |
<?php if ( $timeout > 0 ) : ?> |
| 99 |
setTimeout( load, <?php echo (int) $timeout; ?> ); |
| 100 |
<?php endif; ?> |
| 101 |
} )(); |
| 102 |
</script> |
| 103 |
<?php |
| 104 |
} |
| 105 |
} |
| 106 |
|