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

Add useSettings hook for reading multiple settings at once #55337

Merged
merged 18 commits into from
Oct 19, 2023

Conversation

jsnajdr
Copy link
Member

@jsnajdr jsnajdr commented Oct 13, 2023

This is an attempt to improve the performance situation discussed in #54819, to make typing faster. I expected better results, but here it is anyway.

The problem is that 1000 blocks create 80k subscriptions to the core/block-editor store, and every action dispatch leads to calling 80k selectors and checking if their return value has changed. Of course, in a vast majority of cases the return value hasn't changed.

useSelect has async mode that's active on almost all of the 1000 blocks, but even adding an item to the priority queue 80k times takes some effort.

This PR addresses one hook that is to blame for many (20k out of 80k) of the store subscriptions: useSetting.

The first few commits perform optimizations on the existing useSetting hook, making it in total 40% faster. Don't create new strings, memoize splitting a.b.c paths into ['a', 'b', 'c'] arrays (lodash.get always did this, too), etc.

The second part of the PR is creating a new hook, useSettings. Instead of reading settings individually:

const allowFixed = useSetting( 'position.fixed' );
const allowSticky = useSetting( 'position.sticky' );

we merge them into one hook call:

const [ allowFixed, allowSticky ] = useSettings( [ 'position.fixed', 'position.sticky' ] );

The point is that each useSetting hook has one useSelect inside, leading to one store subscription per hook. My merging the setting reads together, we can save a lot of subscriptions.

Migrating most usages of useSetting to useSettings reduced the number of block-editor subscription, on a large post with 1000 blocks, from 80k to 60k.

The improvements are nice but still very incremental, they don't really solve the problem. We'll need to continue to search for other solutions.

The last commit contains some performance-measuring debug code I've been using. There are:

  • console.log statements that report the number of block-editor subscriptions
  • console.log statements that report on how the async mode priority queue is being processed in a series of requestIdleCallbacks. Gives a good picture on how many store listeners we can process per second.

I'll remove this debugging code before merging (hopefully 🙂)

This PR is best tested with a 4x CPU throttling enabled in the Performance tab in Chrome devtools.

@jsnajdr jsnajdr added [Type] Performance Related to performance efforts [Package] Block editor /packages/block-editor labels Oct 13, 2023
@jsnajdr jsnajdr self-assigned this Oct 13, 2023
@@ -126,48 +126,93 @@ export function shouldSkipSerialization( blockType, featureSet, feature ) {
* @return {Object} Settings object.
*/
export function useBlockSettings( name, parentLayout ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For this particular function, my goal was to actually move its call to BlockEdit or something and add it to the context of a block (probably as a private thing), that way it's only called once per block (as opposed to one per hook like today).

@github-actions
Copy link

github-actions bot commented Oct 13, 2023

Size Change: +198 B (0%)

Total Size: 1.66 MB

Filename Size Change
build/block-editor/index.min.js 219 kB +246 B (0%)
build/block-library/index.min.js 211 kB -47 B (0%)
build/edit-post/index.min.js 35.7 kB +2 B (0%)
build/format-library/index.min.js 7.82 kB -3 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 964 B
build/annotations/index.min.js 2.71 kB
build/api-fetch/index.min.js 2.29 kB
build/autop/index.min.js 2.11 kB
build/blob/index.min.js 461 B
build/block-directory/index.min.js 7.25 kB
build/block-directory/style-rtl.css 1.04 kB
build/block-directory/style.css 1.04 kB
build/block-editor/content-rtl.css 4.28 kB
build/block-editor/content.css 4.27 kB
build/block-editor/default-editor-styles-rtl.css 403 B
build/block-editor/default-editor-styles.css 403 B
build/block-editor/style-rtl.css 15.6 kB
build/block-editor/style.css 15.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 90 B
build/block-library/blocks/archives/style.css 90 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 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 138 B
build/block-library/blocks/audio/theme.css 138 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 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/block/editor-rtl.css 305 B
build/block-library/blocks/block/editor.css 305 B
build/block-library/blocks/button/editor-rtl.css 587 B
build/block-library/blocks/button/editor.css 587 B
build/block-library/blocks/button/style-rtl.css 633 B
build/block-library/blocks/button/style.css 632 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 113 B
build/block-library/blocks/categories/editor.css 112 B
build/block-library/blocks/categories/style-rtl.css 124 B
build/block-library/blocks/categories/style.css 124 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 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 421 B
build/block-library/blocks/columns/style.css 421 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 199 B
build/block-library/blocks/comment-template/style.css 198 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 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 647 B
build/block-library/blocks/cover/editor.css 650 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 98 B
build/block-library/blocks/details/style.css 98 B
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 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 138 B
build/block-library/blocks/embed/theme.css 138 B
build/block-library/blocks/file/editor-rtl.css 316 B
build/block-library/blocks/file/editor.css 316 B
build/block-library/blocks/file/style-rtl.css 311 B
build/block-library/blocks/file/style.css 312 B
build/block-library/blocks/file/view.min.js 321 B
build/block-library/blocks/footnotes/style-rtl.css 201 B
build/block-library/blocks/footnotes/style.css 199 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 228 B
build/block-library/blocks/form-input/style-rtl.css 343 B
build/block-library/blocks/form-input/style.css 343 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 343 B
build/block-library/blocks/form-submission-notification/editor.css 342 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/form/view.min.js 452 B
build/block-library/blocks/freeform/editor-rtl.css 2.61 kB
build/block-library/blocks/freeform/editor.css 2.61 kB
build/block-library/blocks/gallery/editor-rtl.css 957 B
build/block-library/blocks/gallery/editor.css 962 B
build/block-library/blocks/gallery/style-rtl.css 1.55 kB
build/block-library/blocks/gallery/style.css 1.55 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 654 B
build/block-library/blocks/group/editor.css 654 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 189 B
build/block-library/blocks/heading/style.css 189 B
build/block-library/blocks/html/editor-rtl.css 340 B
build/block-library/blocks/html/editor.css 341 B
build/block-library/blocks/image/editor-rtl.css 834 B
build/block-library/blocks/image/editor.css 833 B
build/block-library/blocks/image/style-rtl.css 1.54 kB
build/block-library/blocks/image/style.css 1.54 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/image/view.min.js 2 kB
build/block-library/blocks/latest-comments/style-rtl.css 357 B
build/block-library/blocks/latest-comments/style.css 357 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 478 B
build/block-library/blocks/latest-posts/style.css 478 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 505 B
build/block-library/blocks/media-text/style.css 503 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 671 B
build/block-library/blocks/navigation-link/editor.css 672 B
build/block-library/blocks/navigation-link/style-rtl.css 103 B
build/block-library/blocks/navigation-link/style.css 103 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/editor-rtl.css 2.26 kB
build/block-library/blocks/navigation/editor.css 2.26 kB
build/block-library/blocks/navigation/style-rtl.css 2.26 kB
build/block-library/blocks/navigation/style.css 2.25 kB
build/block-library/blocks/navigation/view.min.js 1.07 kB
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 401 B
build/block-library/blocks/page-list/editor.css 401 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 235 B
build/block-library/blocks/paragraph/editor.css 235 B
build/block-library/blocks/paragraph/style-rtl.css 335 B
build/block-library/blocks/paragraph/style.css 335 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 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 508 B
build/block-library/blocks/post-comments-form/style.css 508 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 141 B
build/block-library/blocks/post-excerpt/style.css 141 B
build/block-library/blocks/post-featured-image/editor-rtl.css 588 B
build/block-library/blocks/post-featured-image/editor.css 586 B
build/block-library/blocks/post-featured-image/style-rtl.css 345 B
build/block-library/blocks/post-featured-image/style.css 345 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 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 409 B
build/block-library/blocks/post-template/style.css 408 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 69 B
build/block-library/blocks/post-time-to-read/style.css 69 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 335 B
build/block-library/blocks/pullquote/style.css 335 B
build/block-library/blocks/pullquote/theme-rtl.css 168 B
build/block-library/blocks/pullquote/theme.css 168 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 288 B
build/block-library/blocks/query-pagination/style.css 284 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 486 B
build/block-library/blocks/query/editor.css 486 B
build/block-library/blocks/query/style-rtl.css 312 B
build/block-library/blocks/query/style.css 308 B
build/block-library/blocks/query/view.min.js 609 B
build/block-library/blocks/quote/style-rtl.css 222 B
build/block-library/blocks/quote/style.css 222 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 149 B
build/block-library/blocks/rss/editor.css 149 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 184 B
build/block-library/blocks/search/editor.css 184 B
build/block-library/blocks/search/style-rtl.css 613 B
build/block-library/blocks/search/style.css 613 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/search/view.min.js 471 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 234 B
build/block-library/blocks/separator/style.css 234 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 329 B
build/block-library/blocks/shortcode/editor.css 329 B
build/block-library/blocks/site-logo/editor-rtl.css 760 B
build/block-library/blocks/site-logo/editor.css 760 B
build/block-library/blocks/site-logo/style-rtl.css 204 B
build/block-library/blocks/site-logo/style.css 204 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 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 682 B
build/block-library/blocks/social-links/editor.css 681 B
build/block-library/blocks/social-links/style-rtl.css 1.45 kB
build/block-library/blocks/social-links/style.css 1.45 kB
build/block-library/blocks/spacer/editor-rtl.css 359 B
build/block-library/blocks/spacer/editor.css 359 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 432 B
build/block-library/blocks/table/editor.css 432 B
build/block-library/blocks/table/style-rtl.css 646 B
build/block-library/blocks/table/style.css 645 B
build/block-library/blocks/table/theme-rtl.css 157 B
build/block-library/blocks/table/theme.css 157 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 403 B
build/block-library/blocks/template-part/editor.css 403 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/term-description/style-rtl.css 111 B
build/block-library/blocks/term-description/style.css 111 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 99 B
build/block-library/blocks/verse/style.css 99 B
build/block-library/blocks/video/editor-rtl.css 552 B
build/block-library/blocks/video/editor.css 555 B
build/block-library/blocks/video/style-rtl.css 191 B
build/block-library/blocks/video/style.css 191 B
build/block-library/blocks/video/theme-rtl.css 139 B
build/block-library/blocks/video/theme.css 139 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.11 kB
build/block-library/common.css 1.11 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 12.4 kB
build/block-library/editor.css 12.4 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 14.4 kB
build/block-library/style.css 14.4 kB
build/block-library/theme-rtl.css 700 B
build/block-library/theme.css 705 B
build/block-serialization-default-parser/index.min.js 1.13 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 51.5 kB
build/commands/index.min.js 15.5 kB
build/commands/style-rtl.css 947 B
build/commands/style.css 942 B
build/components/index.min.js 250 kB
build/components/style-rtl.css 11.9 kB
build/components/style.css 11.9 kB
build/compose/index.min.js 12.7 kB
build/core-commands/index.min.js 2.72 kB
build/core-data/index.min.js 70.9 kB
build/customize-widgets/index.min.js 12 kB
build/customize-widgets/style-rtl.css 1.51 kB
build/customize-widgets/style.css 1.5 kB
build/data-controls/index.min.js 651 B
build/data/index.min.js 8.87 kB
build/date/index.min.js 17.9 kB
build/deprecated/index.min.js 462 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 571 B
build/edit-post/classic.css 571 B
build/edit-post/style-rtl.css 7.88 kB
build/edit-post/style.css 7.88 kB
build/edit-site/index.min.js 205 kB
build/edit-site/style-rtl.css 14.3 kB
build/edit-site/style.css 14.3 kB
build/edit-widgets/index.min.js 17 kB
build/edit-widgets/style-rtl.css 4.85 kB
build/edit-widgets/style.css 4.84 kB
build/editor/index.min.js 45.9 kB
build/editor/style-rtl.css 3.58 kB
build/editor/style.css 3.58 kB
build/element/index.min.js 4.87 kB
build/escape-html/index.min.js 548 B
build/format-library/style-rtl.css 577 B
build/format-library/style.css 577 B
build/hooks/index.min.js 1.57 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.61 kB
build/interactivity/index.min.js 11.4 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.76 kB
build/keycodes/index.min.js 1.9 kB
build/list-reusable-blocks/index.min.js 2.21 kB
build/list-reusable-blocks/style-rtl.css 865 B
build/list-reusable-blocks/style.css 865 B
build/media-utils/index.min.js 2.92 kB
build/notices/index.min.js 964 B
build/nux/index.min.js 2.01 kB
build/nux/style-rtl.css 775 B
build/nux/style.css 771 B
build/patterns/index.min.js 4.51 kB
build/patterns/style-rtl.css 518 B
build/patterns/style.css 517 B
build/plugins/index.min.js 1.81 kB
build/preferences-persistence/index.min.js 1.85 kB
build/preferences/index.min.js 1.26 kB
build/primitives/index.min.js 994 B
build/priority-queue/index.min.js 1.52 kB
build/private-apis/index.min.js 988 B
build/react-i18n/index.min.js 631 B
build/react-refresh-entry/index.min.js 9.46 kB
build/react-refresh-runtime/index.min.js 6.78 kB
build/redux-routine/index.min.js 2.71 kB
build/reusable-blocks/index.min.js 2.73 kB
build/reusable-blocks/style-rtl.css 265 B
build/reusable-blocks/style.css 265 B
build/rich-text/index.min.js 10.2 kB
build/router/index.min.js 1.79 kB
build/server-side-render/index.min.js 1.96 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 1.98 kB
build/token-list/index.min.js 587 B
build/url/index.min.js 3.83 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 41.8 kB
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 967 B
build/warning/index.min.js 259 B
build/widgets/index.min.js 7.18 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.18 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

@github-actions
Copy link

github-actions bot commented Oct 13, 2023

Flaky tests detected in 64b12aee38f1f1fcb77f9015a8c1c91f66dfdeb6.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/6569868365
📝 Reported issues:

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 13, 2023

I removed the commit with debugging code to make the CI check pass. You can manually apply it with git cherry-pick c029303.

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

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

Nice work 🥇

Thanks for diving deeper into the large-post typing performance issues. 👍

If useSettings is preferred for multiple settings, I think we should ensure to educate folks about it (adding some additional explanation in docs) and maybe introduce an ESLint rule that prevents sibling useSetting() calls?

@@ -125,7 +128,7 @@ export function setImmutably( object, path, value ) {
* @return {*} Value of the object property at the specified path.
*/
export const getValueFromObjectPath = ( object, path, defaultValue ) => {
const normalizedPath = Array.isArray( path ) ? path : path.split( '.' );
const normalizedPath = Array.isArray( path ) ? path : stringToPath( path );
Copy link
Member

Choose a reason for hiding this comment

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

This can totally be shipped as a separate PR regardless of how we decide to move forward.

* ```js
* const isEnabled = useSetting( 'typography.dropCap' );
* ```
* @param {string[]} paths The path to the setting.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* @param {string[]} paths The path to the setting.
* @param {string[]} paths The paths to the settings.

}
return result;
}

/**
* Hook that retrieves the given setting for the block instance in use.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Hook that retrieves the given setting for the block instance in use.
* Hook that retrieves the given settings for the block instance in use.

}
return result;
}

/**
* Hook that retrieves the given setting for the block instance in use.
*
* It looks up the settings first in the block instance hierarchy.
* If none is found, it'll look it up in the block editor store.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* If none is found, it'll look it up in the block editor store.
* If none are found, it'll look them up in the block editor store.

Copy link
Member Author

Choose a reason for hiding this comment

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

Pushed a commit that fixes all the typos in the docblocks.

*/
export default function useSetting( path ) {
const { name: blockName, clientId } = useBlockEditContext();
export function useSettings( paths ) {
Copy link
Member

Choose a reason for hiding this comment

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

Does it make sense to move useSettings() to a separate file? Perhaps under ../use-settings/index.js? Right now it's a bit unfortunate that use-setting/index.js exports useSettings().

Copy link
Member Author

Choose a reason for hiding this comment

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

The hooks are very closely related, it's not justified to have them in separate files.

Most consumers should import them from the parent components module, not using the /use-setting subpath.

Copy link
Member

Choose a reason for hiding this comment

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

I disagree because I still find it confusing and unexpected, but I'll agree to disagree and commit.

Comment on lines 967 to 985
Hook that retrieves the given setting for the block instance in use.

It looks up the settings first in the block instance hierarchy. If none is found, it'll look it up in the block editor store.

_Parameters_

- _paths_ `string[]`: The path to the setting.

_Returns_

- `any[]`: Returns the values defined for the settings.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Hook that retrieves the given setting for the block instance in use.
It looks up the settings first in the block instance hierarchy. If none is found, it'll look it up in the block editor store.
_Parameters_
- _paths_ `string[]`: The path to the setting.
_Returns_
- `any[]`: Returns the values defined for the settings.
Hook that retrieves the given settings for the block instance in use.
It looks up the settings first in the block instance hierarchy. If none are found, it'll look them up in the block editor store.
_Parameters_
- _paths_ `string[]`: The paths to the settings.
_Returns_
- `any[]`: Returns the values defined for the settings.


function FontSizePicker( props ) {
const fontSizes = useSetting( 'typography.fontSizes' );
const disableCustomFontSizes = ! useSetting( 'typography.customFontSize' );
const [ fontSizes, customFontSizes ] = useSettings( [
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should keep the same name as the subsetting name:

Suggested change
const [ fontSizes, customFontSizes ] = useSettings( [
const [ fontSizes, customFontSize ] = useSettings( [

Copy link
Member Author

Choose a reason for hiding this comment

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

Here I chose the variable name based on the name of the setting: customFontSize.

Copy link
Member

Choose a reason for hiding this comment

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

I may be missing something, but that's exactly why I added the suggestion - in the PR we're using customFontSizes and not customFontSize.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I see 🙂 Changed as suggested.


return (
<BaseFontSizePicker
{ ...props }
fontSizes={ fontSizes }
disableCustomFontSizes={ disableCustomFontSizes }
disableCustomFontSizes={ ! customFontSizes }
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
disableCustomFontSizes={ ! customFontSizes }
disableCustomFontSizes={ ! customFontSize }

isTextEnabled,
isHeadingEnabled,
isButtonEnabled,
] = useSettings( [
Copy link
Member

Choose a reason for hiding this comment

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

I think it could be nice to consider building an ESLint rule that prevents folks from having multiple useSetting calls at the same level, recommending that we use useSettings() instead.

Not for this PR, obviously, but something that we discussed earlier and I wanted to make sure it doesn't fall through the cracks.

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.

very nice update here, and one that is probably overdue. thanks for addressing this - the reduction in hook count is great.

export function useSettings( paths ) {
const { name: blockName, clientId = null } = useBlockEditContext();

paths = useMemo( () => paths, paths );
Copy link
Member

Choose a reason for hiding this comment

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

what's the purpose of this useMemo? am I mistaken in that it's doing nothing since all the internal function does is return its stored value?

Copy link
Member Author

Choose a reason for hiding this comment

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

On each useSettings call, the paths array will be different, although it contains the same values. This useMemo is to ensure that paths is a constant in that case, and to ensure that useSelect can memoize its return value. useSelect itself does something similar internally.

Copy link
Member

Choose a reason for hiding this comment

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

aha! the second value is the dependencies array, which in this case is paths itself. in my head I was reading [paths]

mergeCache.set( value, result );
}
return result;
}
Copy link
Member

Choose a reason for hiding this comment

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

it would be nice to have a docblock-style comment explaining the purpose of this function, and I wonder if the name could be clearer since mergeOrigins doesn't outwardly communicate that there's a cache involved.

I'm also confused on the value we're storing here, or why we need flatMap(). In the old code it's not clear to me either, so maybe this is why 🤷‍♂️ but it looks like it might be using .concat() with a default of [] to avoid needing a conditional for the case where result is missing the necessary key. I guess it's convenient for when keys are undefined and then it automatically returns an array with only those values.

but the old code was directly returning these things too while here we're storing them. since these are retrieved from value is there a need to split them out at this point? did you find that extracting these three properties was showing up in the hot path?

I'm so confused on all of this, really. is it worth adding a separate function and cache vs. directly building the array every time? maybe it was all confused by the nature of the original use of reduce()? I'm not trying to argue that the cache isn't worth it; I'm only asking if we have reason to believe it is.

const values = [];
[ 'default', 'theme', 'custom' ].forEach(
	( key ) => {
		if ( value[ key ] ) {
			values.push( value[ key ] );
		}
	}
)

return values;

Copy link
Member Author

Choose a reason for hiding this comment

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

Some settings, like color.palette, have a "raw" value where settings from different "origins" are in separate arrays:

{
  default: [ ... ],
  theme: [ ... ],
  custom: [ ... ],
}

The mergeOrigins function merges the three arrays into one, concatenating them together. I added memoization so that two calls to useSelect with the same state and arguments return the same value. This was a little pre-existing bug.

I added a docblock to the function that contains this explanation.

*/
export default function useSetting( path ) {
const { name: blockName, clientId } = useBlockEditContext();
export function useSettings( paths ) {
Copy link
Member

Choose a reason for hiding this comment

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

did you consider using variable argument counts here? it seems like most cases we'd be calling this we'll have literal values vs. dynamically-created arrays and I wonder if that's a convenience that would be nice.

const [  ] = useSettings( 'setting-a', 'setting-b', 'color.setting.thing' );

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that's a good idea. Variable arguments are better than an array argument. This is quite related to @tyxla's feedback about the module name:

Does it make sense to move useSettings() to a separate file? Perhaps under ../use-settings/index.js? Right now it's a bit unfortunate that use-setting/index.js exports useSettings().

To solve both problems, we could do this:

  1. Rewrite useSettings to use variable arguments.
  2. Migrate all remaining usages of useSetting to useSettings. The entire difference is the return value as array: const [ a ] = useSettings( 'a' ) vs const a = useSetting( 'a' ).
  3. This makes useSetting practically deprecated: there's no good reason any more to use it instead of useSettings.
  4. The actual source file can then be renamed to use-settings/index.js. The main thing it exports is useSettings, and then there's also the little useSetting back-compat wrapper. I think that solves the naming concerns very well.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I really like that idea 👍 one fewer new API while still supporting all original use cases. Any good reason not to go that way?

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented! All usages of useSetting are refactored to useSettings, and the source folder name is now use-settings.

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 17, 2023

@tyxla @dmsnell I believe I addressed all feedback, now ready for final review.

const usedLayout = ! layout?.type
? { ...defaultLayout, ...layout, type: 'default' }
: { ...defaultLayout, ...layout };
const { type = 'default' } = usedLayout;
Copy link
Member Author

Choose a reason for hiding this comment

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

@tellthemachines This is a change that could be a separate PR. I found that the useSetting( 'layout' ) call can be completely removed, because the usedLayout value is no longer used. Only its .type field is used. And the type can be calculated without knowing defaultLayout. That value always comes from the layout object.

usedLayout was used until #47477 before usages of __experimentalLayout, but today it's redundant.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah well spotted! Yes, this can be removed. Not too fussed about whether it's done here or in a separate PR, your choice 😄

@dmsnell
Copy link
Member

dmsnell commented Oct 17, 2023

@jsnajdr a couple questions on deprecation, you may have addressed and I may have missed.

  • should we add a deprecation notice to useSetting?
  • should we leave in a wrapping stub in use-setting/index.js?

I wasn't sure if any imports of useSetting change with the rename of the folder, but I think existing imports should continue to work. only wanted to double check that we don't suddenly break third-party code on this.

that being said, if we want to move away from useSetting maybe it would be good to call deprecated() in there and start pushing people towards useSettings?

@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 18, 2023

should we add a deprecation notice to useSetting?

I wasn't sure if I want to really formally deprecate useSetting, with warnings at all, but why not? I added a commit that does that.

should we leave in a wrapping stub in use-setting/index.js?

I think by now the names of the folders and files are sufficiently fine. The main thing that use-settings/index.js contains is the useSettings function, and the names correspond. Additionally it exports the legacy useSetting. It doesn't need to be a separate module.

I wasn't sure if any imports of useSetting change with the rename of the folder

This is just the internal layout of the block-editorpackage, and internal imports. The public API is exported by the root @wordpress/block-editor. And also by the /components sub-module, but that's also only package-internal.

Copy link
Member

@tyxla tyxla 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 great 🚀 Nice optimization!

I've left a few suggestions, but it's minor stuff and nothing really feels blocking.

I'm not particularly convinced that we should deprecate useSetting(), I'd rather expect that we discourage sibling useSetting() calls and encourage using useSettings() in those cases, but this is something we could explore separately.

@@ -2,21 +2,23 @@

## Unreleased

- Deprecated the `useSetting` function in favor of new `useSettings` one that can retrieve multiple settings at once.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- Deprecated the `useSetting` function in favor of new `useSettings` one that can retrieve multiple settings at once.
- Deprecated the `useSetting` function in favor of new `useSettings` one that can retrieve multiple settings at once ([#55337](https://github.com/WordPress/gutenberg/pull/55337)).

@@ -944,9 +944,11 @@ _Parameters_

### useSetting

> **Deprecated**

Copy link
Member

Choose a reason for hiding this comment

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

Should we add a note right here that it's deprecated in favor of useSettings()?

Copy link
Member Author

Choose a reason for hiding this comment

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

Note added 👍

@@ -98,7 +98,7 @@ function UncontrolledInnerBlocks( props ) {

const { allowSizingOnChildren = false } = defaultLayoutBlockSupport;

const defaultLayout = useSetting( 'layout' ) || EMPTY_OBJECT;
const [ defaultLayout ] = useSettings( 'layout' );
Copy link
Member

Choose a reason for hiding this comment

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

Should we keep the EMPTY_OBJECT fallback here?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's not needed because defaultLayout is used only with an object spread operator, ...defaultLayout, which treats undefined or null as an empty object already.


const [ settingsSizes ] = useSettings( 'spacing.spacingSizes' );
if ( settingsSizes ) {
spacingSizes.push( ...settingsSizes );
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice to avoid the mutation here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that local mutation is fine, it's not worse in any way than creating a new array and concatenating existing arrays into it with the spread operator.


// 0. Allow third parties to filter the block's settings at runtime.
let result = applyFilters(
'blockEditor.useSetting.before',
Copy link
Member

Choose a reason for hiding this comment

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

There's no need to introduce a bulk settings filter right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Certainly not, it would be counterproductive. If you want to modify a setting with a plugin, you want to register only one filter, not two of them, both useSetting and useSettings.

return storedResult;
}

describe( 'useSettings', () => {
Copy link
Member

Choose a reason for hiding this comment

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

I'd keep a test for useSetting() too, even if it's just a single one.

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 a test for useSetting 👍

@@ -68,14 +68,9 @@ function Controls( {
[ minHeight ]
);

const [ availableUnits ] = useSettings( 'spacing.units' );
const units = useCustomUnits( {
Copy link
Member

Choose a reason for hiding this comment

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

Not from this PR, but this appears to be so repetitive! I wonder if we should abstract it to a separate hook with these default args.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes! The default units change from case to case, but the rest is the same.

const layout = useSetting( 'layout' );
const [ fluidTypographySettings, layout ] = useSettings(
'typography.fluid',
'layout'
Copy link
Member

Choose a reason for hiding this comment

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

Should we get layout.wideSize directly here since that's the only one we need?

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe, but I'll keep the existing 'layout' retrieval. The codebase consistently uses 'layout', never its subfields.

const spacingSizes = [ { name: 0, slug: '0', size: 0 } ];
const [ settingsSizes ] = useSettings( 'spacing.spacingSizes' );
if ( settingsSizes ) {
spacingSizes.push( ...settingsSizes );
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice if we could avoid mutation.

* ```
*/
export function useSetting( path ) {
deprecated( 'wp.blockEditor.useSetting', {
Copy link
Member

Choose a reason for hiding this comment

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

At the same time, I'm not convinced useSetting() really needs to be deprecated. It uses useSettings() under the hood, so what difference does it make if we continue using it when we want to retrieve a single setting?

Copy link
Member Author

Choose a reason for hiding this comment

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

Retrieving a single setting is very ergonomic even with useSettings. Having also useSetting doesn't add any interesting improvement. Therefore, it's a good idea to support only one canonical API, and clearly mark the other one as "inferior".

@jsnajdr jsnajdr merged commit d70f278 into trunk Oct 19, 2023
50 checks passed
@jsnajdr jsnajdr deleted the optimize/use-setting branch October 19, 2023 07:25
@github-actions github-actions bot added this to the Gutenberg 17.0 milestone Oct 19, 2023
@jsnajdr
Copy link
Member Author

jsnajdr commented Oct 20, 2023

This PR delivered a significant improvement in performance tests, namely for the "Type" and "Inserter Hovering Items" stats. Immediately visible in the charts on codevitals.run:

Screenshot 2023-10-20 at 17 08 05 Screenshot 2023-10-20 at 17 08 25

@fabiankaegy
Copy link
Member

This should have been called out in the Misc Dev note for 6.4 as it deprecates a stabile hook

@Mamaduka
Copy link
Member

I don't remember this being cherry-picked for 6.4. Based on the Gutenberg version this hasn't shipped with latest WP.

@youknowriad youknowriad added the Needs Dev Note Requires a developer note for a major WordPress release cycle label Nov 21, 2023
@youknowriad
Copy link
Contributor

Just added the dev note label to avoid forgetting about it for 6.5

@jsnajdr
Copy link
Member Author

jsnajdr commented Nov 21, 2023

I just downloaded WordPress 6.4.1 and inspected the wp-content/js/dist/block-editor.js file, and it still has the old version of useSetting and no useSettings. The patch wasn't backported, it's only in the Gutenberg plugin and will become part of Core only in 6.5.

@tyxla
Copy link
Member

tyxla commented Nov 21, 2023

Yes, 6.4 shipped with Gutenberg up to 16.7, so anything that's in 16.8 or newer and wasn't specifically cherry-picked didn't make it into the release.

@fabiankaegy
Copy link
Member

Thanks all for the confirmation and sorry for jumping to conclusions too early ❤️

Appreciate you all!

@fabiankaegy
Copy link
Member

Should we update the version number in the deprecation just for greater clarity?

I created a PR for it here: #56377

@@ -112,6 +113,8 @@ export function setImmutably( object, path, value ) {
return newObject;
}

const stringToPath = memoize( ( path ) => path.split( '.' ) );
Copy link
Member

Choose a reason for hiding this comment

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

This has the drawback of taking up memory that is never released. Maybe harmless here because it's small strings that probably get reused, but still worth nothing. I guess this is a weird case where a max size doesn't make sense. See also https://github.com/WordPress/gutenberg/pull/53406/files#r1286726012

Copy link
Member Author

Choose a reason for hiding this comment

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

Removing in #56711. I remember when profiling, the memoization didn't have any impact on performance anyway.

_Usage_

```js
const [ fixed, sticky ] = useSettings( 'position.fixed', 'position.sticky' );
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit confused here because the PR description says

const [ allowFixed, allowSticky ] = useSettings( [ 'position.fixed', 'position.sticky' ] );

Can both be used? It would be nice to be consistent so we don't need to normalise.

Copy link
Member Author

Choose a reason for hiding this comment

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

Only the useSettings( 'a', 'b' ) form can be used. The PR originally started with an array argument, useSettings( settings: string[] ), but then was changed to useSettings( ...settings: string[] ) during review. That's why the PR description is out of date.

The main reason for that was that when reading a single setting, useSetting( 'a' ) and useSettings( 'a' ) are the same, except that for useSettings the return value is always an array (with one element).

@jsnajdr
Copy link
Member Author

jsnajdr commented Mar 1, 2024

Dev note

New useSettings hook for reading block instance settings

When trying to improve performance of the block editor for WordPress 6.5, one of the identified issues was related to the useSetting hook that is used by block instances to read various settings provided by the environment they are in: parent blocks, the theme, the block editor itself. It turns out that it's inefficent to read multiple settings with separate useSetting calls:

const allowFixed = useSetting( 'position.fixed' );
const allowSticky = useSetting( 'position.sticky' );

The editor performance is improved when all the settings are read at once, using a new hook called useSettings:

const [ allowFixed, allowSticky ] = useSettings( 'position.fixed', 'position.sticky' );

That's why WordPress 6.5 introduces this new hook. The useSetting (singular) hook is now deprecated (using it will trigger a console warning), because reading even a single setting is very easy with the useSettings (plural) hook:

const [ allowFixed ] = useSettings( 'position.fixed' );

The only change is that the useSettings hook always returns an array, and the array needs to be destructured to read the single value. Supporting two APIs for performing the same task is therefore not justified.

@ellatrix
Copy link
Member

ellatrix commented Mar 1, 2024

Makes me wonder if we should just expose the getBlockSettings selector too.

@jsnajdr
Copy link
Member Author

jsnajdr commented Mar 5, 2024

we should just expose the getBlockSettings selector too.

Yes, of course, let's do that! After all, useSettings is just a very thin wrapper around this. And the selector call could be merged into existing useSelect calls, reducing the number of store subscriptions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Dev Note Requires a developer note for a major WordPress release cycle [Package] Block editor /packages/block-editor [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants