PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.11.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.11.1
2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 All 80 releases
ablocks / includes / performance / defer-js.php

defer-js.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.11.1, at includes/performance/defer-js.php

120 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Performance;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 use ABlocks\Helper;
9
10 /**
11 * Performance Suite — defer render-blocking scripts in the document <head>.
12 *
13 * Core dependencies such as `wp-hooks` and `wp-i18n` print in the head with no
14 * loading strategy, so the browser must fetch and execute them before first
15 * paint (they show up under PageSpeed "Render-blocking requests"). This module
16 * adds `defer` to a filterable set of frontend script handles so they no longer
17 * block rendering, while preserving execution order (defer scripts run in DOM
18 * order, after parsing).
19 *
20 * Opt-in via `perf_defer_js`. Scoped to a curated handle list plus aBlocks'
21 * own scripts; third-party JS is left untouched. Distinct from `perf_delay_js`,
22 * which holds scripts until the first user interaction.
23 */
24 class DeferJs {
25
26 public static function init() {
27 if ( is_admin() ) {
28 return;
29 }
30 $enabled = (bool) apply_filters(
31 'ablocks/perf/perf_defer_js',
32 (bool) Helper::get_settings( 'perf_defer_js', false )
33 );
34 if ( ! $enabled ) {
35 return;
36 }
37 // Don't defer for a logged-in editor previewing the frontend, so the
38 // editing experience is unaffected; real visitors still get it.
39 if ( is_user_logged_in() && current_user_can( 'edit_posts' )
40 && (bool) apply_filters( 'ablocks/perf/bypass_optimizations_for_editors', true ) ) {
41 return;
42 }
43 $self = new self();
44 add_filter( 'script_loader_tag', [ $self, 'defer_tag' ], 10, 3 );
45 }
46
47 /**
48 * Extra (non-aBlocks) handles to defer. Empty by default.
49 *
50 * We deliberately no longer defer the shared core utilities
51 * (`wp-hooks`/`wp-i18n`/`wp-dom-ready`/`wp-a11y`). Plain `defer` moves a head
52 * script's execution to AFTER parsing, i.e. after the non-deferred footer
53 * bundles that depend on it — so a bundle like StoreEngine's `frontend` (whose
54 * asset manifest lists `wp-hooks`/`wp-dom-ready`/`wp-i18n`) runs first and
55 * calls a not-yet-defined global (`wp.hooks.*` / `wp.domReady` / `wp.i18n.__`),
56 * throwing `… is not a function` and killing the storefront/checkout. Deferring
57 * a script that anything depends on is unsafe with this blunt string approach.
58 *
59 * aBlocks' own `ablocks-*` view scripts are leaf scripts (nothing depends on
60 * them, they expose no globals to inline code) so they remain safe to defer —
61 * see {@see should_defer}. Power users can still opt specific handles back in
62 * via this filter if their site's dependency graph allows it.
63 */
64 private function handles() {
65 return (array) apply_filters( 'ablocks/perf/defer_js_handles', [] );
66 }
67
68 /**
69 * Add `defer` to a matching script tag unless it already carries a loading
70 * strategy (defer/async) or is an inline script (no src).
71 */
72 public function defer_tag( $tag, $handle, $src ) {
73 if ( empty( $src ) ) {
74 return $tag;
75 }
76 if ( ! $this->should_defer( $handle ) ) {
77 return $tag;
78 }
79 if ( false !== strpos( $tag, ' defer' ) || false !== strpos( $tag, ' async' ) ) {
80 return $tag;
81 }
82 // Leave scripts the delay-JS module has already rewritten alone.
83 if ( false !== strpos( $tag, 'ablocks/delayed' ) ) {
84 return $tag;
85 }
86 // Never defer a script that carries inline before/after data. WordPress
87 // prints that inline as a plain (non-deferred) <script> right beside the
88 // tag, so it executes during parse — before the deferred external file
89 // runs. The canonical break is `wp-i18n`: its `wp-i18n-js-after` inline
90 // calls `wp.i18n.setLocaleData()` before the deferred i18n.js defines
91 // `wp.i18n`, throwing and taking every downstream `__()` call (checkout,
92 // storefront bundles) down with it.
93 if ( $this->has_inline_data( $handle ) ) {
94 return $tag;
95 }
96 return preg_replace( '/^<script\s/', '<script defer ', $tag, 1 );
97 }
98
99 /**
100 * Whether the handle has inline `before`/`after` script data queued, which
101 * WordPress emits as non-deferrable inline <script> tags.
102 */
103 private function has_inline_data( $handle ) {
104 $wp_scripts = wp_scripts();
105 if ( ! $wp_scripts ) {
106 return false;
107 }
108 return (bool) $wp_scripts->get_data( $handle, 'before' )
109 || (bool) $wp_scripts->get_data( $handle, 'after' );
110 }
111
112 private function should_defer( $handle ) {
113 if ( in_array( $handle, $this->handles(), true ) ) {
114 return true;
115 }
116 // aBlocks-owned frontend scripts (library + per-block view scripts).
117 return 0 === strpos( (string) $handle, 'ablocks-' );
118 }
119 }
120