Changeset 3453927
- Timestamp:
- 02/04/2026 04:02:07 PM (13 days ago)
- Location:
- page-optimize/trunk
- Files:
-
- 4 edited
-
concat-css.php (modified) (7 diffs)
-
page-optimize.php (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
-
service.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
page-optimize/trunk/concat-css.php
r2251132 r3453927 36 36 } 37 37 38 protected function has_inline_style( $handle ) { 39 $after_output = $this->get_data( $handle, 'after' ); 40 if ( ! empty( $after_output ) ) { 41 return true; 42 } 43 44 return false; 45 } 46 47 protected function css_has_import( $path ) { 48 static $cache = array(); 49 50 if ( empty( $path ) ) { 51 return false; 52 } 53 54 $key = $path; 55 56 if ( array_key_exists( $key, $cache ) ) { 57 return $cache[ $key ]; 58 } 59 60 $fh = @fopen( $path, 'rb' ); 61 if ( ! $fh ) { 62 $cache[ $key ] = false; 63 return false; 64 } 65 66 $found = false; 67 $tail = ''; 68 69 // Scan in chunks to keep memory bounded for large stylesheets. 70 while ( ! feof( $fh ) ) { 71 $chunk = fread( $fh, 32768 ); 72 if ( false === $chunk || '' === $chunk ) { 73 break; 74 } 75 76 $haystack = $tail . $chunk; 77 if ( false !== stripos( $haystack, '@import' ) ) { 78 $found = true; 79 break; 80 } 81 82 // Keep a short overlap so "@import" across chunk boundaries isn't missed. 83 $tail = substr( $haystack, -7 ); 84 } 85 86 fclose( $fh ); 87 88 $cache[ $key ] = $found; 89 return $found; 90 } 91 38 92 function do_items( $handles = false, $group = false ) { 39 93 $handles = false === $handles ? $this->queue : (array) $handles; … … 43 97 $this->all_deps( $handles ); 44 98 45 $stylesheet_group_index = 0; 46 // Merge CSS into a single file 47 $concat_group = 'concat'; 48 // Concat group on top (first array element gets processed earlier) 49 $stylesheets[ $concat_group ] = array(); 99 $concat_group = null; 50 100 51 101 foreach ( $this->to_do as $key => $handle ) { 102 $css_realpath = null; 103 // 1a. Skip invalid dependencies. 104 if ( ! isset( $this->registered[ $handle ] ) ) { 105 unset( $this->to_do[ $key ] ); 106 continue; 107 } 108 52 109 $obj = $this->registered[ $handle ]; 110 111 // 1b. Skip virtual (src-less) dependencies. 112 if ( empty( $obj->src ) ) { 113 if ( null !== $concat_group ) { 114 $stylesheets[] = $concat_group; 115 $concat_group = null; 116 } 117 118 $stylesheets[] = array( 119 'type' => 'do_item', 120 'handle' => $handle, 121 ); 122 unset( $this->to_do[ $key ] ); 123 continue; 124 } 125 53 126 $obj->src = apply_filters( 'style_loader_src', $obj->src, $obj->handle ); 54 127 … … 61 134 } 62 135 63 $css_url_parsed = parse_url( $obj->src ); 136 $css_url_parsed = parse_url( $css_url ); 137 $css_path = ( is_array( $css_url_parsed ) && isset( $css_url_parsed['path'] ) ) ? $css_url_parsed['path'] : ''; 64 138 $extra = $obj->extra; 139 140 // 2. Determine if this stylesheet can be concatenated. 65 141 66 142 // Don't concat by default … … 68 144 69 145 // Only try to concat static css files 70 if ( false !== strpos( $css_url_parsed['path'], '.css' ) ) {146 if ( $css_path && false !== strpos( $css_path, '.css' ) ) { 71 147 $do_concat = true; 72 148 } else { … … 124 200 125 201 // Allow plugins to disable concatenation of certain stylesheets. 126 if ( $do_concat && ! apply_filters( 'css_do_concat', $do_concat, $handle ) ) { 202 $filtered_concat = apply_filters( 'css_do_concat', $do_concat, $handle ); 203 if ( $do_concat && ! $filtered_concat ) { 127 204 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 128 205 echo sprintf( "\n<!-- No Concat CSS %s => Filtered `false` -->\n", esc_html( $handle ) ); 129 206 } 130 207 } 131 $do_concat = apply_filters( 'css_do_concat', $do_concat, $handle ); 132 208 $do_concat = $filtered_concat; 209 210 /** 211 * 3. Add to the stylesheets output list. 212 * 213 * We keep a running $concat_group that accumulates consecutive concat-eligible styles 214 * sharing the same media type. The group is finalized and appended to $stylesheets when: 215 * - A non-concat style appears 216 * - The media type changes 217 * - An inline style is attached to the current style 218 * This makes sure output order matches registration order. 219 **/ 133 220 if ( true === $do_concat ) { 134 221 $media = $obj->args; … … 137 224 } 138 225 139 $stylesheets[ $concat_group ][ $media ][ $handle ] = $css_url_parsed['path']; 226 // Media type changed - finish the current group. 227 if ( null !== $concat_group && $concat_group['media'] !== $media ) { 228 $stylesheets[] = $concat_group; 229 $concat_group = null; 230 } 231 232 // If a non-first stylesheet in a group contains @import, the concat service hoists it, 233 // reordering CSS. Split groups so @import stylesheets are always first in their group. 234 if ( 235 null !== $concat_group 236 && ! empty( $css_realpath ) 237 && $this->css_has_import( $css_realpath ) 238 ) { 239 $stylesheets[] = $concat_group; 240 $concat_group = null; 241 } 242 243 // Start a new group if needed. 244 if ( null === $concat_group ) { 245 $concat_group = array( 246 'type' => 'concat', 247 'media' => $media, 248 'paths' => array(), 249 'handles' => array(), 250 ); 251 } 252 253 // Add this stylesheet to the current group. 254 $concat_group['paths'][] = $css_path; 255 $concat_group['handles'][] = $handle; 140 256 $this->done[] = $handle; 257 258 // Inline styles must print right after their <link>, so break the group 259 // because we can't concat directly anything after this CSS. 260 if ( $this->has_inline_style( $handle ) ) { 261 $stylesheets[] = $concat_group; 262 $concat_group = null; 263 } 141 264 } else { 142 $stylesheet_group_index ++; 143 $stylesheets[ $stylesheet_group_index ]['noconcat'][] = $handle; 144 $stylesheet_group_index ++; 265 // Non-concat item - finish any current open group to preserve order. 266 if ( null !== $concat_group ) { 267 $stylesheets[] = $concat_group; 268 $concat_group = null; 269 } 270 // Add the non-concat item. 271 $stylesheets[] = array( 272 'type' => 'do_item', 273 'handle' => $handle, 274 ); 145 275 } 146 276 unset( $this->to_do[ $key ] ); 147 277 } 148 278 149 foreach ( $stylesheets as $idx => $stylesheets_group ) { 150 foreach ( $stylesheets_group as $media => $css ) { 151 if ( 'noconcat' == $media ) { 152 foreach ( $css as $handle ) { 153 if ( $this->do_item( $handle, $group ) ) { 154 $this->done[] = $handle; 155 } 156 } 157 continue; 158 } elseif ( count( $css ) > 1 ) { 279 if ( null !== $concat_group ) { 280 $stylesheets[] = $concat_group; 281 } 282 283 foreach ( $stylesheets as $css_array ) { 284 if ( 'do_item' === $css_array['type'] ) { 285 if ( $this->do_item( $css_array['handle'], $group ) ) { 286 $this->done[] = $css_array['handle']; 287 } 288 } elseif ( 'concat' === $css_array['type'] && isset( $css_array['paths'] ) ) { 289 $media = $css_array['media']; 290 $css = $css_array['paths']; 291 $handles = $css_array['handles']; 292 293 if ( count( $css ) > 1 ) { 159 294 $fs_paths = array(); 160 295 foreach ( $css as $css_uri_path ) { … … 179 314 $href = $siteurl . "/_static/??" . $path_str; 180 315 } else { 181 $href = Page_Optimize_Utils::cache_bust_mtime( current( $css ), $siteurl ); 182 } 183 184 $handles = array_keys( $css ); 316 $href = Page_Optimize_Utils::cache_bust_mtime( $css[0], $siteurl ); 317 } 318 185 319 $css_id = "$media-css-" . md5( $href ); 186 320 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 187 echo apply_filters( 'page_optimize_style_loader_tag', "<link data-handles='" . esc_attr( implode( ',', $handles ) ) . "' rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n", $handles, $href, $media );321 $tag = "<link data-handles='" . esc_attr( implode( ',', $handles ) ) . "' rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n"; 188 322 } else { 189 echo apply_filters( 'page_optimize_style_loader_tag', "<link rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n", $handles, $href, $media ); 190 } 191 array_map( array( $this, 'print_inline_style' ), array_keys( $css ) ); 323 $tag = "<link rel='stylesheet' id='$css_id' href='$href' type='text/css' media='$media' />\n"; 324 } 325 326 $tag = apply_filters( 'page_optimize_style_loader_tag', $tag, $handles, $href, $media ); 327 328 if ( is_array( $handles ) && count( $handles ) === 1 ) { 329 $tag = apply_filters( 'style_loader_tag', $tag, $handles[0], $href, $media ); 330 } 331 332 echo $tag; 333 array_map( array( $this, 'print_inline_style' ), $handles ); 192 334 } 193 335 } -
page-optimize/trunk/page-optimize.php
r3408386 r3453927 5 5 Description: Optimizes JS and CSS for faster page load and render in the browser. 6 6 Author: Automattic 7 Version: 0. 5.87 Version: 0.6.0 8 8 Author URI: http://automattic.com/ 9 9 */ -
page-optimize/trunk/readme.txt
r3408386 r3453927 1 1 === Page Optimize === 2 Contributors: aidvu, bjorsch, bpayton, rcrdortiz2 Contributors: aidvu, bjorsch, bpayton, mreishus, rcrdortiz 3 3 Tags: performance 4 4 Requires at least: 5.3 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 0. 5.87 Stable tag: 0.6.0 8 8 License: GPLv2 or later 9 9 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 48 48 * `load-mode-js` controls how non-critical JavaScript are loaded. Values: 'defer' for [deferred](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer), 'async' for [async loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async), any other value indicates the feature should be disabled. 49 49 50 = PHPUnit (Docker) = 51 52 You can run the PHPUnit tests locally using Docker (no local MySQL required). 53 54 First time (or after changing DB credentials): 55 56 `docker compose down -v` 57 58 Run tests: 59 60 `docker compose up --build --abort-on-container-exit --exit-code-from tests` 61 62 Optional overrides (examples): 63 64 * `WP_VERSION=6.5 docker compose up --build --abort-on-container-exit --exit-code-from tests` 65 * `PHPUNIT_VERSION=9.6.20 docker compose up --build --abort-on-container-exit --exit-code-from tests` 66 50 67 == Changelog == 68 69 = 0.6.0 = 70 * Fix: Preserve stylesheet enqueue/document order when concatenating CSS. Concat-eligible styles are now emitted as sequential runs and split around non-concatenated items (e.g. external/excluded/dynamic URLs), media changes, RTL handling, and other boundaries. 71 * Fix: Inline styles (wp_add_inline_style) now print immediately after their parent stylesheet, including when styles are concatenated. 72 * Fix: Apply core's style_loader_tag filter when a concatenation run contains only a single stylesheet (matching core behavior and the JS-side fix from 0.5.0). 73 * Fix: The css_do_concat filter is now evaluated once per handle. 74 * Fix: The concat service no longer drops @import directives due to a closure scoping bug. (@charset/@import handling now runs against the intended pre-output buffer.) 75 * Fix: Stylesheets containing @import now start a new concat run so service-side @import hoisting cannot reorder imports ahead of earlier stylesheets. 76 * Fix: Treat @import and @charset as case‑insensitive when building concatenated CSS, preventing missed rules in some stylesheets. 51 77 52 78 = 0.5.8 = -
page-optimize/trunk/service.php
r2959889 r3453927 202 202 203 203 // The @charset rules must be on top of the output 204 if ( 0 === str pos( $buf, '@charset' ) ) {205 preg_replace_callback(204 if ( 0 === stripos( $buf, '@charset' ) ) { 205 $buf = preg_replace_callback( 206 206 '/(?P<charset_rule>@charset\s+[\'"][^\'"]+[\'"];)/i', 207 function ( $match ) { 208 global $pre_output; 209 210 if ( 0 === strpos( (string) $pre_output, '@charset' ) ) { 207 function ( $match ) use ( &$pre_output ) { 208 209 if ( 0 === stripos( (string) $pre_output, '@charset' ) ) { 211 210 return ''; 212 211 } 213 212 214 213 $pre_output = $match[0] . "\n" . $pre_output; 215 216 214 return ''; 217 215 }, … … 222 220 // Move the @import rules on top of the concatenated output. 223 221 // Only @charset rule are allowed before them. 224 if ( false !== str pos( $buf, '@import' ) ) {222 if ( false !== stripos( $buf, '@import' ) ) { 225 223 $buf = preg_replace_callback( 226 224 '/(?P<pre_path>@import\s+(?:url\s*\()?[\'"\s]*)(?P<path>[^\'"\s](?:https?:\/\/.+\/?)?.+?)(?P<post_path>[\'"\s\)]*;)/i', 227 function ( $match ) use ( $dirpath ) { 228 global $pre_output; 229 230 if ( 0 !== strpos( $match['path'], 'http' ) && '/' != $match['path'][0] ) { 225 function ( $match ) use ( $dirpath, &$pre_output ) { 226 227 if ( 0 !== stripos( $match['path'], 'http' ) && '/' != $match['path'][0] ) { 231 228 $pre_output .= $match['pre_path'] . ( $dirpath == '/' ? '/' : $dirpath . '/' ) . 232 229 $match['path'] . $match['post_path'] . "\n"; … … 326 323 } 327 324 328 page_optimize_service_request(); 325 if ( ! defined( 'PAGE_OPTIMIZE_SKIP_SERVICE_REQUEST' ) ) { 326 page_optimize_service_request(); 327 }
Note: See TracChangeset
for help on using the changeset viewer.