PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.0
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.13.0, at includes/performance/defer-js.php

126 lines 4.4 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 has a blocking inline `after` script. That
87 // inline runs synchronously while the document parses, so deferring the
88 // external src makes the inline execute BEFORE the library it depends on.
89 // wp-i18n is the canonical case: core prints
90 // `wp.i18n.setLocaleData( … )` as wp-i18n's inline `after`, and deferring
91 // wp-i18n leaves `wp.i18n` undefined for the whole page (breaking every
92 // script that calls `wp.i18n.__`). This mirrors WordPress core, whose own
93 // strategy API declares such scripts ineligible for defer/async.
94 if ( $this->has_blocking_inline( $handle ) ) {
95 return $tag;
96 }
97 return preg_replace( '/^<script\s/', '<script defer ', $tag, 1 );
98 }
99
100 /**
101 * Whether a registered script carries an inline `after` script — which must
102 * run synchronously right after the external file and therefore blocks safe
103 * deferral of that file.
104 *
105 * @param string $handle
106 *
107 * @return bool
108 */
109 private function has_blocking_inline( $handle ) {
110 $scripts = wp_scripts();
111 if ( ! $scripts ) {
112 return false;
113 }
114
115 return ! empty( $scripts->get_data( $handle, 'after' ) );
116 }
117
118 private function should_defer( $handle ) {
119 if ( in_array( $handle, $this->handles(), true ) ) {
120 return true;
121 }
122 // aBlocks-owned frontend scripts (library + per-block view scripts).
123 return 0 === strpos( (string) $handle, 'ablocks-' );
124 }
125 }
126