Skip to content

Conversation

@westonruter
Copy link
Member

@westonruter westonruter commented Oct 30, 2025

This primarily ensures that stylesheets related to blocks are inserted right after wp-block-library whereas other stylesheets not related to blocks are appended to the end of the HEAD. This helps ensure the expected cascade is preserved. If no wp-block-library inline style is present, then all styles get appended to the HEAD regardless.

When the CSS placeholder comment is removed from the wp-block-library inline style, the entire inline style is now removed if there are no styles left (aside from the sourceURL comment).

This PR also addresses various deficiencies with the logic in wp_load_classic_theme_block_styles_on_demand() and wp_hoist_late_printed_styles() after more thorough review.

Lastly, the use of the HTML Tag Processor was significantly improved to leverage WP_HTML_Text_Replacement.

Trac ticket: https://core.trac.wordpress.org/ticket/64099

This is a follow-up to #10288 (r61008) and to @dmsnell's post-merge feedback.

Gemini Review

The changes introduce a new mechanism for hoisting late-printed styles, particularly block-related styles, to the <head> section in classic themes. This is a significant improvement for performance and proper CSS cascade.

Here's a detailed review:

phpcompat.xml.dist

  • Observation: A new exclusion rule for PHPCompatibility.FunctionDeclarations.NewClosure.ThisFoundOutsideClass has been added for /src/wp-includes/script-loader.php.
  • Review: This indicates a new closure using this outside a class context has been introduced. The comment correctly notes this is a temporary exclusion pending PHPCompatibility v10. This is acceptable as a temporary measure, but it's important to follow up and remove this exclusion once the compatibility issue is resolved.

src/wp-includes/script-loader.php

  • Docblock for wp_print_head_scripts():
    • Observation: The docblock has been updated to provide a clearer explanation of how wp_hoist_late_printed_styles() functions, detailing the two sets of styles (block-related and others) and their hoisting.
    • Review: This is a good improvement for code clarity and documentation.
  • wp_load_classic_theme_block_styles_on_demand():
    • Observation: Comments have been refined for clarity regarding opt-out mechanisms. A new early exit if ( ! wp_should_load_separate_core_block_assets() ) { return; } has been added. The print_late_styles filter and its associated comment have been removed.
    • Review: The early exit is a good optimization. The removal of the print_late_styles filter suggests a more streamlined approach to handling late styles, which is positive.
  • wp_hoist_late_printed_styles():
    • Observation: The capture_late_styles closure now captures both $printed_block_styles and $printed_late_styles. Logic has been added to differentiate and gather block-related styles. print_late_styles() is replaced by wp_styles()->do_footer_items(). The anonymous WP_HTML_Tag_Processor class has new utility methods (get_span, insert_before, insert_after, remove). The style insertion logic within the wp_template_enhancement_output_buffer filter has been significantly refactored to precisely insert block styles after wp-block-library-inline-css and other late styles before </head>. It also handles the removal of empty inline style tags.
    • Review: This is the core of the change and appears well-executed. Separating block styles from other late styles is crucial for maintaining the CSS cascade. The use of WP_HTML_Tag_Processor for precise HTML manipulation is a robust approach. The logic for removing empty inline style tags (especially those only containing a sourceURL comment) is a good detail.
    • Nitpick: The regex preg_match( ':^/\*# sourceURL=\\S+? \*/$:', trim( $css_text ) ) is quite specific. A brief comment explaining its purpose (to identify an inline style that is effectively empty, containing only a sourceURL comment) would enhance readability.

tests/phpunit/tests/template.php

  • setUp() and tearDown():
    • Observation: Several lines related to global wp_scripts, wp_styles, and _wp_theme_features have been removed.
    • Review: This suggests a cleaner test setup and teardown, indicating that these globals are either no longer being directly manipulated or are handled by other mechanisms.
  • data_wp_hoist_late_printed_styles():
    • Observation: The data provider now includes inline_size_limit and expected_styles, and new test cases have been added to cover various scenarios (e.g., min/max inline styles, opting out of separate block styles, disabling block library).
    • Review: This is a substantial improvement in test coverage, ensuring the new logic is thoroughly validated under different configurations.
  • test_wp_hoist_late_printed_styles():
    • Observation: The method signature is updated. switch_theme('default') is called earlier. Filters for styles_inline_size_limit and wp_get_custom_css are added. wp_load_classic_theme_block_styles_on_demand() and register_core_block_style_handles() are explicitly called. A new helper ensure_style_asset_file_created() is used to create necessary CSS files for testing. wp_enqueue_style now uses https. The wp_hoist_late_printed_styles() call is conditional. the_content filter is applied. Assertions are updated to use the new $expected_styles array and a new helper get_array_snapshot_export().
    • Review: These are excellent improvements. The ensure_style_asset_file_created() helper makes the tests more robust by not relying on a prior build step. The expanded assertions and the get_array_snapshot_export() helper significantly improve the maintainability and clarity of the test suite, especially for snapshot testing. The @ticket and @covers tags are correctly used.

Conclusion:

The changes are well-implemented and significantly improve the handling of late-printed styles in classic themes, particularly for block styles. The accompanying test suite is comprehensive and robust. The temporary phpcompat.xml.dist exclusion should be addressed in the future.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

$instance->set_bookmark( 'here' );
return $instance->bookmarks['here'];
$this->set_bookmark( 'here' );
return $this->bookmarks['here'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so much better with the removal of the additional surprising complexity!

}

/**
* Export PHP array as string formatted for pasting into unit test case.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a brief example would go a long way. I have a rough idea of what this is supposed to do, which I assume is to aid in debugging test failures, but I really can’t visualize it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 156c6f5

$export = preg_replace( '/\barray \($/m', 'array(', $export );
if ( isset( $snapshot[0] ) ) {
$export = preg_replace( '/^(\s+)\d+\s+=>\s+/m', '$1', $export );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we’re trying to remove numeric indices?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, unfortunately var_export() includes them, even when sequential and starting from 0.

@westonruter westonruter changed the title #4099: Improve hoisted stylesheet ordering #64099: Improve hoisted stylesheet ordering Nov 2, 2025
function ( $buffer ) use ( $placeholder, &$printed_block_styles, &$printed_late_styles ) {

// Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans.
$processor = new class( $buffer ) extends WP_HTML_Tag_Processor {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dmsnell How does this updated WP_HTML_Tag_Processor extension look here? Note the insert_before and insert_after methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And now a review method as well.

@westonruter westonruter force-pushed the trac-64099-improve-hoisted-stylesheet-ordering branch 4 times, most recently from 2b38a00 to 1895883 Compare November 6, 2025 01:53
@westonruter westonruter force-pushed the trac-64099-improve-hoisted-stylesheet-ordering branch from 1895883 to fd0c402 Compare November 6, 2025 02:55
@westonruter westonruter force-pushed the trac-64099-improve-hoisted-stylesheet-ordering branch from dd0ce41 to c2fd29f Compare November 6, 2025 03:15
@westonruter westonruter marked this pull request as ready for review November 6, 2025 03:17
@westonruter
Copy link
Member Author

This was a big headache. But I believe all the kinks are now worked out!

@peterwilsoncc
Copy link
Contributor

peterwilsoncc commented Nov 6, 2025

I've tested this with a helper plugin that includes three four functions to run and am seeing some regressions between the 6.8 branch and this one when it comes to de registering or modifying the block CSS.

There's various ways a theme may wish to modify the block styles that are now being replaced with Core's styles.

Test function WordPress 6.8 Feature branch Result
add_dependency_to_block_library Screenshot 2025-11-07 at 9 35 46 am Screenshot 2025-11-07 at 9 36 38 am
deregister_and_replace_blocks_styles Screenshot 2025-11-07 at 9 38 31 am Screenshot 2025-11-07 at 9 39 02 am ⚠️
remove_theme_support_block_styles Screenshot 2025-11-07 at 9 40 09 am Screenshot 2025-11-07 at 9 40 54 am ⚠️
register_custom_block_styles Screenshot 2025-11-07 at 9 52 40 am Screenshot 2025-11-07 at 9 54 04 am ⚠️

@westonruter
Copy link
Member Author

@peterwilsoncc Thank you for this. After evaluating the error scenarios, they all appear to be the same as is described in my ticket comment. Namely, if a site is explicitly expecting wp-block-library to be present and not the separate block styles, then they need to add this plugin code:

add_filter( 'should_load_separate_core_block_assets', '__return_false' );

This needs to be called out in the dev note for sure.

Copy link
Contributor

@peterwilsoncc peterwilsoncc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good for commit.

I've gone through manual testing and reviewed the code in the editor as the diff is a little less clear than reading the code.

As discussed here and in slack, I think we'll need to shout out classic themes replacing styles in the dev note to ensure they are using the correct filters.

Hitting approve as the tests look firmly on their way to passing.

pento pushed a commit that referenced this pull request Nov 7, 2025
…) to preserve CSS cascade.

This ensures that on-demand block styles are inserted right after the `wp-block-library` inline style whereas other stylesheets not related to blocks are appended to the end of the `HEAD`. This helps ensure the expected cascade is preserved. If no `wp-block-library` inline style is present, then all styles get appended to the `HEAD` regardless.

The handling of the CSS placeholder comment added to the `wp-block-library` inline style is also improved. It is now inserted later to ensure the inline style is printed. Additionally, when the CSS placeholder comment is removed from the `wp-block-library` inline style, the entire `STYLE` tag is now removed if there are no styles left (aside from the `sourceURL` comment).

Lastly, the use of the HTML Tag Processor is significantly improved to leverage `WP_HTML_Text_Replacement`.

Developed in #10436

Follow-up to [61008].

Props westonruter, peterwilsoncc, dmsnell.
Fixes #64099.


git-svn-id: https://develop.svn.wordpress.org/trunk@61174 602fd350-edb4-49c9-b593-d223f7449a82
@github-actions
Copy link

github-actions bot commented Nov 7, 2025

A commit was made that fixes the Trac ticket referenced in the description of this pull request.

SVN changeset: 61174
GitHub commit: 5b28781

This PR will be closed, but please confirm the accuracy of this and reopen if there is more work to be done.

@github-actions github-actions bot closed this Nov 7, 2025
markjaquith pushed a commit to markjaquith/WordPress that referenced this pull request Nov 7, 2025
…) to preserve CSS cascade.

This ensures that on-demand block styles are inserted right after the `wp-block-library` inline style whereas other stylesheets not related to blocks are appended to the end of the `HEAD`. This helps ensure the expected cascade is preserved. If no `wp-block-library` inline style is present, then all styles get appended to the `HEAD` regardless.

The handling of the CSS placeholder comment added to the `wp-block-library` inline style is also improved. It is now inserted later to ensure the inline style is printed. Additionally, when the CSS placeholder comment is removed from the `wp-block-library` inline style, the entire `STYLE` tag is now removed if there are no styles left (aside from the `sourceURL` comment).

Lastly, the use of the HTML Tag Processor is significantly improved to leverage `WP_HTML_Text_Replacement`.

Developed in WordPress/wordpress-develop#10436

Follow-up to [61008].

Props westonruter, peterwilsoncc, dmsnell.
Fixes #64099.

Built from https://develop.svn.wordpress.org/trunk@61174


git-svn-id: http://core.svn.wordpress.org/trunk@60510 1a063a9b-81f0-0310-95a4-ce76da25c4cd
github-actions bot pushed a commit to gilzow/wordpress-performance that referenced this pull request Nov 7, 2025
…) to preserve CSS cascade.

This ensures that on-demand block styles are inserted right after the `wp-block-library` inline style whereas other stylesheets not related to blocks are appended to the end of the `HEAD`. This helps ensure the expected cascade is preserved. If no `wp-block-library` inline style is present, then all styles get appended to the `HEAD` regardless.

The handling of the CSS placeholder comment added to the `wp-block-library` inline style is also improved. It is now inserted later to ensure the inline style is printed. Additionally, when the CSS placeholder comment is removed from the `wp-block-library` inline style, the entire `STYLE` tag is now removed if there are no styles left (aside from the `sourceURL` comment).

Lastly, the use of the HTML Tag Processor is significantly improved to leverage `WP_HTML_Text_Replacement`.

Developed in WordPress/wordpress-develop#10436

Follow-up to [61008].

Props westonruter, peterwilsoncc, dmsnell.
Fixes #64099.

Built from https://develop.svn.wordpress.org/trunk@61174


git-svn-id: https://core.svn.wordpress.org/trunk@60510 1a063a9b-81f0-0310-95a4-ce76da25c4cd
Copy link
Member

@dmsnell dmsnell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@westonruter the HTML API usage looks nice. adding those extra methods to the anonymous class does clean up the logic in the part handling the STYLE placeholders.

*/
private function get_span(): WP_HTML_Span {
// Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true.
$this->set_bookmark( 'here' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically it could also fail if there are too many bookmarks already. probably not an issue. in my own code I haven’t been documenting these cases of overlooking the failure given I control the whole flow of execution in the anonymous classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants