PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.0.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.0.7
1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 1.2.0 All 28 releases
xspeed / includes / modules / Lazy / LazyModule.php

LazyModule.php in xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN 1.0.7, at includes/modules/Lazy/LazyModule.php

163 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Lazy module — defers img / iframe / video loading via native browser
4 * lazy-load attributes. Also auto-fills missing image dimensions to
5 * prevent CLS.
6 *
7 * What WordPress core does already (since 5.5):
8 * - Adds loading="lazy" to the_content images.
9 *
10 * What this module adds:
11 * - First N images get loading="eager" so the LCP isn't deferred.
12 * - Adds decoding="async" (core doesn't).
13 * - Lazy-loads iframes (core's iframe lazy was reverted).
14 * - preload="none" on <video> (closest thing to native video lazy).
15 * - Auto-fills missing width / height attributes (best CLS win).
16 * - Excludes by substring patterns (src or class match) — useful for
17 * hero banner classes, logo files, etc.
18 *
19 * Tier: Free per FEATURES.md "Images" §1-6 (LiteSpeed parity — all
20 * Free in LS Cache).
21 *
22 * @package XSpeed
23 */
24
25 declare(strict_types=1);
26
27 namespace XSpeed\Modules\Lazy;
28
29 defined( 'ABSPATH' ) || exit;
30
31 use XSpeed\Lazy_Loader;
32 use XSpeed\Module;
33
34 final class LazyModule extends Module {
35
36 public const SLUG = 'lazy';
37 public const TIER = self::TIER_FREE;
38 public const VERSION = '1.0.0';
39
40 public function ui_metadata(): array {
41 return array(
42 'label' => 'Lazy Load',
43 'icon' => 'Image',
44 'description' => 'Defer images / iframes / videos until they scroll into view, and auto-add missing dimensions to prevent CLS.',
45 );
46 }
47
48 public function settings_schema(): array {
49 return array(
50 'lazy_images' => array(
51 'type' => 'bool',
52 'default' => true,
53 'label' => 'Lazy-load Images',
54 'description' => 'Add loading="lazy" + decoding="async" to <img> tags in post content. The first images on the page get loading="eager" so the LCP image is not deferred.',
55 ),
56 'lazy_iframes' => array(
57 'type' => 'bool',
58 'default' => true,
59 'label' => 'Lazy-load Iframes',
60 'description' => 'Add loading="lazy" to <iframe> tags. Useful for YouTube / Vimeo embeds + map widgets that pull a lot of bytes.',
61 ),
62 'lazy_videos' => array(
63 'type' => 'bool',
64 'default' => true,
65 'label' => 'Lazy-load HTML5 Videos',
66 'description' => 'Set preload="none" on self-hosted <video> tags. Browsers do not yet support loading="lazy" on video; preload="none" is the closest equivalent.',
67 ),
68 'eager_first_n' => array(
69 'type' => 'int',
70 'default' => 1,
71 'min' => 0,
72 'max' => 10,
73 'label' => 'Eager-load First N Images',
74 'description' => 'How many images at the top of the post get loading="eager". 1 is usually right (the LCP hero image). 0 to lazy-load everything.',
75 ),
76 'add_missing_dimensions' => array(
77 'type' => 'bool',
78 'default' => true,
79 'label' => 'Add Missing Image Dimensions',
80 'description' => 'When an <img class="wp-image-N"> has no width/height, look the values up from the media library and inject them. Prevents the page-layout shift that hurts CLS scores.',
81 ),
82 'excluded_images' => array(
83 'type' => 'list',
84 'default' => array(),
85 'item_type' => 'string',
86 'label' => 'Excluded Images',
87 'description' => 'Substring patterns that, if found anywhere in the <img> / <iframe> tag (typically a class or filename), exempt that element from lazy-loading. Useful for hero / logo / sprite images. Or add data-skip-lazy to the tag directly.',
88 ),
89 );
90 }
91
92 public function conflicts(): array {
93 return array(
94 array(
95 'plugin' => 'wp-smushit/wp-smush.php',
96 'feature' => 'images.lazyload',
97 'strategy' => \XSpeed\Conflict_Registry::STRATEGY_WARN,
98 'reason' => 'Smush also offers lazy-loading; running both can cause double-rewriting.',
99 ),
100 array(
101 'plugin' => 'a3-lazy-load/a3-lazy-load.php',
102 'feature' => 'images.lazyload',
103 'strategy' => \XSpeed\Conflict_Registry::STRATEGY_REFUSE,
104 'reason' => 'a3 Lazy Load is a dedicated lazy-load plugin; disable it before enabling xSpeed lazy-load.',
105 ),
106 );
107 }
108
109 public function boot(): void {
110 // Bail entirely on admin / feed / cron / REST — same scope as
111 // Minifier. Lazy-loading rendered HTML only matters on real
112 // frontend page renders.
113 if ( is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
114 return;
115 }
116
117 $opts = $this->get_settings();
118 $any_enabled = ! empty( $opts['lazy_images'] )
119 || ! empty( $opts['lazy_iframes'] )
120 || ! empty( $opts['lazy_videos'] )
121 || ! empty( $opts['add_missing_dimensions'] );
122 if ( ! $any_enabled ) {
123 return;
124 }
125
126 // Reset the eager-load budget once per page render, before any
127 // content filter runs, so the "first N images eager" budget is
128 // shared across the featured image + content + avatars rather than
129 // restarting on every filter pass. (FBS-82172 Bug 1)
130 add_action( 'template_redirect', array( Lazy_Loader::class, 'reset_state' ) );
131
132 // Late priority so the_content runs after every other filter
133 // (shortcodes, do_blocks, embeds). Avoids rewriting tags that
134 // haven't been generated yet.
135 add_filter( 'the_content', array( Lazy_Loader::class, 'process_html' ), 999 );
136 add_filter( 'post_thumbnail_html', array( Lazy_Loader::class, 'process_html' ), 999 );
137 add_filter( 'get_avatar', array( Lazy_Loader::class, 'process_html' ), 999 );
138 add_filter( 'widget_text_content', array( Lazy_Loader::class, 'process_html' ), 999 );
139 }
140
141 public function cli_commands(): array {
142 return array(
143 array(
144 'name' => 'xspeed lazy',
145 'callback' => array( $this, 'cli_handler' ),
146 'shortdesc' => 'Show which lazy-load toggles are active.',
147 'synopsis' => array(),
148 ),
149 );
150 }
151
152 public function cli_handler( array $args, array $assoc ): void {
153 $opts = $this->get_settings();
154 foreach ( $opts as $key => $value ) {
155 $display = is_array( $value ) ? implode( ',', $value ) : ( $value ? 'on' : ( is_numeric( $value ) ? (string) $value : 'off' ) );
156 if ( is_int( $value ) ) {
157 $display = (string) $value;
158 }
159 \WP_CLI::log( sprintf( '%-30s %s', $key, $display ) );
160 }
161 }
162 }
163