WordPress.org

Make WordPress Core

Opened 2 months ago

Last modified 4 weeks ago

#50328 assigned enhancement

Enqueue script and style assets only for blocks present on the page

Reported by: aduth Owned by: gziolo
Milestone: 5.6 Priority: normal
Severity: normal Version:
Component: Editor Keywords:
Focuses: javascript Cc:

Description

Related: https://github.com/WordPress/gutenberg/pull/22754
Related: #49926

Context:

Currently, all scripts and styles for all blocks will be rendered in every front-end page. This is hugely wasteful, since in reality only the scripts and styles associated with blocks relevant for that page should be enqueued.

In part, this is made difficult by the fact that it's quite common to enqueue block assets as part of the enqueue_block_assets action, which is called not specifically for any one block type, but rather once per page load.

Proposal:

Only block assets (scripts and styles) of blocks present in the page should be enqueued.

Implementation Specifics:

See also relevant Gutenberg proposal changes: https://github.com/WordPress/gutenberg/pull/22754

While the previously-mentioned behavior of enqueue_block_assets may not be reconcilable with the desire to load assets relevant for the page, it should be possible for a block implementer to opt in to this behavior using the (already supported) script and style arguments of register_block_type.

Backward Compatibility:

See also discussion from: https://github.com/WordPress/gutenberg/pull/22754

Notably this comment:

I think it's something that, if implemented, should warrant some release devnotes explaining the change.
The broad answer for me is that it does change some behaviors, but not anything that was ever particularly documented as expected, or practically speaking would be common to depend upon.
Specifically, these changes could include:
If one was to rely on some block script to define some functionality leveraged outside the script and on pages where the block isn't present, those scripts would now only be enqueued on pages where the block is present. This is very similar case to "incidental availability" of script functionality from dependencies of other scripts, and is more an error on the part of the developer where any such script should be defined as an explicit dependency of the script.
If one was to rely on some block stylesheet to style pages where that block is not present, it would now only be enqueued on pages where the block is present. It is not clear there would be any reason someone would have done this, since the purpose of a block stylesheet should be to style the block (or even if it was styling the page, at least only the page where the block is present).
If one was to rely on the particular page enqueue lifecycle (header, footer placement), those expectations could change. This is documented in more detail in the original comment, and is potentially not an unsolvable challenge to preserve the current behavior if problematic.
It could also use some further testing regarding the comment at #21838 (comment) of more specialized use-cases, though the later comment #21838 (comment) seems to imply that the general approach is compatible.

Another potential use-case described at https://github.com/WordPress/gutenberg/pull/22754#issuecomment-639079262, though granted to be an uncommon scenario.

Attachments (2)

50328-test.diff (6.2 KB) - added by gziolo 6 weeks ago.
Test version - work in progress
50328-skip-assets.diff (6.3 KB) - added by gziolo 6 weeks ago.
Test with the logic to skip assets when not on the front

Download all attachments as: .zip

Change History (14)

#1 @westonruter
2 months ago

Cross-posting Gutenberg issue comments where I've raised a concern about FOUC:

Has it been considered to not just enqueue the scripts and styles but rather print them instead? This will reduce the risk of a flash of unstyled content, as otherwise enqueued assets will get printed at wp_footer, correct?

I took this approach in the Syntax-highlighting Code Block plugin. It's definitely not as clean as just deferring to enqueue in gutenberg#22754, but it did address FOUC.

See also atomic-blocks#294 & atomic-blocks#297 which do this for Atomic Blocks, to conditionally print the Font Awesome stylesheet before the first dependent block in the page.

#2 @gziolo
6 weeks ago

  • Milestone changed from Awaiting Review to 5.5
  • Owner set to gziolo
  • Status changed from new to assigned

@gziolo
6 weeks ago

Test version - work in progress

#3 follow-up: @gziolo
6 weeks ago

@westonruter, I'm trying to print those styles and scripts on the front-end using the approach found in the plugin you shared:

https://github.com/westonruter/syntax-highlighting-code-block/blob/e652901420ee6991213eba11e7c8c52176e84e0c/syntax-highlighting-code-block.php#L346-L352

It works for the front-end perfectly fine, but I run into an issue in the editor. If the block isn't inserted into a post, everything works correctly. As soon as insert it into the post, it renders JS and CSS inside the preloaded REST API call for the post object :)

So I have two questions:

  1. Is the approach I took valid, or did I misunderstood what you were proposing?
  2. If (1) is on track, any thoughts on how I can prevent rendering CSS/JS inside the preloaded REST API call :)

@gziolo
6 weeks ago

Test with the logic to skip assets when not on the front

#4 follow-up: @gziolo
6 weeks ago

It looks like the following check fixes the issue for the editor:

<?php
$skip_assets = is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST );

I'm not sure it's the best way to approach it though.

#5 in reply to: ↑ 4 @westonruter
6 weeks ago

Replying to gziolo:

It looks like the following check fixes the issue for the editor:

<?php
$skip_assets = is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST );

I'm not sure it's the best way to approach it though.

This is similar to what was done for Atomic Blocks in https://github.com/studiopress/atomic-blocks/pull/297

So I think that approach makes sense. I didn't need to do this in the syntax-highlighting-code-block plugin because the CSS is only ever added in the render_callback on the frontend. So the CSS is not needed in the editor.

#6 in reply to: ↑ 3 @westonruter
6 weeks ago

Replying to gziolo:

  1. Is the approach I took valid, or did I misunderstood what you were proposing?

And yes, the approach you took is the same I took in Atomic Blocks, except I had to use the render_block filter naturally.

I do have one potential issue to simply prepending the link, style, and script tags. Some themes (actually, quite a few) have CSS that target top-level elements of the content via CSS like .entry-content > *.

Consider a theme that does this:

.entry-content > * {
    display: block;
}

This would have negative results! (I faced this on an actual site.)

For this reason, in my plugin I actually inject the style markup inside the block's opening start tag.

Nevertheless, this won't work in the general case, as it can break CSS that is trying to target a block's :first-child.

Maybe themes are just _doing_it_wrong by targeting .entry-content > * with display properties and this should be fixed with education. But I see another potential problem: consider CSS that is trying to target a given block that appears right after another one: p + .wp-block-illustration. If there is CSS that gets extracted from such an Illustration block to be printed before the block, then this will break the selector.

One area to explore could be to extract the styles and scripts during do_blocks and then prepend them all to the $output of that function. That would improve the sibling selector situation, but it would not help with the .entry-content > *:first-child case.

I don't have a conclusion here, nor am I entirely sure how much of a problem this will with sites generally. But I wanted to raise this as an area of concern.

#7 follow-up: @gziolo
6 weeks ago

@westonruter, it all edge cases makes me wonder whether the initial approach proposed by @aduth wouldn't be more reliable in the majority of cases. Plugin developers can always opt-out from using style or script when registering a block and handle everything themselves.

Shouldn't we seek for an alternative proposal that would make it easier to print CSS or JS inside the rendered block?

There is also this question @matveb asked about sites with infinite scrolls and how it would work with the current proposal:
https://github.com/WordPress/gutenberg/issues/21838#issuecomment-620586710

#8 in reply to: ↑ 7 @westonruter
6 weeks ago

Replying to gziolo:

it all edge cases makes me wonder whether the initial approach proposed by aduth wouldn't be more reliable in the majority of cases.

Are you referring to enqueueing the scripts/styles when the blocks are rendered and then printing the scripts/styles in the footer? The risk there is with FOUC. But FOUC is probably better than adding a lot of assets which are never used on the page. Nevertheless, FOUC can negatively impact Core Web Vitals, in particular largest contentful paint (LCP) and cumulative layout shift (CLS).

#9 @mcsf
6 weeks ago

Catching up on this ticket and taking a new look at Gutenberg PR 22754, it seems to me that we are risking a lot more bugs and breakage by switching from plain enqueueing to inline printing.

The original approach has been more thoroughly tested and, while flashes of unstyled content are something to avoid, they seem to be a better problem to have, one that can be mitigated elsewhere, and one especially mitigated by caching.

Maybe themes are just _doing_it_wrong by targeting .entry-content > * with display properties and this should be fixed with education.

I think this is the wrong way to look at it. Even when there are clear directives (e.g. deprecation of a set of private HTML classes) there are always issues of breakage because those directives were ignored. I can imagine a much messier situation if we retroactively burden themes with staying away from particular ways of styling .entry-content. It would be destructive for both existing sites and the block editor.


This question reminds me of how JS projects under webpack can be "tree-shaken" when modules are clear about whether they are "pure" or have side effects. Down the line (or sooner if the Beta period reveals this to be a more pressing issue), I can imagine a similar affordance in the block-rendering stack where admins can choose whether they prefer their dependencies inlined.

#10 @gziolo
6 weeks ago

  • Milestone changed from 5.5 to 5.6

We are reverting this functionality on the Gutenberg plugin side with https://github.com/WordPress/gutenberg/pull/23708. It's based on reports that it doesn't work for some folks in some cases. It's hard to tell what the condition is so we better move it to WordPress 5.6 to have more time to properly investigate.

This ticket was mentioned in Slack in #core-editor by gziolo. View the logs.


6 weeks ago

This ticket was mentioned in Slack in #core-restapi by sageshilling. View the logs.


4 weeks ago

Note: See TracTickets for help on using tickets.