Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree-shaking block styles on the frontend #41020

Open
wants to merge 11 commits into
base: trunk
Choose a base branch
from
Open

Conversation

aristath
Copy link
Member

@aristath aristath commented May 12, 2022

What?

In #25220 we split the CSS files for blocks, so block-themes can load separate files per-block, depending on what blocks are present on a page.
This PR takes the concept further, and allows loading CSS files depending on the actual block contents/classes etc.

Why?

Block stylesheets contain all styles for all scenarios a block may encounter. Some of those scenarios are not that frequent, yet their styles must always be loaded because we currently have no way to differentiate what's used and what's not.
This results is loading way more CSS than what is actually needed in a page, wasting resources.
The implementation in this PR allows us to do some tree-shaking and only load the styles that are actually used on a page.
The benefits will be improved performance for site visitors - especially for block themes, and improved sustainability for the WP ecosystem.

How?

The PR adds a wp_maybe_inline_block_style_parts function. That function checks for a style.[].parts (array) entry in block.json files (see example in the included paragraph block tweaks)
It then adds a filter on render_block, and if the block contains the parts from the above array, it includes the corresponding CSS file (named styles/{$part}.css).

This PR also includes changes to the core/paragraph block, splitting it to separate files, to make the implementation clearer and easier to test.

Testing Instructions

Add a paragraph block on a page, load the post on the frontend and ensure that if you don't use a dropcap, the CSS for it doesn't get loaded. If you edit the post and add a dropcap, the CSS for it should then become available on the frontend.
This change should not have any impact on the editor, this is a frontend-only improvement.

@github-actions
Copy link

github-actions bot commented May 12, 2022

Size Change: +54 B (0%)

Total Size: 1.24 MB

Filename Size Change
build/block-library/index.min.js 181 kB +54 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/annotations/index.min.js 2.75 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.51 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/index.min.js 151 kB
build/block-editor/style-rtl.css 14.6 kB
build/block-editor/style.css 14.6 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 95 B
build/block-library/blocks/comments/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 961 B
build/block-library/blocks/gallery/editor.css 964 B
build/block-library/blocks/gallery/style-rtl.css 1.51 kB
build/block-library/blocks/gallery/style.css 1.51 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 738 B
build/block-library/blocks/image/editor.css 737 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.95 kB
build/block-library/blocks/navigation/style.css 1.94 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 395 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/paragraph/styles/has-background-rtl.css 58 B
build/block-library/blocks/paragraph/styles/has-background.css 58 B
build/block-library/blocks/paragraph/styles/has-drop-cap-rtl.css 171 B
build/block-library/blocks/paragraph/styles/has-drop-cap.css 172 B
build/block-library/blocks/paragraph/styles/has-text-color-rtl.css 69 B
build/block-library/blocks/paragraph/styles/has-text-color.css 69 B
build/block-library/blocks/paragraph/styles/is-large-text-rtl.css 52 B
build/block-library/blocks/paragraph/styles/is-large-text.css 52 B
build/block-library/blocks/paragraph/styles/is-larger-text-rtl.css 50 B
build/block-library/blocks/paragraph/styles/is-larger-text.css 50 B
build/block-library/blocks/paragraph/styles/is-regular-text-rtl.css 51 B
build/block-library/blocks/paragraph/styles/is-regular-text.css 51 B
build/block-library/blocks/paragraph/styles/is-small-text-rtl.css 52 B
build/block-library/blocks/paragraph/styles/is-small-text.css 52 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 69 B
build/block-library/blocks/post-comments-form/editor.css 69 B
build/block-library/blocks/post-comments-form/style-rtl.css 495 B
build/block-library/blocks/post-comments-form/style.css 495 B
build/block-library/blocks/post-comments/editor-rtl.css 77 B
build/block-library/blocks/post-comments/editor.css 77 B
build/block-library/blocks/post-comments/style-rtl.css 628 B
build/block-library/blocks/post-comments/style.css 628 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 370 B
build/block-library/blocks/pullquote/style.css 370 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 369 B
build/block-library/blocks/query/editor.css 369 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 759 B
build/block-library/blocks/site-logo/editor.css 759 B
build/block-library/blocks/site-logo/style-rtl.css 192 B
build/block-library/blocks/site-logo/style.css 192 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 504 B
build/block-library/blocks/table/editor.css 504 B
build/block-library/blocks/table/style-rtl.css 625 B
build/block-library/blocks/table/style.css 625 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 149 B
build/block-library/blocks/template-part/editor.css 149 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 561 B
build/block-library/blocks/video/editor.css 563 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 993 B
build/block-library/common.css 990 B
build/block-library/editor-rtl.css 10.2 kB
build/block-library/editor.css 10.3 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 11.6 kB
build/block-library/style.css 11.6 kB
build/block-library/theme-rtl.css 689 B
build/block-library/theme.css 694 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47.1 kB
build/components/index.min.js 227 kB
build/components/style-rtl.css 13.9 kB
build/components/style.css 13.9 kB
build/compose/index.min.js 11.7 kB
build/core-data/index.min.js 14.6 kB
build/customize-widgets/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.4 kB
build/customize-widgets/style.css 1.4 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 7.98 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.69 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 4.05 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 30.3 kB
build/edit-post/style-rtl.css 7.08 kB
build/edit-post/style.css 7.08 kB
build/edit-site/index.min.js 47.9 kB
build/edit-site/style-rtl.css 7.73 kB
build/edit-site/style.css 7.71 kB
build/edit-widgets/index.min.js 16.4 kB
build/edit-widgets/style-rtl.css 4.4 kB
build/edit-widgets/style.css 4.4 kB
build/editor/index.min.js 38.5 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.7 kB
build/element/index.min.js 4.3 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.8 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.9 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.07 kB
build/nux/style-rtl.css 744 B
build/nux/style.css 741 B
build/plugins/index.min.js 1.98 kB
build/preferences-persistence/index.min.js 2.23 kB
build/preferences/index.min.js 1.32 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 628 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.2 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@aristath aristath changed the title WIP , nothing to see here yet Tree-shaking block styles on the frontend May 13, 2022
@aristath aristath requested a review from gziolo May 13, 2022 10:49
@aristath aristath marked this pull request as ready for review May 13, 2022 10:50
@aristath aristath requested review from mtias and a team May 13, 2022 10:50
@gziolo gziolo added [Feature] Blocks Overall functionality of blocks [Type] Performance Related to performance efforts [Feature] Block API API that allows to express the block paradigm. [Type] New API New API to be used by plugin developers or package users. Needs Technical Feedback Needs testing from a developer perspective. labels May 13, 2022
schemas/json/block.json Outdated Show resolved Hide resolved
@ramonjd
Copy link
Member

ramonjd commented May 15, 2022

Would this have any eventual overlap with/effect on the exploratory work in

I believe the premise is to move some block CSS rules to block.json and then merge with theme.json styles.

cc @oandregal

Copy link
Contributor

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

This is a fascinating exploration @aristath, nice work digging into the performance / fine-tuning the styles that get output to the site frontend!

What sort of impact will this change have for plugins that load block content in asynchronous contexts (for example, endless scroll plugins, or any other use case where post content is fetched via the REST API and then appended to the page)?

We currently have an open issue (#35376) for Layout styles (or anything that's currently output to separate style tags when individual blocks are rendered) where certain styles are unavailable in async contexts, so that the Post Content block does not render correctly within the editor, but this'll also apply to those endless scroll / async loading plugins, too. I previously tried a few different ideas to solve this problem, but unfortunately haven't landed on a good approach yet, but I'd be very keen to hear if you have any thoughts on how we might solve for this if we tree-shake block style output.

To try to distill the use case / question:

"As a plugin developer, on an already loaded page of the site frontend, if I asynchronously load some extra block content to be appended to the page, how do I ensure I have the styles in place for that extra markup?"

@aristath
Copy link
Member Author

Would this have any eventual overlap with/effect on the exploratory work in

I believe the premise is to move some block CSS rules to block.json and then merge with theme.json styles.

There would be no overlap between these two.
If some styles end up in theme.json instead of .css files, then the only thing that will change is that some CSS files will have fewer rules in them, or some "partial" CSS files will be removed. However, the mechanism that loads the CSS files themselves will be unaffected, and won't have any impact or interfere in any way, with the JSON implementation 👍

What sort of impact will this change have for plugins that load block content in asynchronous contexts (for example, endless scroll plugins, or any other use case where post content is fetched via the REST API and then appended to the page)?

"As a plugin developer, on an already loaded page of the site frontend, if I asynchronously load some extra block content to be appended to the page, how do I ensure I have the styles in place for that extra markup?"

This doesn't work now either... If in a block theme, there is an infinite scroll plugin (or any other method that loads content async), and that plugin loads a block that doesn't already exist on the page, then styles for that block don't get loaded.
So not much will actually change... eventually we'll need to implement something for lazy-loaded styles regardless of whether this PR gets merged or not.
In a PR almost 2 years ago I experimented a bit with different methods of loading block stylesheets: #25118
The reason that we have not implemented something like that yet, is that I don't think there are any plugins/themes out there that load content asynchronously, so this was never really a priority... In order to solve the problem, we must first be able to replicate it consistently, and have a working scenario we can experiment with.
Have you seen any infinite-scroll plugins for block themes? If we have something tangible, I'd be more than happy to take a look and figure out something!
I expect that something like the inject solution from the PR I linked to above - or the async-head implementation from https://github.com/WordPress/gutenberg/pull/27716/files#diff-e205675e31cf3d832ac1189ca39d437e3d9713f9ccd670903ec5ca0a085b8d87R798-R861 would be necessary in order to make async plugins properly load the stylesheets.

@andrewserong
Copy link
Contributor

If in a block theme, there is an infinite scroll plugin (or any other method that loads content async), and that plugin loads a block that doesn't already exist on the page, then styles for that block don't get loaded.

Great point, thanks for clarifying.

eventually we'll need to implement something for lazy-loaded styles regardless of whether this PR gets merged or not.

Absolutely. I mostly wanted to ask the question to check whether or not the proposed change will make it harder or easier for us when it does come time to solve it, and it sounds like it won't make too much of a difference. If we think the difference is going to be negligible then let's not hold up introducing the performance enhancement!

The reason that we have not implemented something like that yet, is that I don't think there are any plugins/themes out there that load content asynchronously, so this was never really a priority... In order to solve the problem, we must first be able to replicate it consistently, and have a working scenario we can experiment with.

It's a tricky issue, because there's lots of infinite scroll plugins in the plugin directory with large numbers of active installations (e.g. 50k+), but that are intended for classic themes. If it's difficult or impossible to implement in blocks-based themes, then we're sort of in a position where we know lots of sites use infinite scroll, but there isn't yet an easy way for folks to build it — so we have a potential gap where I think the current state of FSE and global styles means that it isn't easy for plugin developers to enthusiastically jump into this space. But that's very much beyond the scope of this PR 😀 and probably more relates to discussions in #38224 on block hydration.

In order to solve the problem, we must first be able to replicate it consistently, and have a working scenario we can experiment with.

The main concrete scenario we have at the moment is the problem of being able to accurately preview post content in the Post Content block in the site editor. Ideally we'd be rendering a 1:1 representation of the post, but as in #35376, there are some styles already that don't appear correctly there. So again, not a blocker for this PR, just an issue that I'd be keen for us to come up with a long-term solution to, or to keep in the back of our minds if we make changes that might make it trickier to fix.

I expect that something like the inject solution from the PR I linked to above ... would be necessary

Oh, the injection ideas in your earlier PRs are really interesting, too. Thanks again!

Copy link
Contributor

@draganescu draganescu left a comment

Choose a reason for hiding this comment

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

cools stuff 👏🏻 I think the code looks good in terms of functionality but I am unsure about:

  • the "styledClasses" name in block json schema
  • the editor / front end disconnect

Am I missing something or if I want styledClasses then every style of my block has to be one of these classes?

@aristath
Copy link
Member Author

aristath commented May 18, 2022

cools stuff 👏🏻 I think the code looks good

Thank you @draganescu !

  • the editor / front end disconnect

In the editor, we have no way to "lazy-load" or inject styles JIT as a block gets added. That's why the current behavior of the editor is to just load a single file for all blocks (packages/block-library/src/style.scss). Currently, the frontend loads files only for blocks that exist on a page, while the editor loads them all. This PR doesn't change the existing behavior, it just splits the single-block CSS files to smaller parts so the frontend is more modular and performant.

  • the "styledClasses" name in block json schema

Am I missing something or if I want styledClasses then every style of my block has to be one of these classes?

Well... not quite. In the styledClasses array we're defining strings that should be checked in the block-content using strpos in the render_block filter.
So if I want to only have a separate file for the has-drop-cap class in a <p>, then I could define "styledClasses": [ "p", "has-drop-cap" ]. Then, then style-part-p.css file will always get loaded (because a paragraph element will always contain p), and the has-drop-cap.css file will only get loaded when the block contains has-drop-cap. So we can split just 1 class off the CSS if that's what we want. The paragraph block just didn't have any styles that should always get loaded and it was all special-cases, that's why it's not evident in this example.

I agree that the styledClasses name is not 100% accurate... It checks any string, not just classNames, but it is close to the intended behavior. Do you perhaps have any suggestions on something that would fit the purpose better?

@ntsekouras
Copy link
Contributor

@aristath do you think we should try to have all the css files inside a separate directory? Just for easier navigation and better file organization, as the main block directory could end up with many css files.

@aristath
Copy link
Member Author

do you think we should try to have all the css files inside a separate directory? Just for easier navigation and better file organization, as the main block directory could end up with many css files.

I think that would be nice, yes. It's what I initially tried to do in my first commit here. However, I could not get the build processes right at the time, and files in a subfolder were not getting compiled.
The initial idea was to have files like packages/block-library/src/paragraph/styles/has-drop-cap.scss, but in order to accommodate the build tooling I ended up with packages/block-library/src/paragraph/style-part-has-drop-cap.scss.

I'll take another look and try to get them to get properly compiled in a subfolder, but it would be great if someone with more experience in webpack & our tooling can lend a hand 👍

@aristath
Copy link
Member Author

Update: Moved the style partials to a styles folder and successfully tweaked the build tasks so they now work correctly 👍

@gziolo
Copy link
Member

gziolo commented May 19, 2022

@aristath, thank you so much for taking a stab at another performance optimization for blocks. It's really interesting to see how you try to find a way to ship only those styles provided for the block that is really used on the front end. While the implementation proposed is technically working and I believe could be shipped as-is, I would like us to discuss what other alternatives we have here. Can we avoid introducing another Block API related to styles? We have already several ways to control styles:

  • style, editStyle for assets, soon probably also viewStyle based on requests from devs
  • wp_enqueue_block_style
  • styles for style variations (that is also based on class names) and register_block_style
  • supports and Style Engine

Is there a way to update one of the existing methods to achieve similar goals?

Using class names as the way to perform all the optimizations is also something I want to understand better. There was a lengthy discussion in #38998 about standardizing block markup, CSS classes, and so on. The consensus was that there is no plan to promote the existing class names used with core blocks for some styling purposes to public APIs as explained by @mtias in #38998 (comment). Aren't you afraid that the API proposed would increase the importance of using standardized class names in block markup?

@aristath
Copy link
Member Author

Thank you for the feedback @gziolo! Those are some interesting questions...

  • We can use wp_enqueue_block_style internally, that won't change the implementation much.
  • If it was possible to have more than 1 styles variations applied at the same time, then technically we would be able to use the block-styles API. In that scenario, things like is-large-text, has-drop-cap etc would be internally translated to style variations and we'd be able to apply multiple styles at the same time.

Can we avoid introducing another Block API related to styles?

I wouldn't call this one an "API"... When backported to Core it could be a part of one of the existing functions without exposing anything additional. Using the block_type_metadata_settings filter (and therefore exposing a new function) was the only way to achieve it in the plugin.

Regarding class names: Technically the implementation does not exactly use class names... As mentioned in my comment above, it can be any string that is included in the block markup. So if for example next year we move away from a class name has-drop-cap and instead use something like data-drop-cap="true", the implementation will still work by changing the filename (and the reference in block.json) to data-drop-cap. The styledClasses name that I added is not properly reflecting the actual mechanism, but I couldn't find a better name for it - and at this stage it made sense.

Aren't you afraid that the API proposed would increase the importance of using standardized class names in block markup?

well... The partial styles will be on a per-block basis, so each block is treated uniquely depending on its scenario. I don't think that it will make standardization any more or less important, especially if we find a more suitable name for the styledClasses, something that reflects what it does more accurately. Naming it styledElements for example would be just as fine, and would maybe avoid confusion? 🤔

@gziolo
Copy link
Member

gziolo commented May 19, 2022

I wouldn't call this one an "API"... When backported to Core it could be a part of one of the existing functions without exposing anything additional. Using the block_type_metadata_settings filter (and therefore exposing a new function) was the only way to achieve it in the plugin.

Currently, it's a top-level field in block.json so it's de fecto a public API 😄

Screenshot 2022-05-19 at 15 34 43

How would you implement it in WordPress core differently?

Regarding class names: Technically the implementation does not exactly use class names... As mentioned in my comment above, it can be any string that is included in the block markup. So if for example next year we move away from a class name has-drop-cap and instead use something like data-drop-cap="true", the implementation will still work by changing the filename (and the reference in block.json) to data-drop-cap. The styledClasses name that I added is not properly reflecting the actual mechanism, but I couldn't find a better name for it - and at this stage it made sense.

That's interesting. So styledClasses is more like a list of matching phrases that are used to detect whether a corresponding file has to be enqueued on the front end.

Naming it styledElements for example would be just as fine, and would maybe avoid confusion? 🤔

Elements are already a thing in theme.json, for example: #40260 and #41140. It might be also relevant to what you are trying to do here.

@aristath
Copy link
Member Author

Thank you @gziolo for the feedback!

Taking it into account, I pushed a commit that moved the implementation inside style instead of introducing a new styledClasses.
style could previously be either a string, or an array. If an array, then each item had to be a string. With this change, each item in the array can be either a string, or an object. If an object, then it's defined as { "handle": string, "parts": [ string, string ] }. I think using an array of objects will give us maximum versatility in the future, as we'll be able to define extra things there, without the need to re-invent the wheel. In the future we could have things like src, dependencies, version, media etc in there. Transitioning to an array of objects seems to me like the most future-proof implementation

I think this change also resolves the issue about using the term "class" in there... 😅

@mtias
Copy link
Member

mtias commented May 21, 2022

If it was possible to have more than 1 styles variations applied at the same time, then technically we would be able to use the block-styles API.

One thing to look at is that a lot of the block styles we bundle in core predated style attributes. Same for core style variations that are opaque (i.e. their styles are not expressed as style attributes, which means they are not composable). Instead of thinking about having multiple style variations we should look at decomposing them into attributes, so we can migrate all of those into style properties and out of the opaque classes in most cases.

@gziolo gziolo mentioned this pull request May 23, 2022
58 tasks
@luisherranz
Copy link
Member

I think this solution is acceptable for core blocks, where the developer experience is not that important, and we need to squeeze any performance gain.

But I don't like the idea of adding more APIs to our already long list. In my opinion, we should aim to remove APIs, not to add more. Tree-shaking is an automatic process handled by compilers/bundlers, not by the developers. We should invest more time investigating ways to absorb these optimizations in WordPress in a way that is transparent to the developer, works by default and doesn't need a public API.

@aristath
Copy link
Member Author

Perhaps "tree-shaking" was a poor choice of words 😅
Tree-shaking, as mentioned above, is an automated process. It involves eliminating dead (or unused) code, and should not require any developer interaction. "True" tree-shaking would involve checking the CSS files for blocks, parsing their contents, and then eliminating the parts that are not relevant to the page.
It is actually very much possible to implement that... We have the HTML, we have the CSS, we'd just need to cleverly parse them and tree-shake the CSS. But implementing something like that would also have a performance cost, and would involve some pretty heavy-duty CSS & HTML parsing on the server.
So in this case, the concept was to follow the reverse logic: The developer decides to split their CSS in "scenarios", which will then get enqueued if and only if that scenario is encountered. I suppose you could call that reverse-tree-shaking? Or JIT-styling 🤷
I agree that adding yet another API would be a bad idea. In the latest iteration after feedback I received - mostly from @gziolo in this PR, we don't add any more APIs... It allows the style in block.json to be an array of objects, which is something that would facilitate and future-proof the implementation as well.

@luisherranz
Copy link
Member

In the latest iteration [...] we don't add any more APIs

Even if it's not a new property or function, it's yet another API in the sense that developers need to know about that option and how they can use it.

But hey, I don't pretend to go against this PR if it can squeeze a few kilobytes out of core blocks CSS for each page load. It's just that I think that we need to find ways to reduce the number of APIs/concepts developers are exposed to, especially when they are related to performance optimizations 🙂

@aristath
Copy link
Member Author

It's just that I think that we need to find ways to reduce the number of APIs/concepts developers are exposed to, especially when they are related to performance optimizations

No argument there... Any suggestions?

@luisherranz
Copy link
Member

If Gutenberg were to absorb these optimizations, it must do so programmatically. Then the question becomes: what information does Gutenberg need to do this optimization automatically?

As an example, this issue is also related to CSS tree-shaking but is more aligned with the mindset I'm proposing. We give Gutenberg all the information (the block CSS, the theme.json and the Global Styles), and it decides what to render in each case.

Of course, adding CSS in a JSON is a step backward in terms of developer experience, so the next step should be to figure out how developers could write their CSS in CSS files. Fortunately, turning CSS files into objects is something that has been widely explored in JS framework compilers and CSS-in-JS solutions. It can even be done with a regexp (code, tests). So at least we know it's doable.

But as I said, Ari, I don't want to block this issue. I think it still adds value to Gutenberg. It was just a comment about a mindset I hope we could embrace when thinking about optimizations, to slowly improve Gutenberg's developer experience over time 😊

@draganescu
Copy link
Contributor

Just adding my 2 cents that since the whole theme.json global styles, style engine, block.json are so much in flux, this API, while very clever 👏🏻 , may not have a place after all :/. I am also not a fan of one file per rule 😁, but that is personal preference.

@aristath would you say #34180 opens the door for better approaches to tree shaking block styles on the front end? If, and only if, yes, then could that path be considered and improved towards optimisation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Blocks Overall functionality of blocks Needs Technical Feedback Needs testing from a developer perspective. [Type] New API New API to be used by plugin developers or package users. [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants