Plugin Directory

Changeset 3453927


Ignore:
Timestamp:
02/04/2026 04:02:07 PM (13 days ago)
Author:
mreishus
Message:

Update Page Optimize to 0.6.0

Location:
page-optimize/trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • page-optimize/trunk/concat-css.php

    r2251132 r3453927  
    3636    }
    3737
     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
    3892    function do_items( $handles = false, $group = false ) {
    3993        $handles = false === $handles ? $this->queue : (array) $handles;
     
    4397        $this->all_deps( $handles );
    4498
    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;
    50100
    51101        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
    52109            $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
    53126            $obj->src = apply_filters( 'style_loader_src', $obj->src, $obj->handle );
    54127
     
    61134            }
    62135
    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'] : '';
    64138            $extra = $obj->extra;
     139
     140            // 2. Determine if this stylesheet can be concatenated.
    65141
    66142            // Don't concat by default
     
    68144
    69145            // 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' ) ) {
    71147                $do_concat = true;
    72148            } else {
     
    124200
    125201            // 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 ) {
    127204                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
    128205                    echo sprintf( "\n<!-- No Concat CSS %s => Filtered `false` -->\n", esc_html( $handle ) );
    129206                }
    130207            }
    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             **/
    133220            if ( true === $do_concat ) {
    134221                $media = $obj->args;
     
    137224                }
    138225
    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;
    140256                $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                }
    141264            } 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                );
    145275            }
    146276            unset( $this->to_do[ $key ] );
    147277        }
    148278
    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 ) {
    159294                    $fs_paths = array();
    160295                    foreach ( $css as $css_uri_path ) {
     
    179314                    $href = $siteurl . "/_static/??" . $path_str;
    180315                } 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
    185319                $css_id = "$media-css-" . md5( $href );
    186320                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";
    188322                } 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 );
    192334            }
    193335        }
  • page-optimize/trunk/page-optimize.php

    r3408386 r3453927  
    55Description: Optimizes JS and CSS for faster page load and render in the browser.
    66Author: Automattic
    7 Version: 0.5.8
     7Version: 0.6.0
    88Author URI: http://automattic.com/
    99*/
  • page-optimize/trunk/readme.txt

    r3408386 r3453927  
    11=== Page Optimize ===
    2 Contributors: aidvu, bjorsch, bpayton, rcrdortiz
     2Contributors: aidvu, bjorsch, bpayton, mreishus, rcrdortiz
    33Tags: performance
    44Requires at least: 5.3
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 0.5.8
     7Stable tag: 0.6.0
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    4848* `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.
    4949
     50= PHPUnit (Docker) =
     51
     52You can run the PHPUnit tests locally using Docker (no local MySQL required).
     53
     54First time (or after changing DB credentials):
     55
     56`docker compose down -v`
     57
     58Run tests:
     59
     60`docker compose up --build --abort-on-container-exit --exit-code-from tests`
     61
     62Optional 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
    5067== 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.
    5177
    5278= 0.5.8 =
  • page-optimize/trunk/service.php

    r2959889 r3453927  
    202202
    203203            // The @charset rules must be on top of the output
    204             if ( 0 === strpos( $buf, '@charset' ) ) {
    205                 preg_replace_callback(
     204            if ( 0 === stripos( $buf, '@charset' ) ) {
     205                $buf = preg_replace_callback(
    206206                    '/(?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' ) ) {
    211210                            return '';
    212211                        }
    213212
    214213                        $pre_output = $match[0] . "\n" . $pre_output;
    215 
    216214                        return '';
    217215                    },
     
    222220            // Move the @import rules on top of the concatenated output.
    223221            // Only @charset rule are allowed before them.
    224             if ( false !== strpos( $buf, '@import' ) ) {
     222            if ( false !== stripos( $buf, '@import' ) ) {
    225223                $buf = preg_replace_callback(
    226224                    '/(?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] ) {
    231228                            $pre_output .= $match['pre_path'] . ( $dirpath == '/' ? '/' : $dirpath . '/' ) .
    232229                                           $match['path'] . $match['post_path'] . "\n";
     
    326323}
    327324
    328 page_optimize_service_request();
     325if ( ! defined( 'PAGE_OPTIMIZE_SKIP_SERVICE_REQUEST' ) ) {
     326    page_optimize_service_request();
     327}
Note: See TracChangeset for help on using the changeset viewer.